blob: 02deb7c6160a60c25c03078ebf2a35ec88a5a060 [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
2120 self.assertRaises(TypeError, combine, d, t, 1) # too many args
2121 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2122 self.assertRaises(TypeError, combine, d, "time") # wrong type
2123 self.assertRaises(TypeError, combine, "date", t) # wrong type
2124
2125 def test_replace(self):
2126 cls = self.theclass
2127 args = [1, 2, 3, 4, 5, 6, 7]
2128 base = cls(*args)
2129 self.assertEqual(base, base.replace())
2130
2131 i = 0
2132 for name, newval in (("year", 2),
2133 ("month", 3),
2134 ("day", 4),
2135 ("hour", 5),
2136 ("minute", 6),
2137 ("second", 7),
2138 ("microsecond", 8)):
2139 newargs = args[:]
2140 newargs[i] = newval
2141 expected = cls(*newargs)
2142 got = base.replace(**{name: newval})
2143 self.assertEqual(expected, got)
2144 i += 1
2145
2146 # Out of bounds.
2147 base = cls(2000, 2, 29)
2148 self.assertRaises(ValueError, base.replace, year=2001)
2149
2150 def test_astimezone(self):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002151 return # The rest is no longer applicable
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002152 # Pretty boring! The TZ test is more interesting here. astimezone()
2153 # simply can't be applied to a naive object.
2154 dt = self.theclass.now()
2155 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002156 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002157 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2158 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2159 self.assertRaises(ValueError, dt.astimezone, f) # naive
2160 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2161
2162 class Bogus(tzinfo):
2163 def utcoffset(self, dt): return None
2164 def dst(self, dt): return timedelta(0)
2165 bog = Bogus()
2166 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2167 self.assertRaises(ValueError,
2168 dt.replace(tzinfo=bog).astimezone, f)
2169
2170 class AlsoBogus(tzinfo):
2171 def utcoffset(self, dt): return timedelta(0)
2172 def dst(self, dt): return None
2173 alsobog = AlsoBogus()
2174 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2175
2176 def test_subclass_datetime(self):
2177
2178 class C(self.theclass):
2179 theAnswer = 42
2180
2181 def __new__(cls, *args, **kws):
2182 temp = kws.copy()
2183 extra = temp.pop('extra')
2184 result = self.theclass.__new__(cls, *args, **temp)
2185 result.extra = extra
2186 return result
2187
2188 def newmeth(self, start):
2189 return start + self.year + self.month + self.second
2190
2191 args = 2003, 4, 14, 12, 13, 41
2192
2193 dt1 = self.theclass(*args)
2194 dt2 = C(*args, **{'extra': 7})
2195
2196 self.assertEqual(dt2.__class__, C)
2197 self.assertEqual(dt2.theAnswer, 42)
2198 self.assertEqual(dt2.extra, 7)
2199 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2200 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2201 dt1.second - 7)
2202
2203class TestSubclassDateTime(TestDateTime):
2204 theclass = SubclassDatetime
2205 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002206 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002207 def test_roundtrip(self):
2208 pass
2209
2210class SubclassTime(time):
2211 sub_var = 1
2212
2213class TestTime(HarmlessMixedComparison, unittest.TestCase):
2214
2215 theclass = time
2216
2217 def test_basic_attributes(self):
2218 t = self.theclass(12, 0)
2219 self.assertEqual(t.hour, 12)
2220 self.assertEqual(t.minute, 0)
2221 self.assertEqual(t.second, 0)
2222 self.assertEqual(t.microsecond, 0)
2223
2224 def test_basic_attributes_nonzero(self):
2225 # Make sure all attributes are non-zero so bugs in
2226 # bit-shifting access show up.
2227 t = self.theclass(12, 59, 59, 8000)
2228 self.assertEqual(t.hour, 12)
2229 self.assertEqual(t.minute, 59)
2230 self.assertEqual(t.second, 59)
2231 self.assertEqual(t.microsecond, 8000)
2232
2233 def test_roundtrip(self):
2234 t = self.theclass(1, 2, 3, 4)
2235
2236 # Verify t -> string -> time identity.
2237 s = repr(t)
2238 self.assertTrue(s.startswith('datetime.'))
2239 s = s[9:]
2240 t2 = eval(s)
2241 self.assertEqual(t, t2)
2242
2243 # Verify identity via reconstructing from pieces.
2244 t2 = self.theclass(t.hour, t.minute, t.second,
2245 t.microsecond)
2246 self.assertEqual(t, t2)
2247
2248 def test_comparing(self):
2249 args = [1, 2, 3, 4]
2250 t1 = self.theclass(*args)
2251 t2 = self.theclass(*args)
2252 self.assertEqual(t1, t2)
2253 self.assertTrue(t1 <= t2)
2254 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002255 self.assertFalse(t1 != t2)
2256 self.assertFalse(t1 < t2)
2257 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002258
2259 for i in range(len(args)):
2260 newargs = args[:]
2261 newargs[i] = args[i] + 1
2262 t2 = self.theclass(*newargs) # this is larger than t1
2263 self.assertTrue(t1 < t2)
2264 self.assertTrue(t2 > t1)
2265 self.assertTrue(t1 <= t2)
2266 self.assertTrue(t2 >= t1)
2267 self.assertTrue(t1 != t2)
2268 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002269 self.assertFalse(t1 == t2)
2270 self.assertFalse(t2 == t1)
2271 self.assertFalse(t1 > t2)
2272 self.assertFalse(t2 < t1)
2273 self.assertFalse(t1 >= t2)
2274 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002275
2276 for badarg in OTHERSTUFF:
2277 self.assertEqual(t1 == badarg, False)
2278 self.assertEqual(t1 != badarg, True)
2279 self.assertEqual(badarg == t1, False)
2280 self.assertEqual(badarg != t1, True)
2281
2282 self.assertRaises(TypeError, lambda: t1 <= badarg)
2283 self.assertRaises(TypeError, lambda: t1 < badarg)
2284 self.assertRaises(TypeError, lambda: t1 > badarg)
2285 self.assertRaises(TypeError, lambda: t1 >= badarg)
2286 self.assertRaises(TypeError, lambda: badarg <= t1)
2287 self.assertRaises(TypeError, lambda: badarg < t1)
2288 self.assertRaises(TypeError, lambda: badarg > t1)
2289 self.assertRaises(TypeError, lambda: badarg >= t1)
2290
2291 def test_bad_constructor_arguments(self):
2292 # bad hours
2293 self.theclass(0, 0) # no exception
2294 self.theclass(23, 0) # no exception
2295 self.assertRaises(ValueError, self.theclass, -1, 0)
2296 self.assertRaises(ValueError, self.theclass, 24, 0)
2297 # bad minutes
2298 self.theclass(23, 0) # no exception
2299 self.theclass(23, 59) # no exception
2300 self.assertRaises(ValueError, self.theclass, 23, -1)
2301 self.assertRaises(ValueError, self.theclass, 23, 60)
2302 # bad seconds
2303 self.theclass(23, 59, 0) # no exception
2304 self.theclass(23, 59, 59) # no exception
2305 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2306 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2307 # bad microseconds
2308 self.theclass(23, 59, 59, 0) # no exception
2309 self.theclass(23, 59, 59, 999999) # no exception
2310 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2311 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2312
2313 def test_hash_equality(self):
2314 d = self.theclass(23, 30, 17)
2315 e = self.theclass(23, 30, 17)
2316 self.assertEqual(d, e)
2317 self.assertEqual(hash(d), hash(e))
2318
2319 dic = {d: 1}
2320 dic[e] = 2
2321 self.assertEqual(len(dic), 1)
2322 self.assertEqual(dic[d], 2)
2323 self.assertEqual(dic[e], 2)
2324
2325 d = self.theclass(0, 5, 17)
2326 e = self.theclass(0, 5, 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 def test_isoformat(self):
2337 t = self.theclass(4, 5, 1, 123)
2338 self.assertEqual(t.isoformat(), "04:05:01.000123")
2339 self.assertEqual(t.isoformat(), str(t))
2340
2341 t = self.theclass()
2342 self.assertEqual(t.isoformat(), "00:00:00")
2343 self.assertEqual(t.isoformat(), str(t))
2344
2345 t = self.theclass(microsecond=1)
2346 self.assertEqual(t.isoformat(), "00:00:00.000001")
2347 self.assertEqual(t.isoformat(), str(t))
2348
2349 t = self.theclass(microsecond=10)
2350 self.assertEqual(t.isoformat(), "00:00:00.000010")
2351 self.assertEqual(t.isoformat(), str(t))
2352
2353 t = self.theclass(microsecond=100)
2354 self.assertEqual(t.isoformat(), "00:00:00.000100")
2355 self.assertEqual(t.isoformat(), str(t))
2356
2357 t = self.theclass(microsecond=1000)
2358 self.assertEqual(t.isoformat(), "00:00:00.001000")
2359 self.assertEqual(t.isoformat(), str(t))
2360
2361 t = self.theclass(microsecond=10000)
2362 self.assertEqual(t.isoformat(), "00:00:00.010000")
2363 self.assertEqual(t.isoformat(), str(t))
2364
2365 t = self.theclass(microsecond=100000)
2366 self.assertEqual(t.isoformat(), "00:00:00.100000")
2367 self.assertEqual(t.isoformat(), str(t))
2368
Alexander Belopolskya2998a62016-03-06 14:58:43 -05002369 t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2370 self.assertEqual(t.isoformat(timespec='hours'), "12")
2371 self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2372 self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2373 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2374 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2375 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2376 self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2377
2378 t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2379 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2380
2381 t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2382 self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2383 self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2384 self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2385
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002386 def test_1653736(self):
2387 # verify it doesn't accept extra keyword arguments
2388 t = self.theclass(second=1)
2389 self.assertRaises(TypeError, t.isoformat, foo=3)
2390
2391 def test_strftime(self):
2392 t = self.theclass(1, 2, 3, 4)
2393 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2394 # A naive object replaces %z and %Z with empty strings.
2395 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2396
2397 def test_format(self):
2398 t = self.theclass(1, 2, 3, 4)
2399 self.assertEqual(t.__format__(''), str(t))
2400
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002401 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002402 t.__format__(123)
2403
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002404 # check that a derived class's __str__() gets called
2405 class A(self.theclass):
2406 def __str__(self):
2407 return 'A'
2408 a = A(1, 2, 3, 4)
2409 self.assertEqual(a.__format__(''), 'A')
2410
2411 # check that a derived class's strftime gets called
2412 class B(self.theclass):
2413 def strftime(self, format_spec):
2414 return 'B'
2415 b = B(1, 2, 3, 4)
2416 self.assertEqual(b.__format__(''), str(t))
2417
2418 for fmt in ['%H %M %S',
2419 ]:
2420 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2421 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2422 self.assertEqual(b.__format__(fmt), 'B')
2423
2424 def test_str(self):
2425 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2426 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2427 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2428 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2429 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2430
2431 def test_repr(self):
2432 name = 'datetime.' + self.theclass.__name__
2433 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2434 "%s(1, 2, 3, 4)" % name)
2435 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2436 "%s(10, 2, 3, 4000)" % name)
2437 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2438 "%s(0, 2, 3, 400000)" % name)
2439 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2440 "%s(12, 2, 3)" % name)
2441 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2442 "%s(23, 15)" % name)
2443
2444 def test_resolution_info(self):
2445 self.assertIsInstance(self.theclass.min, self.theclass)
2446 self.assertIsInstance(self.theclass.max, self.theclass)
2447 self.assertIsInstance(self.theclass.resolution, timedelta)
2448 self.assertTrue(self.theclass.max > self.theclass.min)
2449
2450 def test_pickling(self):
2451 args = 20, 59, 16, 64**2
2452 orig = self.theclass(*args)
2453 for pickler, unpickler, proto in pickle_choices:
2454 green = pickler.dumps(orig, proto)
2455 derived = unpickler.loads(green)
2456 self.assertEqual(orig, derived)
2457
2458 def test_pickling_subclass_time(self):
2459 args = 20, 59, 16, 64**2
2460 orig = SubclassTime(*args)
2461 for pickler, unpickler, proto in pickle_choices:
2462 green = pickler.dumps(orig, proto)
2463 derived = unpickler.loads(green)
2464 self.assertEqual(orig, derived)
2465
2466 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002467 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002468 cls = self.theclass
2469 self.assertTrue(cls(1))
2470 self.assertTrue(cls(0, 1))
2471 self.assertTrue(cls(0, 0, 1))
2472 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002473 self.assertTrue(cls(0))
2474 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002475
2476 def test_replace(self):
2477 cls = self.theclass
2478 args = [1, 2, 3, 4]
2479 base = cls(*args)
2480 self.assertEqual(base, base.replace())
2481
2482 i = 0
2483 for name, newval in (("hour", 5),
2484 ("minute", 6),
2485 ("second", 7),
2486 ("microsecond", 8)):
2487 newargs = args[:]
2488 newargs[i] = newval
2489 expected = cls(*newargs)
2490 got = base.replace(**{name: newval})
2491 self.assertEqual(expected, got)
2492 i += 1
2493
2494 # Out of bounds.
2495 base = cls(1)
2496 self.assertRaises(ValueError, base.replace, hour=24)
2497 self.assertRaises(ValueError, base.replace, minute=-1)
2498 self.assertRaises(ValueError, base.replace, second=100)
2499 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2500
2501 def test_subclass_time(self):
2502
2503 class C(self.theclass):
2504 theAnswer = 42
2505
2506 def __new__(cls, *args, **kws):
2507 temp = kws.copy()
2508 extra = temp.pop('extra')
2509 result = self.theclass.__new__(cls, *args, **temp)
2510 result.extra = extra
2511 return result
2512
2513 def newmeth(self, start):
2514 return start + self.hour + self.second
2515
2516 args = 4, 5, 6
2517
2518 dt1 = self.theclass(*args)
2519 dt2 = C(*args, **{'extra': 7})
2520
2521 self.assertEqual(dt2.__class__, C)
2522 self.assertEqual(dt2.theAnswer, 42)
2523 self.assertEqual(dt2.extra, 7)
2524 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2525 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2526
2527 def test_backdoor_resistance(self):
2528 # see TestDate.test_backdoor_resistance().
2529 base = '2:59.0'
2530 for hour_byte in ' ', '9', chr(24), '\xff':
2531 self.assertRaises(TypeError, self.theclass,
2532 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002533 # Good bytes, but bad tzinfo:
2534 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2535 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002536
2537# A mixin for classes with a tzinfo= argument. Subclasses must define
Martin Panter46f50722016-05-26 05:35:26 +00002538# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002539# must be legit (which is true for time and datetime).
2540class TZInfoBase:
2541
2542 def test_argument_passing(self):
2543 cls = self.theclass
2544 # A datetime passes itself on, a time passes None.
2545 class introspective(tzinfo):
2546 def tzname(self, dt): return dt and "real" or "none"
2547 def utcoffset(self, dt):
2548 return timedelta(minutes = dt and 42 or -42)
2549 dst = utcoffset
2550
2551 obj = cls(1, 2, 3, tzinfo=introspective())
2552
2553 expected = cls is time and "none" or "real"
2554 self.assertEqual(obj.tzname(), expected)
2555
2556 expected = timedelta(minutes=(cls is time and -42 or 42))
2557 self.assertEqual(obj.utcoffset(), expected)
2558 self.assertEqual(obj.dst(), expected)
2559
2560 def test_bad_tzinfo_classes(self):
2561 cls = self.theclass
2562 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2563
2564 class NiceTry(object):
2565 def __init__(self): pass
2566 def utcoffset(self, dt): pass
2567 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2568
2569 class BetterTry(tzinfo):
2570 def __init__(self): pass
2571 def utcoffset(self, dt): pass
2572 b = BetterTry()
2573 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002574 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002575
2576 def test_utc_offset_out_of_bounds(self):
2577 class Edgy(tzinfo):
2578 def __init__(self, offset):
2579 self.offset = timedelta(minutes=offset)
2580 def utcoffset(self, dt):
2581 return self.offset
2582
2583 cls = self.theclass
2584 for offset, legit in ((-1440, False),
2585 (-1439, True),
2586 (1439, True),
2587 (1440, False)):
2588 if cls is time:
2589 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2590 elif cls is datetime:
2591 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2592 else:
2593 assert 0, "impossible"
2594 if legit:
2595 aofs = abs(offset)
2596 h, m = divmod(aofs, 60)
2597 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2598 if isinstance(t, datetime):
2599 t = t.timetz()
2600 self.assertEqual(str(t), "01:02:03" + tag)
2601 else:
2602 self.assertRaises(ValueError, str, t)
2603
2604 def test_tzinfo_classes(self):
2605 cls = self.theclass
2606 class C1(tzinfo):
2607 def utcoffset(self, dt): return None
2608 def dst(self, dt): return None
2609 def tzname(self, dt): return None
2610 for t in (cls(1, 1, 1),
2611 cls(1, 1, 1, tzinfo=None),
2612 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002613 self.assertIsNone(t.utcoffset())
2614 self.assertIsNone(t.dst())
2615 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002616
2617 class C3(tzinfo):
2618 def utcoffset(self, dt): return timedelta(minutes=-1439)
2619 def dst(self, dt): return timedelta(minutes=1439)
2620 def tzname(self, dt): return "aname"
2621 t = cls(1, 1, 1, tzinfo=C3())
2622 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2623 self.assertEqual(t.dst(), timedelta(minutes=1439))
2624 self.assertEqual(t.tzname(), "aname")
2625
2626 # Wrong types.
2627 class C4(tzinfo):
2628 def utcoffset(self, dt): return "aname"
2629 def dst(self, dt): return 7
2630 def tzname(self, dt): return 0
2631 t = cls(1, 1, 1, tzinfo=C4())
2632 self.assertRaises(TypeError, t.utcoffset)
2633 self.assertRaises(TypeError, t.dst)
2634 self.assertRaises(TypeError, t.tzname)
2635
2636 # Offset out of range.
2637 class C6(tzinfo):
2638 def utcoffset(self, dt): return timedelta(hours=-24)
2639 def dst(self, dt): return timedelta(hours=24)
2640 t = cls(1, 1, 1, tzinfo=C6())
2641 self.assertRaises(ValueError, t.utcoffset)
2642 self.assertRaises(ValueError, t.dst)
2643
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002644 # Not a whole number of seconds.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002645 class C7(tzinfo):
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04002646 def utcoffset(self, dt): return timedelta(microseconds=61)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002647 def dst(self, dt): return timedelta(microseconds=-81)
2648 t = cls(1, 1, 1, tzinfo=C7())
2649 self.assertRaises(ValueError, t.utcoffset)
2650 self.assertRaises(ValueError, t.dst)
2651
2652 def test_aware_compare(self):
2653 cls = self.theclass
2654
2655 # Ensure that utcoffset() gets ignored if the comparands have
2656 # the same tzinfo member.
2657 class OperandDependentOffset(tzinfo):
2658 def utcoffset(self, t):
2659 if t.minute < 10:
2660 # d0 and d1 equal after adjustment
2661 return timedelta(minutes=t.minute)
2662 else:
2663 # d2 off in the weeds
2664 return timedelta(minutes=59)
2665
2666 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2667 d0 = base.replace(minute=3)
2668 d1 = base.replace(minute=9)
2669 d2 = base.replace(minute=11)
2670 for x in d0, d1, d2:
2671 for y in d0, d1, d2:
2672 for op in lt, le, gt, ge, eq, ne:
2673 got = op(x, y)
2674 expected = op(x.minute, y.minute)
2675 self.assertEqual(got, expected)
2676
2677 # However, if they're different members, uctoffset is not ignored.
2678 # Note that a time can't actually have an operand-depedent offset,
2679 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2680 # so skip this test for time.
2681 if cls is not time:
2682 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2683 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2684 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2685 for x in d0, d1, d2:
2686 for y in d0, d1, d2:
2687 got = (x > y) - (x < y)
2688 if (x is d0 or x is d1) and (y is d0 or y is d1):
2689 expected = 0
2690 elif x is y is d2:
2691 expected = 0
2692 elif x is d2:
2693 expected = -1
2694 else:
2695 assert y is d2
2696 expected = 1
2697 self.assertEqual(got, expected)
2698
2699
2700# Testing time objects with a non-None tzinfo.
2701class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2702 theclass = time
2703
2704 def test_empty(self):
2705 t = self.theclass()
2706 self.assertEqual(t.hour, 0)
2707 self.assertEqual(t.minute, 0)
2708 self.assertEqual(t.second, 0)
2709 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002710 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002711
2712 def test_zones(self):
2713 est = FixedOffset(-300, "EST", 1)
2714 utc = FixedOffset(0, "UTC", -2)
2715 met = FixedOffset(60, "MET", 3)
2716 t1 = time( 7, 47, tzinfo=est)
2717 t2 = time(12, 47, tzinfo=utc)
2718 t3 = time(13, 47, tzinfo=met)
2719 t4 = time(microsecond=40)
2720 t5 = time(microsecond=40, tzinfo=utc)
2721
2722 self.assertEqual(t1.tzinfo, est)
2723 self.assertEqual(t2.tzinfo, utc)
2724 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002725 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002726 self.assertEqual(t5.tzinfo, utc)
2727
2728 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2729 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2730 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002731 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002732 self.assertRaises(TypeError, t1.utcoffset, "no args")
2733
2734 self.assertEqual(t1.tzname(), "EST")
2735 self.assertEqual(t2.tzname(), "UTC")
2736 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002737 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002738 self.assertRaises(TypeError, t1.tzname, "no args")
2739
2740 self.assertEqual(t1.dst(), timedelta(minutes=1))
2741 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2742 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002743 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002744 self.assertRaises(TypeError, t1.dst, "no args")
2745
2746 self.assertEqual(hash(t1), hash(t2))
2747 self.assertEqual(hash(t1), hash(t3))
2748 self.assertEqual(hash(t2), hash(t3))
2749
2750 self.assertEqual(t1, t2)
2751 self.assertEqual(t1, t3)
2752 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002753 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002754 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2755 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2756
2757 self.assertEqual(str(t1), "07:47:00-05:00")
2758 self.assertEqual(str(t2), "12:47:00+00:00")
2759 self.assertEqual(str(t3), "13:47:00+01:00")
2760 self.assertEqual(str(t4), "00:00:00.000040")
2761 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2762
2763 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2764 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2765 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2766 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2767 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2768
2769 d = 'datetime.time'
2770 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2771 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2772 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2773 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2774 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2775
2776 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2777 "07:47:00 %Z=EST %z=-0500")
2778 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2779 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2780
2781 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2782 t1 = time(23, 59, tzinfo=yuck)
2783 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2784 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2785
2786 # Check that an invalid tzname result raises an exception.
2787 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002788 tz = 42
2789 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002790 t = time(2, 3, 4, tzinfo=Badtzname())
2791 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2792 self.assertRaises(TypeError, t.strftime, "%Z")
2793
Alexander Belopolskye239d232010-12-08 23:31:48 +00002794 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002795 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002796 Badtzname.tz = '\ud800'
2797 self.assertRaises(ValueError, t.strftime, "%Z")
2798
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002799 def test_hash_edge_cases(self):
2800 # Offsets that overflow a basic time.
2801 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2802 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2803 self.assertEqual(hash(t1), hash(t2))
2804
2805 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2806 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2807 self.assertEqual(hash(t1), hash(t2))
2808
2809 def test_pickling(self):
2810 # Try one without a tzinfo.
2811 args = 20, 59, 16, 64**2
2812 orig = self.theclass(*args)
2813 for pickler, unpickler, proto in pickle_choices:
2814 green = pickler.dumps(orig, proto)
2815 derived = unpickler.loads(green)
2816 self.assertEqual(orig, derived)
2817
2818 # Try one with a tzinfo.
2819 tinfo = PicklableFixedOffset(-300, 'cookie')
2820 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2821 for pickler, unpickler, proto in pickle_choices:
2822 green = pickler.dumps(orig, proto)
2823 derived = unpickler.loads(green)
2824 self.assertEqual(orig, derived)
2825 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2826 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2827 self.assertEqual(derived.tzname(), 'cookie')
2828
2829 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002830 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002831 cls = self.theclass
2832
2833 t = cls(0, tzinfo=FixedOffset(-300, ""))
2834 self.assertTrue(t)
2835
2836 t = cls(5, tzinfo=FixedOffset(-300, ""))
2837 self.assertTrue(t)
2838
2839 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002840 self.assertTrue(t)
2841
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002842 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2843 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002844
2845 def test_replace(self):
2846 cls = self.theclass
2847 z100 = FixedOffset(100, "+100")
2848 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2849 args = [1, 2, 3, 4, z100]
2850 base = cls(*args)
2851 self.assertEqual(base, base.replace())
2852
2853 i = 0
2854 for name, newval in (("hour", 5),
2855 ("minute", 6),
2856 ("second", 7),
2857 ("microsecond", 8),
2858 ("tzinfo", zm200)):
2859 newargs = args[:]
2860 newargs[i] = newval
2861 expected = cls(*newargs)
2862 got = base.replace(**{name: newval})
2863 self.assertEqual(expected, got)
2864 i += 1
2865
2866 # Ensure we can get rid of a tzinfo.
2867 self.assertEqual(base.tzname(), "+100")
2868 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002869 self.assertIsNone(base2.tzinfo)
2870 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002871
2872 # Ensure we can add one.
2873 base3 = base2.replace(tzinfo=z100)
2874 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002875 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002876
2877 # Out of bounds.
2878 base = cls(1)
2879 self.assertRaises(ValueError, base.replace, hour=24)
2880 self.assertRaises(ValueError, base.replace, minute=-1)
2881 self.assertRaises(ValueError, base.replace, second=100)
2882 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2883
2884 def test_mixed_compare(self):
2885 t1 = time(1, 2, 3)
2886 t2 = time(1, 2, 3)
2887 self.assertEqual(t1, t2)
2888 t2 = t2.replace(tzinfo=None)
2889 self.assertEqual(t1, t2)
2890 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2891 self.assertEqual(t1, t2)
2892 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002893 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002894
2895 # In time w/ identical tzinfo objects, utcoffset is ignored.
2896 class Varies(tzinfo):
2897 def __init__(self):
2898 self.offset = timedelta(minutes=22)
2899 def utcoffset(self, t):
2900 self.offset += timedelta(minutes=1)
2901 return self.offset
2902
2903 v = Varies()
2904 t1 = t2.replace(tzinfo=v)
2905 t2 = t2.replace(tzinfo=v)
2906 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2907 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2908 self.assertEqual(t1, t2)
2909
2910 # But if they're not identical, it isn't ignored.
2911 t2 = t2.replace(tzinfo=Varies())
2912 self.assertTrue(t1 < t2) # t1's offset counter still going up
2913
2914 def test_subclass_timetz(self):
2915
2916 class C(self.theclass):
2917 theAnswer = 42
2918
2919 def __new__(cls, *args, **kws):
2920 temp = kws.copy()
2921 extra = temp.pop('extra')
2922 result = self.theclass.__new__(cls, *args, **temp)
2923 result.extra = extra
2924 return result
2925
2926 def newmeth(self, start):
2927 return start + self.hour + self.second
2928
2929 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2930
2931 dt1 = self.theclass(*args)
2932 dt2 = C(*args, **{'extra': 7})
2933
2934 self.assertEqual(dt2.__class__, C)
2935 self.assertEqual(dt2.theAnswer, 42)
2936 self.assertEqual(dt2.extra, 7)
2937 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2938 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2939
2940
2941# Testing datetime objects with a non-None tzinfo.
2942
2943class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2944 theclass = datetime
2945
2946 def test_trivial(self):
2947 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2948 self.assertEqual(dt.year, 1)
2949 self.assertEqual(dt.month, 2)
2950 self.assertEqual(dt.day, 3)
2951 self.assertEqual(dt.hour, 4)
2952 self.assertEqual(dt.minute, 5)
2953 self.assertEqual(dt.second, 6)
2954 self.assertEqual(dt.microsecond, 7)
2955 self.assertEqual(dt.tzinfo, None)
2956
2957 def test_even_more_compare(self):
2958 # The test_compare() and test_more_compare() inherited from TestDate
2959 # and TestDateTime covered non-tzinfo cases.
2960
2961 # Smallest possible after UTC adjustment.
2962 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2963 # Largest possible after UTC adjustment.
2964 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2965 tzinfo=FixedOffset(-1439, ""))
2966
2967 # Make sure those compare correctly, and w/o overflow.
2968 self.assertTrue(t1 < t2)
2969 self.assertTrue(t1 != t2)
2970 self.assertTrue(t2 > t1)
2971
2972 self.assertEqual(t1, t1)
2973 self.assertEqual(t2, t2)
2974
2975 # Equal afer adjustment.
2976 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2977 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2978 self.assertEqual(t1, t2)
2979
2980 # Change t1 not to subtract a minute, and t1 should be larger.
2981 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2982 self.assertTrue(t1 > t2)
2983
2984 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2985 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2986 self.assertTrue(t1 < t2)
2987
2988 # Back to the original t1, but make seconds resolve it.
2989 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2990 second=1)
2991 self.assertTrue(t1 > t2)
2992
2993 # Likewise, but make microseconds resolve it.
2994 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2995 microsecond=1)
2996 self.assertTrue(t1 > t2)
2997
Alexander Belopolsky08313822012-06-15 20:19:47 -04002998 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002999 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04003000 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003001 self.assertEqual(t2, t2)
3002
3003 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3004 class Naive(tzinfo):
3005 def utcoffset(self, dt): return None
3006 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04003007 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003008 self.assertEqual(t2, t2)
3009
3010 # OTOH, it's OK to compare two of these mixing the two ways of being
3011 # naive.
3012 t1 = self.theclass(5, 6, 7)
3013 self.assertEqual(t1, t2)
3014
3015 # Try a bogus uctoffset.
3016 class Bogus(tzinfo):
3017 def utcoffset(self, dt):
3018 return timedelta(minutes=1440) # out of bounds
3019 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3020 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3021 self.assertRaises(ValueError, lambda: t1 == t2)
3022
3023 def test_pickling(self):
3024 # Try one without a tzinfo.
3025 args = 6, 7, 23, 20, 59, 1, 64**2
3026 orig = self.theclass(*args)
3027 for pickler, unpickler, proto in pickle_choices:
3028 green = pickler.dumps(orig, proto)
3029 derived = unpickler.loads(green)
3030 self.assertEqual(orig, derived)
3031
3032 # Try one with a tzinfo.
3033 tinfo = PicklableFixedOffset(-300, 'cookie')
3034 orig = self.theclass(*args, **{'tzinfo': tinfo})
3035 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3036 for pickler, unpickler, proto in pickle_choices:
3037 green = pickler.dumps(orig, proto)
3038 derived = unpickler.loads(green)
3039 self.assertEqual(orig, derived)
3040 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3041 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3042 self.assertEqual(derived.tzname(), 'cookie')
3043
3044 def test_extreme_hashes(self):
3045 # If an attempt is made to hash these via subtracting the offset
3046 # then hashing a datetime object, OverflowError results. The
3047 # Python implementation used to blow up here.
3048 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3049 hash(t)
3050 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3051 tzinfo=FixedOffset(-1439, ""))
3052 hash(t)
3053
3054 # OTOH, an OOB offset should blow up.
3055 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3056 self.assertRaises(ValueError, hash, t)
3057
3058 def test_zones(self):
3059 est = FixedOffset(-300, "EST")
3060 utc = FixedOffset(0, "UTC")
3061 met = FixedOffset(60, "MET")
3062 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3063 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3064 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3065 self.assertEqual(t1.tzinfo, est)
3066 self.assertEqual(t2.tzinfo, utc)
3067 self.assertEqual(t3.tzinfo, met)
3068 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3069 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3070 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3071 self.assertEqual(t1.tzname(), "EST")
3072 self.assertEqual(t2.tzname(), "UTC")
3073 self.assertEqual(t3.tzname(), "MET")
3074 self.assertEqual(hash(t1), hash(t2))
3075 self.assertEqual(hash(t1), hash(t3))
3076 self.assertEqual(hash(t2), hash(t3))
3077 self.assertEqual(t1, t2)
3078 self.assertEqual(t1, t3)
3079 self.assertEqual(t2, t3)
3080 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3081 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3082 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3083 d = 'datetime.datetime(2002, 3, 19, '
3084 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3085 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3086 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3087
3088 def test_combine(self):
3089 met = FixedOffset(60, "MET")
3090 d = date(2002, 3, 4)
3091 tz = time(18, 45, 3, 1234, tzinfo=met)
3092 dt = datetime.combine(d, tz)
3093 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3094 tzinfo=met))
3095
3096 def test_extract(self):
3097 met = FixedOffset(60, "MET")
3098 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3099 self.assertEqual(dt.date(), date(2002, 3, 4))
3100 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3101 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3102
3103 def test_tz_aware_arithmetic(self):
3104 import random
3105
3106 now = self.theclass.now()
3107 tz55 = FixedOffset(-330, "west 5:30")
3108 timeaware = now.time().replace(tzinfo=tz55)
3109 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003110 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003111 self.assertEqual(nowaware.timetz(), timeaware)
3112
3113 # Can't mix aware and non-aware.
3114 self.assertRaises(TypeError, lambda: now - nowaware)
3115 self.assertRaises(TypeError, lambda: nowaware - now)
3116
3117 # And adding datetime's doesn't make sense, aware or not.
3118 self.assertRaises(TypeError, lambda: now + nowaware)
3119 self.assertRaises(TypeError, lambda: nowaware + now)
3120 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3121
3122 # Subtracting should yield 0.
3123 self.assertEqual(now - now, timedelta(0))
3124 self.assertEqual(nowaware - nowaware, timedelta(0))
3125
3126 # Adding a delta should preserve tzinfo.
3127 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3128 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003129 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003130 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003131 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003132 self.assertEqual(nowawareplus, nowawareplus2)
3133
3134 # that - delta should be what we started with, and that - what we
3135 # started with should be delta.
3136 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003137 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003138 self.assertEqual(nowaware, diff)
3139 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3140 self.assertEqual(nowawareplus - nowaware, delta)
3141
3142 # Make up a random timezone.
3143 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3144 # Attach it to nowawareplus.
3145 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003146 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003147 # Make sure the difference takes the timezone adjustments into account.
3148 got = nowaware - nowawareplus
3149 # Expected: (nowaware base - nowaware offset) -
3150 # (nowawareplus base - nowawareplus offset) =
3151 # (nowaware base - nowawareplus base) +
3152 # (nowawareplus offset - nowaware offset) =
3153 # -delta + nowawareplus offset - nowaware offset
3154 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3155 self.assertEqual(got, expected)
3156
3157 # Try max possible difference.
3158 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3159 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3160 tzinfo=FixedOffset(-1439, "max"))
3161 maxdiff = max - min
3162 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3163 timedelta(minutes=2*1439))
3164 # Different tzinfo, but the same offset
3165 tza = timezone(HOUR, 'A')
3166 tzb = timezone(HOUR, 'B')
3167 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3168 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3169
3170 def test_tzinfo_now(self):
3171 meth = self.theclass.now
3172 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3173 base = meth()
3174 # Try with and without naming the keyword.
3175 off42 = FixedOffset(42, "42")
3176 another = meth(off42)
3177 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003178 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003179 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3180 # Bad argument with and w/o naming the keyword.
3181 self.assertRaises(TypeError, meth, 16)
3182 self.assertRaises(TypeError, meth, tzinfo=16)
3183 # Bad keyword name.
3184 self.assertRaises(TypeError, meth, tinfo=off42)
3185 # Too many args.
3186 self.assertRaises(TypeError, meth, off42, off42)
3187
3188 # We don't know which time zone we're in, and don't have a tzinfo
3189 # class to represent it, so seeing whether a tz argument actually
3190 # does a conversion is tricky.
3191 utc = FixedOffset(0, "utc", 0)
3192 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3193 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3194 for dummy in range(3):
3195 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003196 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003197 utcnow = datetime.utcnow().replace(tzinfo=utc)
3198 now2 = utcnow.astimezone(weirdtz)
3199 if abs(now - now2) < timedelta(seconds=30):
3200 break
3201 # Else the code is broken, or more than 30 seconds passed between
3202 # calls; assuming the latter, just try again.
3203 else:
3204 # Three strikes and we're out.
3205 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3206
3207 def test_tzinfo_fromtimestamp(self):
3208 import time
3209 meth = self.theclass.fromtimestamp
3210 ts = time.time()
3211 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3212 base = meth(ts)
3213 # Try with and without naming the keyword.
3214 off42 = FixedOffset(42, "42")
3215 another = meth(ts, off42)
3216 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003217 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003218 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3219 # Bad argument with and w/o naming the keyword.
3220 self.assertRaises(TypeError, meth, ts, 16)
3221 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3222 # Bad keyword name.
3223 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3224 # Too many args.
3225 self.assertRaises(TypeError, meth, ts, off42, off42)
3226 # Too few args.
3227 self.assertRaises(TypeError, meth)
3228
3229 # Try to make sure tz= actually does some conversion.
3230 timestamp = 1000000000
3231 utcdatetime = datetime.utcfromtimestamp(timestamp)
3232 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3233 # But on some flavor of Mac, it's nowhere near that. So we can't have
3234 # any idea here what time that actually is, we can only test that
3235 # relative changes match.
3236 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3237 tz = FixedOffset(utcoffset, "tz", 0)
3238 expected = utcdatetime + utcoffset
3239 got = datetime.fromtimestamp(timestamp, tz)
3240 self.assertEqual(expected, got.replace(tzinfo=None))
3241
3242 def test_tzinfo_utcnow(self):
3243 meth = self.theclass.utcnow
3244 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3245 base = meth()
3246 # Try with and without naming the keyword; for whatever reason,
3247 # utcnow() doesn't accept a tzinfo argument.
3248 off42 = FixedOffset(42, "42")
3249 self.assertRaises(TypeError, meth, off42)
3250 self.assertRaises(TypeError, meth, tzinfo=off42)
3251
3252 def test_tzinfo_utcfromtimestamp(self):
3253 import time
3254 meth = self.theclass.utcfromtimestamp
3255 ts = time.time()
3256 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3257 base = meth(ts)
3258 # Try with and without naming the keyword; for whatever reason,
3259 # utcfromtimestamp() doesn't accept a tzinfo argument.
3260 off42 = FixedOffset(42, "42")
3261 self.assertRaises(TypeError, meth, ts, off42)
3262 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3263
3264 def test_tzinfo_timetuple(self):
3265 # TestDateTime tested most of this. datetime adds a twist to the
3266 # DST flag.
3267 class DST(tzinfo):
3268 def __init__(self, dstvalue):
3269 if isinstance(dstvalue, int):
3270 dstvalue = timedelta(minutes=dstvalue)
3271 self.dstvalue = dstvalue
3272 def dst(self, dt):
3273 return self.dstvalue
3274
3275 cls = self.theclass
3276 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3277 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3278 t = d.timetuple()
3279 self.assertEqual(1, t.tm_year)
3280 self.assertEqual(1, t.tm_mon)
3281 self.assertEqual(1, t.tm_mday)
3282 self.assertEqual(10, t.tm_hour)
3283 self.assertEqual(20, t.tm_min)
3284 self.assertEqual(30, t.tm_sec)
3285 self.assertEqual(0, t.tm_wday)
3286 self.assertEqual(1, t.tm_yday)
3287 self.assertEqual(flag, t.tm_isdst)
3288
3289 # dst() returns wrong type.
3290 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3291
3292 # dst() at the edge.
3293 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3294 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3295
3296 # dst() out of range.
3297 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3298 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3299
3300 def test_utctimetuple(self):
3301 class DST(tzinfo):
3302 def __init__(self, dstvalue=0):
3303 if isinstance(dstvalue, int):
3304 dstvalue = timedelta(minutes=dstvalue)
3305 self.dstvalue = dstvalue
3306 def dst(self, dt):
3307 return self.dstvalue
3308
3309 cls = self.theclass
3310 # This can't work: DST didn't implement utcoffset.
3311 self.assertRaises(NotImplementedError,
3312 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3313
3314 class UOFS(DST):
3315 def __init__(self, uofs, dofs=None):
3316 DST.__init__(self, dofs)
3317 self.uofs = timedelta(minutes=uofs)
3318 def utcoffset(self, dt):
3319 return self.uofs
3320
3321 for dstvalue in -33, 33, 0, None:
3322 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3323 t = d.utctimetuple()
3324 self.assertEqual(d.year, t.tm_year)
3325 self.assertEqual(d.month, t.tm_mon)
3326 self.assertEqual(d.day, t.tm_mday)
3327 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3328 self.assertEqual(13, t.tm_min)
3329 self.assertEqual(d.second, t.tm_sec)
3330 self.assertEqual(d.weekday(), t.tm_wday)
3331 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3332 t.tm_yday)
3333 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3334 # is never in effect for a UTC time.
3335 self.assertEqual(0, t.tm_isdst)
3336
3337 # For naive datetime, utctimetuple == timetuple except for isdst
3338 d = cls(1, 2, 3, 10, 20, 30, 40)
3339 t = d.utctimetuple()
3340 self.assertEqual(t[:-1], d.timetuple()[:-1])
3341 self.assertEqual(0, t.tm_isdst)
3342 # Same if utcoffset is None
3343 class NOFS(DST):
3344 def utcoffset(self, dt):
3345 return None
3346 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3347 t = d.utctimetuple()
3348 self.assertEqual(t[:-1], d.timetuple()[:-1])
3349 self.assertEqual(0, t.tm_isdst)
3350 # Check that bad tzinfo is detected
3351 class BOFS(DST):
3352 def utcoffset(self, dt):
3353 return "EST"
3354 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3355 self.assertRaises(TypeError, d.utctimetuple)
3356
3357 # Check that utctimetuple() is the same as
3358 # astimezone(utc).timetuple()
3359 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3360 for tz in [timezone.min, timezone.utc, timezone.max]:
3361 dtz = d.replace(tzinfo=tz)
3362 self.assertEqual(dtz.utctimetuple()[:-1],
3363 dtz.astimezone(timezone.utc).timetuple()[:-1])
3364 # At the edges, UTC adjustment can produce years out-of-range
3365 # for a datetime object. Ensure that an OverflowError is
3366 # raised.
3367 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3368 # That goes back 1 minute less than a full day.
3369 self.assertRaises(OverflowError, tiny.utctimetuple)
3370
3371 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3372 # That goes forward 1 minute less than a full day.
3373 self.assertRaises(OverflowError, huge.utctimetuple)
3374 # More overflow cases
3375 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3376 self.assertRaises(OverflowError, tiny.utctimetuple)
3377 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3378 self.assertRaises(OverflowError, huge.utctimetuple)
3379
3380 def test_tzinfo_isoformat(self):
3381 zero = FixedOffset(0, "+00:00")
3382 plus = FixedOffset(220, "+03:40")
3383 minus = FixedOffset(-231, "-03:51")
3384 unknown = FixedOffset(None, "")
3385
3386 cls = self.theclass
3387 datestr = '0001-02-03'
3388 for ofs in None, zero, plus, minus, unknown:
3389 for us in 0, 987001:
3390 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3391 timestr = '04:05:59' + (us and '.987001' or '')
3392 ofsstr = ofs is not None and d.tzname() or ''
3393 tailstr = timestr + ofsstr
3394 iso = d.isoformat()
3395 self.assertEqual(iso, datestr + 'T' + tailstr)
3396 self.assertEqual(iso, d.isoformat('T'))
3397 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3398 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3399 self.assertEqual(str(d), datestr + ' ' + tailstr)
3400
3401 def test_replace(self):
3402 cls = self.theclass
3403 z100 = FixedOffset(100, "+100")
3404 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3405 args = [1, 2, 3, 4, 5, 6, 7, z100]
3406 base = cls(*args)
3407 self.assertEqual(base, base.replace())
3408
3409 i = 0
3410 for name, newval in (("year", 2),
3411 ("month", 3),
3412 ("day", 4),
3413 ("hour", 5),
3414 ("minute", 6),
3415 ("second", 7),
3416 ("microsecond", 8),
3417 ("tzinfo", zm200)):
3418 newargs = args[:]
3419 newargs[i] = newval
3420 expected = cls(*newargs)
3421 got = base.replace(**{name: newval})
3422 self.assertEqual(expected, got)
3423 i += 1
3424
3425 # Ensure we can get rid of a tzinfo.
3426 self.assertEqual(base.tzname(), "+100")
3427 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003428 self.assertIsNone(base2.tzinfo)
3429 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003430
3431 # Ensure we can add one.
3432 base3 = base2.replace(tzinfo=z100)
3433 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003434 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003435
3436 # Out of bounds.
3437 base = cls(2000, 2, 29)
3438 self.assertRaises(ValueError, base.replace, year=2001)
3439
3440 def test_more_astimezone(self):
3441 # The inherited test_astimezone covered some trivial and error cases.
3442 fnone = FixedOffset(None, "None")
3443 f44m = FixedOffset(44, "44")
3444 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3445
3446 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003447 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003448 # Replacing with degenerate tzinfo raises an exception.
3449 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003450 # Replacing with same tzinfo makes no change.
3451 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003452 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003453 self.assertEqual(x.date(), dt.date())
3454 self.assertEqual(x.time(), dt.time())
3455
3456 # Replacing with different tzinfo does adjust.
3457 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003458 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003459 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3460 expected = dt - dt.utcoffset() # in effect, convert to UTC
3461 expected += fm5h.utcoffset(dt) # and from there to local time
3462 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3463 self.assertEqual(got.date(), expected.date())
3464 self.assertEqual(got.time(), expected.time())
3465 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003466 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003467 self.assertEqual(got, expected)
3468
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003469 @support.run_with_tz('UTC')
3470 def test_astimezone_default_utc(self):
3471 dt = self.theclass.now(timezone.utc)
3472 self.assertEqual(dt.astimezone(None), dt)
3473 self.assertEqual(dt.astimezone(), dt)
3474
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003475 # Note that offset in TZ variable has the opposite sign to that
3476 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003477 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3478 def test_astimezone_default_eastern(self):
3479 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3480 local = dt.astimezone()
3481 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003482 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003483 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3484 local = dt.astimezone()
3485 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003486 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003487
Alexander Belopolsky1dcf4f92016-03-25 15:42:59 -04003488 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3489 def test_astimezone_default_near_fold(self):
3490 # Issue #26616.
3491 u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3492 t = u.astimezone()
3493 s = t.astimezone()
3494 self.assertEqual(t.tzinfo, s.tzinfo)
3495
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003496 def test_aware_subtract(self):
3497 cls = self.theclass
3498
3499 # Ensure that utcoffset() is ignored when the operands have the
3500 # same tzinfo member.
3501 class OperandDependentOffset(tzinfo):
3502 def utcoffset(self, t):
3503 if t.minute < 10:
3504 # d0 and d1 equal after adjustment
3505 return timedelta(minutes=t.minute)
3506 else:
3507 # d2 off in the weeds
3508 return timedelta(minutes=59)
3509
3510 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3511 d0 = base.replace(minute=3)
3512 d1 = base.replace(minute=9)
3513 d2 = base.replace(minute=11)
3514 for x in d0, d1, d2:
3515 for y in d0, d1, d2:
3516 got = x - y
3517 expected = timedelta(minutes=x.minute - y.minute)
3518 self.assertEqual(got, expected)
3519
3520 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3521 # ignored.
3522 base = cls(8, 9, 10, 11, 12, 13, 14)
3523 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3524 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3525 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3526 for x in d0, d1, d2:
3527 for y in d0, d1, d2:
3528 got = x - y
3529 if (x is d0 or x is d1) and (y is d0 or y is d1):
3530 expected = timedelta(0)
3531 elif x is y is d2:
3532 expected = timedelta(0)
3533 elif x is d2:
3534 expected = timedelta(minutes=(11-59)-0)
3535 else:
3536 assert y is d2
3537 expected = timedelta(minutes=0-(11-59))
3538 self.assertEqual(got, expected)
3539
3540 def test_mixed_compare(self):
3541 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3542 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3543 self.assertEqual(t1, t2)
3544 t2 = t2.replace(tzinfo=None)
3545 self.assertEqual(t1, t2)
3546 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3547 self.assertEqual(t1, t2)
3548 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003549 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003550
3551 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3552 class Varies(tzinfo):
3553 def __init__(self):
3554 self.offset = timedelta(minutes=22)
3555 def utcoffset(self, t):
3556 self.offset += timedelta(minutes=1)
3557 return self.offset
3558
3559 v = Varies()
3560 t1 = t2.replace(tzinfo=v)
3561 t2 = t2.replace(tzinfo=v)
3562 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3563 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3564 self.assertEqual(t1, t2)
3565
3566 # But if they're not identical, it isn't ignored.
3567 t2 = t2.replace(tzinfo=Varies())
3568 self.assertTrue(t1 < t2) # t1's offset counter still going up
3569
3570 def test_subclass_datetimetz(self):
3571
3572 class C(self.theclass):
3573 theAnswer = 42
3574
3575 def __new__(cls, *args, **kws):
3576 temp = kws.copy()
3577 extra = temp.pop('extra')
3578 result = self.theclass.__new__(cls, *args, **temp)
3579 result.extra = extra
3580 return result
3581
3582 def newmeth(self, start):
3583 return start + self.hour + self.year
3584
3585 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3586
3587 dt1 = self.theclass(*args)
3588 dt2 = C(*args, **{'extra': 7})
3589
3590 self.assertEqual(dt2.__class__, C)
3591 self.assertEqual(dt2.theAnswer, 42)
3592 self.assertEqual(dt2.extra, 7)
3593 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3594 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3595
3596# Pain to set up DST-aware tzinfo classes.
3597
3598def first_sunday_on_or_after(dt):
3599 days_to_go = 6 - dt.weekday()
3600 if days_to_go:
3601 dt += timedelta(days_to_go)
3602 return dt
3603
3604ZERO = timedelta(0)
3605MINUTE = timedelta(minutes=1)
3606HOUR = timedelta(hours=1)
3607DAY = timedelta(days=1)
3608# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3609DSTSTART = datetime(1, 4, 1, 2)
3610# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3611# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3612# being standard time on that day, there is no spelling in local time of
3613# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3614DSTEND = datetime(1, 10, 25, 1)
3615
3616class USTimeZone(tzinfo):
3617
3618 def __init__(self, hours, reprname, stdname, dstname):
3619 self.stdoffset = timedelta(hours=hours)
3620 self.reprname = reprname
3621 self.stdname = stdname
3622 self.dstname = dstname
3623
3624 def __repr__(self):
3625 return self.reprname
3626
3627 def tzname(self, dt):
3628 if self.dst(dt):
3629 return self.dstname
3630 else:
3631 return self.stdname
3632
3633 def utcoffset(self, dt):
3634 return self.stdoffset + self.dst(dt)
3635
3636 def dst(self, dt):
3637 if dt is None or dt.tzinfo is None:
3638 # An exception instead may be sensible here, in one or more of
3639 # the cases.
3640 return ZERO
3641 assert dt.tzinfo is self
3642
3643 # Find first Sunday in April.
3644 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3645 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3646
3647 # Find last Sunday in October.
3648 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3649 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3650
3651 # Can't compare naive to aware objects, so strip the timezone from
3652 # dt first.
3653 if start <= dt.replace(tzinfo=None) < end:
3654 return HOUR
3655 else:
3656 return ZERO
3657
3658Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3659Central = USTimeZone(-6, "Central", "CST", "CDT")
3660Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3661Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3662utc_real = FixedOffset(0, "UTC", 0)
3663# For better test coverage, we want another flavor of UTC that's west of
3664# the Eastern and Pacific timezones.
3665utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3666
3667class TestTimezoneConversions(unittest.TestCase):
3668 # The DST switch times for 2002, in std time.
3669 dston = datetime(2002, 4, 7, 2)
3670 dstoff = datetime(2002, 10, 27, 1)
3671
3672 theclass = datetime
3673
3674 # Check a time that's inside DST.
3675 def checkinside(self, dt, tz, utc, dston, dstoff):
3676 self.assertEqual(dt.dst(), HOUR)
3677
3678 # Conversion to our own timezone is always an identity.
3679 self.assertEqual(dt.astimezone(tz), dt)
3680
3681 asutc = dt.astimezone(utc)
3682 there_and_back = asutc.astimezone(tz)
3683
3684 # Conversion to UTC and back isn't always an identity here,
3685 # because there are redundant spellings (in local time) of
3686 # UTC time when DST begins: the clock jumps from 1:59:59
3687 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3688 # make sense then. The classes above treat 2:MM:SS as
3689 # daylight time then (it's "after 2am"), really an alias
3690 # for 1:MM:SS standard time. The latter form is what
3691 # conversion back from UTC produces.
3692 if dt.date() == dston.date() and dt.hour == 2:
3693 # We're in the redundant hour, and coming back from
3694 # UTC gives the 1:MM:SS standard-time spelling.
3695 self.assertEqual(there_and_back + HOUR, dt)
3696 # Although during was considered to be in daylight
3697 # time, there_and_back is not.
3698 self.assertEqual(there_and_back.dst(), ZERO)
3699 # They're the same times in UTC.
3700 self.assertEqual(there_and_back.astimezone(utc),
3701 dt.astimezone(utc))
3702 else:
3703 # We're not in the redundant hour.
3704 self.assertEqual(dt, there_and_back)
3705
3706 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003707 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003708 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3709 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3710 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3711 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3712 # expressed in local time. Nevertheless, we want conversion back
3713 # from UTC to mimic the local clock's "repeat an hour" behavior.
3714 nexthour_utc = asutc + HOUR
3715 nexthour_tz = nexthour_utc.astimezone(tz)
3716 if dt.date() == dstoff.date() and dt.hour == 0:
3717 # We're in the hour before the last DST hour. The last DST hour
3718 # is ineffable. We want the conversion back to repeat 1:MM.
3719 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3720 nexthour_utc += HOUR
3721 nexthour_tz = nexthour_utc.astimezone(tz)
3722 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3723 else:
3724 self.assertEqual(nexthour_tz - dt, HOUR)
3725
3726 # Check a time that's outside DST.
3727 def checkoutside(self, dt, tz, utc):
3728 self.assertEqual(dt.dst(), ZERO)
3729
3730 # Conversion to our own timezone is always an identity.
3731 self.assertEqual(dt.astimezone(tz), dt)
3732
3733 # Converting to UTC and back is an identity too.
3734 asutc = dt.astimezone(utc)
3735 there_and_back = asutc.astimezone(tz)
3736 self.assertEqual(dt, there_and_back)
3737
3738 def convert_between_tz_and_utc(self, tz, utc):
3739 dston = self.dston.replace(tzinfo=tz)
3740 # Because 1:MM on the day DST ends is taken as being standard time,
3741 # there is no spelling in tz for the last hour of daylight time.
3742 # For purposes of the test, the last hour of DST is 0:MM, which is
3743 # taken as being daylight time (and 1:MM is taken as being standard
3744 # time).
3745 dstoff = self.dstoff.replace(tzinfo=tz)
3746 for delta in (timedelta(weeks=13),
3747 DAY,
3748 HOUR,
3749 timedelta(minutes=1),
3750 timedelta(microseconds=1)):
3751
3752 self.checkinside(dston, tz, utc, dston, dstoff)
3753 for during in dston + delta, dstoff - delta:
3754 self.checkinside(during, tz, utc, dston, dstoff)
3755
3756 self.checkoutside(dstoff, tz, utc)
3757 for outside in dston - delta, dstoff + delta:
3758 self.checkoutside(outside, tz, utc)
3759
3760 def test_easy(self):
3761 # Despite the name of this test, the endcases are excruciating.
3762 self.convert_between_tz_and_utc(Eastern, utc_real)
3763 self.convert_between_tz_and_utc(Pacific, utc_real)
3764 self.convert_between_tz_and_utc(Eastern, utc_fake)
3765 self.convert_between_tz_and_utc(Pacific, utc_fake)
3766 # The next is really dancing near the edge. It works because
3767 # Pacific and Eastern are far enough apart that their "problem
3768 # hours" don't overlap.
3769 self.convert_between_tz_and_utc(Eastern, Pacific)
3770 self.convert_between_tz_and_utc(Pacific, Eastern)
3771 # OTOH, these fail! Don't enable them. The difficulty is that
3772 # the edge case tests assume that every hour is representable in
3773 # the "utc" class. This is always true for a fixed-offset tzinfo
3774 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3775 # For these adjacent DST-aware time zones, the range of time offsets
3776 # tested ends up creating hours in the one that aren't representable
3777 # in the other. For the same reason, we would see failures in the
3778 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3779 # offset deltas in convert_between_tz_and_utc().
3780 #
3781 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3782 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3783
3784 def test_tricky(self):
3785 # 22:00 on day before daylight starts.
3786 fourback = self.dston - timedelta(hours=4)
3787 ninewest = FixedOffset(-9*60, "-0900", 0)
3788 fourback = fourback.replace(tzinfo=ninewest)
3789 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3790 # 2", we should get the 3 spelling.
3791 # If we plug 22:00 the day before into Eastern, it "looks like std
3792 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3793 # to 22:00 lands on 2:00, which makes no sense in local time (the
3794 # local clock jumps from 1 to 3). The point here is to make sure we
3795 # get the 3 spelling.
3796 expected = self.dston.replace(hour=3)
3797 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3798 self.assertEqual(expected, got)
3799
3800 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3801 # case we want the 1:00 spelling.
3802 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3803 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3804 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3805 # spelling.
3806 expected = self.dston.replace(hour=1)
3807 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3808 self.assertEqual(expected, got)
3809
3810 # Now on the day DST ends, we want "repeat an hour" behavior.
3811 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3812 # EST 23:MM 0:MM 1:MM 2:MM
3813 # EDT 0:MM 1:MM 2:MM 3:MM
3814 # wall 0:MM 1:MM 1:MM 2:MM against these
3815 for utc in utc_real, utc_fake:
3816 for tz in Eastern, Pacific:
3817 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3818 # Convert that to UTC.
3819 first_std_hour -= tz.utcoffset(None)
3820 # Adjust for possibly fake UTC.
3821 asutc = first_std_hour + utc.utcoffset(None)
3822 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3823 # tz=Eastern.
3824 asutcbase = asutc.replace(tzinfo=utc)
3825 for tzhour in (0, 1, 1, 2):
3826 expectedbase = self.dstoff.replace(hour=tzhour)
3827 for minute in 0, 30, 59:
3828 expected = expectedbase.replace(minute=minute)
3829 asutc = asutcbase.replace(minute=minute)
3830 astz = asutc.astimezone(tz)
3831 self.assertEqual(astz.replace(tzinfo=None), expected)
3832 asutcbase += HOUR
3833
3834
3835 def test_bogus_dst(self):
3836 class ok(tzinfo):
3837 def utcoffset(self, dt): return HOUR
3838 def dst(self, dt): return HOUR
3839
3840 now = self.theclass.now().replace(tzinfo=utc_real)
3841 # Doesn't blow up.
3842 now.astimezone(ok())
3843
3844 # Does blow up.
3845 class notok(ok):
3846 def dst(self, dt): return None
3847 self.assertRaises(ValueError, now.astimezone, notok())
3848
3849 # Sometimes blow up. In the following, tzinfo.dst()
3850 # implementation may return None or not None depending on
3851 # whether DST is assumed to be in effect. In this situation,
3852 # a ValueError should be raised by astimezone().
3853 class tricky_notok(ok):
3854 def dst(self, dt):
3855 if dt.year == 2000:
3856 return None
3857 else:
3858 return 10*HOUR
3859 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3860 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3861
3862 def test_fromutc(self):
3863 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3864 now = datetime.utcnow().replace(tzinfo=utc_real)
3865 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3866 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3867 enow = Eastern.fromutc(now) # doesn't blow up
3868 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3869 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3870 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3871
3872 # Always converts UTC to standard time.
3873 class FauxUSTimeZone(USTimeZone):
3874 def fromutc(self, dt):
3875 return dt + self.stdoffset
3876 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3877
3878 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3879 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3880 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3881
3882 # Check around DST start.
3883 start = self.dston.replace(hour=4, tzinfo=Eastern)
3884 fstart = start.replace(tzinfo=FEastern)
3885 for wall in 23, 0, 1, 3, 4, 5:
3886 expected = start.replace(hour=wall)
3887 if wall == 23:
3888 expected -= timedelta(days=1)
3889 got = Eastern.fromutc(start)
3890 self.assertEqual(expected, got)
3891
3892 expected = fstart + FEastern.stdoffset
3893 got = FEastern.fromutc(fstart)
3894 self.assertEqual(expected, got)
3895
3896 # Ensure astimezone() calls fromutc() too.
3897 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3898 self.assertEqual(expected, got)
3899
3900 start += HOUR
3901 fstart += HOUR
3902
3903 # Check around DST end.
3904 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3905 fstart = start.replace(tzinfo=FEastern)
3906 for wall in 0, 1, 1, 2, 3, 4:
3907 expected = start.replace(hour=wall)
3908 got = Eastern.fromutc(start)
3909 self.assertEqual(expected, got)
3910
3911 expected = fstart + FEastern.stdoffset
3912 got = FEastern.fromutc(fstart)
3913 self.assertEqual(expected, got)
3914
3915 # Ensure astimezone() calls fromutc() too.
3916 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3917 self.assertEqual(expected, got)
3918
3919 start += HOUR
3920 fstart += HOUR
3921
3922
3923#############################################################################
3924# oddballs
3925
3926class Oddballs(unittest.TestCase):
3927
3928 def test_bug_1028306(self):
3929 # Trying to compare a date to a datetime should act like a mixed-
3930 # type comparison, despite that datetime is a subclass of date.
3931 as_date = date.today()
3932 as_datetime = datetime.combine(as_date, time())
3933 self.assertTrue(as_date != as_datetime)
3934 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003935 self.assertFalse(as_date == as_datetime)
3936 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003937 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3938 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3939 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3940 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3941 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3942 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3943 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3944 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3945
3946 # Neverthelss, comparison should work with the base-class (date)
3947 # projection if use of a date method is forced.
3948 self.assertEqual(as_date.__eq__(as_datetime), True)
3949 different_day = (as_date.day + 1) % 20 + 1
3950 as_different = as_datetime.replace(day= different_day)
3951 self.assertEqual(as_date.__eq__(as_different), False)
3952
3953 # And date should compare with other subclasses of date. If a
3954 # subclass wants to stop this, it's up to the subclass to do so.
3955 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3956 self.assertEqual(as_date, date_sc)
3957 self.assertEqual(date_sc, as_date)
3958
3959 # Ditto for datetimes.
3960 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3961 as_date.day, 0, 0, 0)
3962 self.assertEqual(as_datetime, datetime_sc)
3963 self.assertEqual(datetime_sc, as_datetime)
3964
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003965 def test_extra_attributes(self):
3966 for x in [date.today(),
3967 time(),
3968 datetime.utcnow(),
3969 timedelta(),
3970 tzinfo(),
3971 timezone(timedelta())]:
3972 with self.assertRaises(AttributeError):
3973 x.abc = 1
3974
3975 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003976 class Number:
3977 def __init__(self, value):
3978 self.value = value
3979 def __int__(self):
3980 return self.value
3981
3982 for xx in [decimal.Decimal(10),
3983 decimal.Decimal('10.9'),
3984 Number(10)]:
3985 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
3986 datetime(xx, xx, xx, xx, xx, xx, xx))
3987
3988 with self.assertRaisesRegex(TypeError, '^an integer is required '
3989 '\(got type str\)$'):
3990 datetime(10, 10, '10')
3991
3992 f10 = Number(10.9)
3993 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
3994 '\(type float\)$'):
3995 datetime(10, 10, f10)
3996
3997 class Float(float):
3998 pass
3999 s10 = Float(10.9)
4000 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4001 'got float$'):
4002 datetime(10, 10, s10)
4003
4004 with self.assertRaises(TypeError):
4005 datetime(10., 10, 10)
4006 with self.assertRaises(TypeError):
4007 datetime(10, 10., 10)
4008 with self.assertRaises(TypeError):
4009 datetime(10, 10, 10.)
4010 with self.assertRaises(TypeError):
4011 datetime(10, 10, 10, 10.)
4012 with self.assertRaises(TypeError):
4013 datetime(10, 10, 10, 10, 10.)
4014 with self.assertRaises(TypeError):
4015 datetime(10, 10, 10, 10, 10, 10.)
4016 with self.assertRaises(TypeError):
4017 datetime(10, 10, 10, 10, 10, 10, 10.)
4018
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004019#############################################################################
4020# Local Time Disambiguation
4021
4022# An experimental reimplementation of fromutc that respects the "fold" flag.
4023
4024class tzinfo2(tzinfo):
4025
4026 def fromutc(self, dt):
4027 "datetime in UTC -> datetime in local time."
4028
4029 if not isinstance(dt, datetime):
4030 raise TypeError("fromutc() requires a datetime argument")
4031 if dt.tzinfo is not self:
4032 raise ValueError("dt.tzinfo is not self")
4033 # Returned value satisfies
4034 # dt + ldt.utcoffset() = ldt
4035 off0 = dt.replace(fold=0).utcoffset()
4036 off1 = dt.replace(fold=1).utcoffset()
4037 if off0 is None or off1 is None or dt.dst() is None:
4038 raise ValueError
4039 if off0 == off1:
4040 ldt = dt + off0
4041 off1 = ldt.utcoffset()
4042 if off0 == off1:
4043 return ldt
4044 # Now, we discovered both possible offsets, so
4045 # we can just try four possible solutions:
4046 for off in [off0, off1]:
4047 ldt = dt + off
4048 if ldt.utcoffset() == off:
4049 return ldt
4050 ldt = ldt.replace(fold=1)
4051 if ldt.utcoffset() == off:
4052 return ldt
4053
4054 raise ValueError("No suitable local time found")
4055
4056# Reimplementing simplified US timezones to respect the "fold" flag:
4057
4058class USTimeZone2(tzinfo2):
4059
4060 def __init__(self, hours, reprname, stdname, dstname):
4061 self.stdoffset = timedelta(hours=hours)
4062 self.reprname = reprname
4063 self.stdname = stdname
4064 self.dstname = dstname
4065
4066 def __repr__(self):
4067 return self.reprname
4068
4069 def tzname(self, dt):
4070 if self.dst(dt):
4071 return self.dstname
4072 else:
4073 return self.stdname
4074
4075 def utcoffset(self, dt):
4076 return self.stdoffset + self.dst(dt)
4077
4078 def dst(self, dt):
4079 if dt is None or dt.tzinfo is None:
4080 # An exception instead may be sensible here, in one or more of
4081 # the cases.
4082 return ZERO
4083 assert dt.tzinfo is self
4084
4085 # Find first Sunday in April.
4086 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4087 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4088
4089 # Find last Sunday in October.
4090 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4091 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4092
4093 # Can't compare naive to aware objects, so strip the timezone from
4094 # dt first.
4095 dt = dt.replace(tzinfo=None)
4096 if start + HOUR <= dt < end:
4097 # DST is in effect.
4098 return HOUR
4099 elif end <= dt < end + HOUR:
4100 # Fold (an ambiguous hour): use dt.fold to disambiguate.
4101 return ZERO if dt.fold else HOUR
4102 elif start <= dt < start + HOUR:
4103 # Gap (a non-existent hour): reverse the fold rule.
4104 return HOUR if dt.fold else ZERO
4105 else:
4106 # DST is off.
4107 return ZERO
4108
4109Eastern2 = USTimeZone2(-5, "Eastern2", "EST", "EDT")
4110Central2 = USTimeZone2(-6, "Central2", "CST", "CDT")
4111Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4112Pacific2 = USTimeZone2(-8, "Pacific2", "PST", "PDT")
4113
4114# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4115# 1941 transition from Olson's tzdist:
4116#
4117# Zone NAME GMTOFF RULES FORMAT [UNTIL]
4118# ZoneEurope/Vilnius 1:00 - CET 1940 Aug 3
4119# 3:00 - MSK 1941 Jun 24
4120# 1:00 C-Eur CE%sT 1944 Aug
4121#
4122# $ zdump -v Europe/Vilnius | grep 1941
4123# Europe/Vilnius Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4124# Europe/Vilnius Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4125
4126class Europe_Vilnius_1941(tzinfo):
4127 def _utc_fold(self):
4128 return [datetime(1941, 6, 23, 21, tzinfo=self), # Mon Jun 23 21:00:00 1941 UTC
4129 datetime(1941, 6, 23, 22, tzinfo=self)] # Mon Jun 23 22:00:00 1941 UTC
4130
4131 def _loc_fold(self):
4132 return [datetime(1941, 6, 23, 23, tzinfo=self), # Mon Jun 23 23:00:00 1941 MSK / CEST
4133 datetime(1941, 6, 24, 0, tzinfo=self)] # Mon Jun 24 00:00:00 1941 CEST
4134
4135 def utcoffset(self, dt):
4136 fold_start, fold_stop = self._loc_fold()
4137 if dt < fold_start:
4138 return 3 * HOUR
4139 if dt < fold_stop:
4140 return (2 if dt.fold else 3) * HOUR
4141 # if dt >= fold_stop
4142 return 2 * HOUR
4143
4144 def dst(self, dt):
4145 fold_start, fold_stop = self._loc_fold()
4146 if dt < fold_start:
4147 return 0 * HOUR
4148 if dt < fold_stop:
4149 return (1 if dt.fold else 0) * HOUR
4150 # if dt >= fold_stop
4151 return 1 * HOUR
4152
4153 def tzname(self, dt):
4154 fold_start, fold_stop = self._loc_fold()
4155 if dt < fold_start:
4156 return 'MSK'
4157 if dt < fold_stop:
4158 return ('MSK', 'CEST')[dt.fold]
4159 # if dt >= fold_stop
4160 return 'CEST'
4161
4162 def fromutc(self, dt):
4163 assert dt.fold == 0
4164 assert dt.tzinfo is self
4165 if dt.year != 1941:
4166 raise NotImplementedError
4167 fold_start, fold_stop = self._utc_fold()
4168 if dt < fold_start:
4169 return dt + 3 * HOUR
4170 if dt < fold_stop:
4171 return (dt + 2 * HOUR).replace(fold=1)
4172 # if dt >= fold_stop
4173 return dt + 2 * HOUR
4174
4175
4176class TestLocalTimeDisambiguation(unittest.TestCase):
4177
4178 def test_vilnius_1941_fromutc(self):
4179 Vilnius = Europe_Vilnius_1941()
4180
4181 gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4182 ldt = gdt.astimezone(Vilnius)
4183 self.assertEqual(ldt.strftime("%c %Z%z"),
4184 'Mon Jun 23 23:59:59 1941 MSK+0300')
4185 self.assertEqual(ldt.fold, 0)
4186 self.assertFalse(ldt.dst())
4187
4188 gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4189 ldt = gdt.astimezone(Vilnius)
4190 self.assertEqual(ldt.strftime("%c %Z%z"),
4191 'Mon Jun 23 23:00:00 1941 CEST+0200')
4192 self.assertEqual(ldt.fold, 1)
4193 self.assertTrue(ldt.dst())
4194
4195 gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4196 ldt = gdt.astimezone(Vilnius)
4197 self.assertEqual(ldt.strftime("%c %Z%z"),
4198 'Tue Jun 24 00:00:00 1941 CEST+0200')
4199 self.assertEqual(ldt.fold, 0)
4200 self.assertTrue(ldt.dst())
4201
4202 def test_vilnius_1941_toutc(self):
4203 Vilnius = Europe_Vilnius_1941()
4204
4205 ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4206 gdt = ldt.astimezone(timezone.utc)
4207 self.assertEqual(gdt.strftime("%c %Z"),
4208 'Mon Jun 23 19:59:59 1941 UTC')
4209
4210 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4211 gdt = ldt.astimezone(timezone.utc)
4212 self.assertEqual(gdt.strftime("%c %Z"),
4213 'Mon Jun 23 20:59:59 1941 UTC')
4214
4215 ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4216 gdt = ldt.astimezone(timezone.utc)
4217 self.assertEqual(gdt.strftime("%c %Z"),
4218 'Mon Jun 23 21:59:59 1941 UTC')
4219
4220 ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4221 gdt = ldt.astimezone(timezone.utc)
4222 self.assertEqual(gdt.strftime("%c %Z"),
4223 'Mon Jun 23 22:00:00 1941 UTC')
4224
4225
4226 def test_constructors(self):
4227 t = time(0, fold=1)
4228 dt = datetime(1, 1, 1, fold=1)
4229 self.assertEqual(t.fold, 1)
4230 self.assertEqual(dt.fold, 1)
4231 with self.assertRaises(TypeError):
4232 time(0, 0, 0, 0, None, 0)
4233
4234 def test_member(self):
4235 dt = datetime(1, 1, 1, fold=1)
4236 t = dt.time()
4237 self.assertEqual(t.fold, 1)
4238 t = dt.timetz()
4239 self.assertEqual(t.fold, 1)
4240
4241 def test_replace(self):
4242 t = time(0)
4243 dt = datetime(1, 1, 1)
4244 self.assertEqual(t.replace(fold=1).fold, 1)
4245 self.assertEqual(dt.replace(fold=1).fold, 1)
4246 self.assertEqual(t.replace(fold=0).fold, 0)
4247 self.assertEqual(dt.replace(fold=0).fold, 0)
4248 # Check that replacement of other fields does not change "fold".
4249 t = t.replace(fold=1, tzinfo=Eastern)
4250 dt = dt.replace(fold=1, tzinfo=Eastern)
4251 self.assertEqual(t.replace(tzinfo=None).fold, 1)
4252 self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4253 # Check that fold is a keyword-only argument
4254 with self.assertRaises(TypeError):
4255 t.replace(1, 1, 1, None, 1)
4256 with self.assertRaises(TypeError):
4257 dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
Alexander Belopolskycd280132016-07-24 14:41:08 -04004258
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004259 def test_comparison(self):
4260 t = time(0)
4261 dt = datetime(1, 1, 1)
4262 self.assertEqual(t, t.replace(fold=1))
4263 self.assertEqual(dt, dt.replace(fold=1))
4264
4265 def test_hash(self):
4266 t = time(0)
4267 dt = datetime(1, 1, 1)
4268 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4269 self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4270
4271 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4272 def test_fromtimestamp(self):
4273 s = 1414906200
4274 dt0 = datetime.fromtimestamp(s)
4275 dt1 = datetime.fromtimestamp(s + 3600)
4276 self.assertEqual(dt0.fold, 0)
4277 self.assertEqual(dt1.fold, 1)
4278
4279 @support.run_with_tz('Australia/Lord_Howe')
4280 def test_fromtimestamp_lord_howe(self):
4281 tm = _time.localtime(1.4e9)
4282 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4283 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4284 # $ TZ=Australia/Lord_Howe date -r 1428158700
4285 # Sun Apr 5 01:45:00 LHDT 2015
4286 # $ TZ=Australia/Lord_Howe date -r 1428160500
4287 # Sun Apr 5 01:45:00 LHST 2015
4288 s = 1428158700
4289 t0 = datetime.fromtimestamp(s)
4290 t1 = datetime.fromtimestamp(s + 1800)
4291 self.assertEqual(t0, t1)
4292 self.assertEqual(t0.fold, 0)
4293 self.assertEqual(t1.fold, 1)
4294
4295
4296 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4297 def test_timestamp(self):
4298 dt0 = datetime(2014, 11, 2, 1, 30)
4299 dt1 = dt0.replace(fold=1)
4300 self.assertEqual(dt0.timestamp() + 3600,
4301 dt1.timestamp())
4302
4303 @support.run_with_tz('Australia/Lord_Howe')
4304 def test_timestamp_lord_howe(self):
4305 tm = _time.localtime(1.4e9)
4306 if _time.strftime('%Z%z', tm) != 'LHST+1030':
4307 self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4308 t = datetime(2015, 4, 5, 1, 45)
4309 s0 = t.replace(fold=0).timestamp()
4310 s1 = t.replace(fold=1).timestamp()
4311 self.assertEqual(s0 + 1800, s1)
4312
4313
4314 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4315 def test_astimezone(self):
4316 dt0 = datetime(2014, 11, 2, 1, 30)
4317 dt1 = dt0.replace(fold=1)
4318 # Convert both naive instances to aware.
4319 adt0 = dt0.astimezone()
4320 adt1 = dt1.astimezone()
4321 # Check that the first instance in DST zone and the second in STD
4322 self.assertEqual(adt0.tzname(), 'EDT')
4323 self.assertEqual(adt1.tzname(), 'EST')
4324 self.assertEqual(adt0 + HOUR, adt1)
4325 # Aware instances with fixed offset tzinfo's always have fold=0
4326 self.assertEqual(adt0.fold, 0)
4327 self.assertEqual(adt1.fold, 0)
4328
4329
4330 def test_pickle_fold(self):
4331 t = time(fold=1)
4332 dt = datetime(1, 1, 1, fold=1)
4333 for pickler, unpickler, proto in pickle_choices:
4334 for x in [t, dt]:
4335 s = pickler.dumps(x, proto)
4336 y = unpickler.loads(s)
4337 self.assertEqual(x, y)
4338 self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4339
4340 def test_repr(self):
4341 t = time(fold=1)
4342 dt = datetime(1, 1, 1, fold=1)
4343 self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4344 self.assertEqual(repr(dt),
4345 'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4346
4347 def test_dst(self):
4348 # Let's first establish that things work in regular times.
4349 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4350 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4351 self.assertEqual(dt_summer.dst(), HOUR)
4352 self.assertEqual(dt_winter.dst(), ZERO)
4353 # The disambiguation flag is ignored
4354 self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4355 self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4356
4357 # Pick local time in the fold.
4358 for minute in [0, 30, 59]:
4359 dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4360 # With fold=0 (the default) it is in DST.
4361 self.assertEqual(dt.dst(), HOUR)
4362 # With fold=1 it is in STD.
4363 self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4364
4365 # Pick local time in the gap.
4366 for minute in [0, 30, 59]:
4367 dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4368 # With fold=0 (the default) it is in STD.
4369 self.assertEqual(dt.dst(), ZERO)
4370 # With fold=1 it is in DST.
4371 self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4372
4373
4374 def test_utcoffset(self):
4375 # Let's first establish that things work in regular times.
4376 dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4377 dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4378 self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4379 self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4380 # The disambiguation flag is ignored
4381 self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4382 self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4383
4384 def test_fromutc(self):
4385 # Let's first establish that things work in regular times.
4386 u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4387 u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4388 t_summer = Eastern2.fromutc(u_summer)
4389 t_winter = Eastern2.fromutc(u_winter)
4390 self.assertEqual(t_summer, u_summer - 4 * HOUR)
4391 self.assertEqual(t_winter, u_winter - 5 * HOUR)
4392 self.assertEqual(t_summer.fold, 0)
4393 self.assertEqual(t_winter.fold, 0)
4394
4395 # What happens in the fall-back fold?
4396 u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4397 t0 = Eastern2.fromutc(u)
4398 u += HOUR
4399 t1 = Eastern2.fromutc(u)
4400 self.assertEqual(t0, t1)
4401 self.assertEqual(t0.fold, 0)
4402 self.assertEqual(t1.fold, 1)
4403 # The tricky part is when u is in the local fold:
4404 u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4405 t = Eastern2.fromutc(u)
4406 self.assertEqual((t.day, t.hour), (26, 21))
4407 # .. or gets into the local fold after a standard time adjustment
4408 u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4409 t = Eastern2.fromutc(u)
4410 self.assertEqual((t.day, t.hour), (27, 1))
4411
4412 # What happens in the spring-forward gap?
4413 u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4414 t = Eastern2.fromutc(u)
4415 self.assertEqual((t.day, t.hour), (6, 21))
4416
4417 def test_mixed_compare_regular(self):
4418 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4419 self.assertEqual(t, t.astimezone(timezone.utc))
4420 t = datetime(2000, 6, 1, tzinfo=Eastern2)
4421 self.assertEqual(t, t.astimezone(timezone.utc))
4422
4423 def test_mixed_compare_fold(self):
4424 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4425 t_fold_utc = t_fold.astimezone(timezone.utc)
4426 self.assertNotEqual(t_fold, t_fold_utc)
4427
4428 def test_mixed_compare_gap(self):
4429 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4430 t_gap_utc = t_gap.astimezone(timezone.utc)
4431 self.assertNotEqual(t_gap, t_gap_utc)
4432
4433 def test_hash_aware(self):
4434 t = datetime(2000, 1, 1, tzinfo=Eastern2)
4435 self.assertEqual(hash(t), hash(t.replace(fold=1)))
4436 t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4437 t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4438 self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4439 self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4440
4441SEC = timedelta(0, 1)
4442
4443def pairs(iterable):
4444 a, b = itertools.tee(iterable)
4445 next(b, None)
4446 return zip(a, b)
4447
4448class ZoneInfo(tzinfo):
4449 zoneroot = '/usr/share/zoneinfo'
4450 def __init__(self, ut, ti):
4451 """
4452
4453 :param ut: array
4454 Array of transition point timestamps
4455 :param ti: list
4456 A list of (offset, isdst, abbr) tuples
4457 :return: None
4458 """
4459 self.ut = ut
4460 self.ti = ti
4461 self.lt = self.invert(ut, ti)
4462
4463 @staticmethod
4464 def invert(ut, ti):
4465 lt = (ut.__copy__(), ut.__copy__())
4466 if ut:
4467 offset = ti[0][0] // SEC
4468 lt[0][0] = max(-2**31, lt[0][0] + offset)
4469 lt[1][0] = max(-2**31, lt[1][0] + offset)
4470 for i in range(1, len(ut)):
4471 lt[0][i] += ti[i-1][0] // SEC
4472 lt[1][i] += ti[i][0] // SEC
4473 return lt
4474
4475 @classmethod
4476 def fromfile(cls, fileobj):
4477 if fileobj.read(4).decode() != "TZif":
4478 raise ValueError("not a zoneinfo file")
4479 fileobj.seek(32)
4480 counts = array('i')
4481 counts.fromfile(fileobj, 3)
4482 if sys.byteorder != 'big':
4483 counts.byteswap()
4484
4485 ut = array('i')
4486 ut.fromfile(fileobj, counts[0])
4487 if sys.byteorder != 'big':
4488 ut.byteswap()
4489
4490 type_indices = array('B')
4491 type_indices.fromfile(fileobj, counts[0])
4492
4493 ttis = []
4494 for i in range(counts[1]):
4495 ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4496
4497 abbrs = fileobj.read(counts[2])
4498
4499 # Convert ttis
4500 for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4501 abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4502 ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4503
4504 ti = [None] * len(ut)
4505 for i, idx in enumerate(type_indices):
4506 ti[i] = ttis[idx]
4507
4508 self = cls(ut, ti)
4509
4510 return self
4511
4512 @classmethod
4513 def fromname(cls, name):
4514 path = os.path.join(cls.zoneroot, name)
4515 with open(path, 'rb') as f:
4516 return cls.fromfile(f)
4517
4518 EPOCHORDINAL = date(1970, 1, 1).toordinal()
4519
4520 def fromutc(self, dt):
4521 """datetime in UTC -> datetime in local time."""
4522
4523 if not isinstance(dt, datetime):
4524 raise TypeError("fromutc() requires a datetime argument")
4525 if dt.tzinfo is not self:
4526 raise ValueError("dt.tzinfo is not self")
4527
4528 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4529 + dt.hour * 3600
4530 + dt.minute * 60
4531 + dt.second)
4532
4533 if timestamp < self.ut[1]:
4534 tti = self.ti[0]
4535 fold = 0
4536 else:
4537 idx = bisect.bisect_right(self.ut, timestamp)
4538 assert self.ut[idx-1] <= timestamp
4539 assert idx == len(self.ut) or timestamp < self.ut[idx]
4540 tti_prev, tti = self.ti[idx-2:idx]
4541 # Detect fold
4542 shift = tti_prev[0] - tti[0]
4543 fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4544 dt += tti[0]
4545 if fold:
4546 return dt.replace(fold=1)
4547 else:
4548 return dt
4549
4550 def _find_ti(self, dt, i):
4551 timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4552 + dt.hour * 3600
4553 + dt.minute * 60
4554 + dt.second)
4555 lt = self.lt[dt.fold]
4556 idx = bisect.bisect_right(lt, timestamp)
4557
4558 return self.ti[max(0, idx - 1)][i]
4559
4560 def utcoffset(self, dt):
4561 return self._find_ti(dt, 0)
4562
4563 def dst(self, dt):
4564 isdst = self._find_ti(dt, 1)
4565 # XXX: We cannot accurately determine the "save" value,
4566 # so let's return 1h whenever DST is in effect. Since
4567 # we don't use dst() in fromutc(), it is unlikely that
4568 # it will be needed for anything more than bool(dst()).
4569 return ZERO if isdst else HOUR
4570
4571 def tzname(self, dt):
4572 return self._find_ti(dt, 2)
4573
4574 @classmethod
4575 def zonenames(cls, zonedir=None):
4576 if zonedir is None:
4577 zonedir = cls.zoneroot
4578 for root, _, files in os.walk(zonedir):
4579 for f in files:
4580 p = os.path.join(root, f)
4581 with open(p, 'rb') as o:
4582 magic = o.read(4)
4583 if magic == b'TZif':
4584 yield p[len(zonedir) + 1:]
4585
4586 @classmethod
4587 def stats(cls, start_year=1):
4588 count = gap_count = fold_count = zeros_count = 0
4589 min_gap = min_fold = timedelta.max
4590 max_gap = max_fold = ZERO
4591 min_gap_datetime = max_gap_datetime = datetime.min
4592 min_gap_zone = max_gap_zone = None
4593 min_fold_datetime = max_fold_datetime = datetime.min
4594 min_fold_zone = max_fold_zone = None
4595 stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4596 for zonename in cls.zonenames():
4597 count += 1
4598 tz = cls.fromname(zonename)
4599 for dt, shift in tz.transitions():
4600 if dt < stats_since:
4601 continue
4602 if shift > ZERO:
4603 gap_count += 1
4604 if (shift, dt) > (max_gap, max_gap_datetime):
4605 max_gap = shift
4606 max_gap_zone = zonename
4607 max_gap_datetime = dt
4608 if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4609 min_gap = shift
4610 min_gap_zone = zonename
4611 min_gap_datetime = dt
4612 elif shift < ZERO:
4613 fold_count += 1
4614 shift = -shift
4615 if (shift, dt) > (max_fold, max_fold_datetime):
4616 max_fold = shift
4617 max_fold_zone = zonename
4618 max_fold_datetime = dt
4619 if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4620 min_fold = shift
4621 min_fold_zone = zonename
4622 min_fold_datetime = dt
4623 else:
4624 zeros_count += 1
4625 trans_counts = (gap_count, fold_count, zeros_count)
4626 print("Number of zones: %5d" % count)
4627 print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4628 ((sum(trans_counts),) + trans_counts))
4629 print("Min gap: %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4630 print("Max gap: %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4631 print("Min fold: %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4632 print("Max fold: %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4633
4634
4635 def transitions(self):
4636 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4637 shift = ti[0] - prev_ti[0]
4638 yield datetime.utcfromtimestamp(t), shift
4639
4640 def nondst_folds(self):
4641 """Find all folds with the same value of isdst on both sides of the transition."""
4642 for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4643 shift = ti[0] - prev_ti[0]
4644 if shift < ZERO and ti[1] == prev_ti[1]:
4645 yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4646
4647 @classmethod
4648 def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4649 count = 0
4650 for zonename in cls.zonenames():
4651 tz = cls.fromname(zonename)
4652 for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4653 if dt.year < start_year or same_abbr and prev_abbr != abbr:
4654 continue
4655 count += 1
4656 print("%3d) %-30s %s %10s %5s -> %s" %
4657 (count, zonename, dt, shift, prev_abbr, abbr))
4658
4659 def folds(self):
4660 for t, shift in self.transitions():
4661 if shift < ZERO:
4662 yield t, -shift
4663
4664 def gaps(self):
4665 for t, shift in self.transitions():
4666 if shift > ZERO:
4667 yield t, shift
4668
4669 def zeros(self):
4670 for t, shift in self.transitions():
4671 if not shift:
4672 yield t
4673
4674
4675class ZoneInfoTest(unittest.TestCase):
4676 zonename = 'America/New_York'
4677
4678 def setUp(self):
Alexander Belopolsky611adf22016-07-26 12:23:16 -04004679 self.sizeof_time_t = sysconfig.get_config_var('SIZEOF_TIME_T')
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004680 if sys.platform == "win32":
4681 self.skipTest("Skipping zoneinfo tests on Windows")
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004682 try:
4683 self.tz = ZoneInfo.fromname(self.zonename)
4684 except FileNotFoundError as err:
4685 self.skipTest("Skipping %s: %s" % (self.zonename, err))
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004686
4687 def assertEquivDatetimes(self, a, b):
4688 self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4689 (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4690
4691 def test_folds(self):
4692 tz = self.tz
4693 for dt, shift in tz.folds():
4694 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4695 udt = dt + x
4696 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4697 self.assertEqual(ldt.fold, 1)
4698 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4699 self.assertEquivDatetimes(adt, ldt)
4700 utcoffset = ldt.utcoffset()
4701 self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4702 # Round trip
4703 self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4704 udt.replace(tzinfo=timezone.utc))
4705
4706
4707 for x in [-timedelta.resolution, shift]:
4708 udt = dt + x
4709 udt = udt.replace(tzinfo=tz)
4710 ldt = tz.fromutc(udt)
4711 self.assertEqual(ldt.fold, 0)
4712
4713 def test_gaps(self):
4714 tz = self.tz
4715 for dt, shift in tz.gaps():
4716 for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4717 udt = dt + x
4718 udt = udt.replace(tzinfo=tz)
4719 ldt = tz.fromutc(udt)
4720 self.assertEqual(ldt.fold, 0)
4721 adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4722 self.assertEquivDatetimes(adt, ldt)
4723 utcoffset = ldt.utcoffset()
4724 self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4725 # Create a local time inside the gap
4726 ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4727 self.assertLess(ldt.replace(fold=1).utcoffset(),
4728 ldt.replace(fold=0).utcoffset(),
4729 "At %s." % ldt)
4730
4731 for x in [-timedelta.resolution, shift]:
4732 udt = dt + x
4733 ldt = tz.fromutc(udt.replace(tzinfo=tz))
4734 self.assertEqual(ldt.fold, 0)
4735
4736 def test_system_transitions(self):
4737 if ('Riyadh8' in self.zonename or
4738 # From tzdata NEWS file:
4739 # The files solar87, solar88, and solar89 are no longer distributed.
4740 # They were a negative experiment - that is, a demonstration that
4741 # tz data can represent solar time only with some difficulty and error.
4742 # Their presence in the distribution caused confusion, as Riyadh
4743 # civil time was generally not solar time in those years.
4744 self.zonename.startswith('right/')):
4745 self.skipTest("Skipping %s" % self.zonename)
Alexander Belopolsky95f7b9f2016-07-24 20:36:55 -04004746 tz = self.tz
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004747 TZ = os.environ.get('TZ')
4748 os.environ['TZ'] = self.zonename
4749 try:
4750 _time.tzset()
4751 for udt, shift in tz.transitions():
4752 if self.zonename == 'Europe/Tallinn' and udt.date() == date(1999, 10, 31):
4753 print("Skip %s %s transition" % (self.zonename, udt))
4754 continue
Alexander Belopolsky611adf22016-07-26 12:23:16 -04004755 if self.sizeof_time_t == 4 and udt.year >= 2037:
4756 print("Skip %s %s transition for 32-bit time_t" % (self.zonename, udt))
4757 continue
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004758 s0 = (udt - datetime(1970, 1, 1)) // SEC
4759 ss = shift // SEC # shift seconds
4760 for x in [-40 * 3600, -20*3600, -1, 0,
4761 ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4762 s = s0 + x
4763 sdt = datetime.fromtimestamp(s)
4764 tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4765 self.assertEquivDatetimes(sdt, tzdt)
4766 s1 = sdt.timestamp()
4767 self.assertEqual(s, s1)
4768 if ss > 0: # gap
4769 # Create local time inside the gap
4770 dt = datetime.fromtimestamp(s0) - shift / 2
4771 ts0 = dt.timestamp()
4772 ts1 = dt.replace(fold=1).timestamp()
4773 self.assertEqual(ts0, s0 + ss / 2)
4774 self.assertEqual(ts1, s0 - ss / 2)
4775 finally:
4776 if TZ is None:
4777 del os.environ['TZ']
4778 else:
4779 os.environ['TZ'] = TZ
4780 _time.tzset()
4781
4782
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004783class ZoneInfoCompleteTest(unittest.TestSuite):
4784 def __init__(self):
4785 tests = []
4786 if is_resource_enabled('tzdata'):
4787 for name in ZoneInfo.zonenames():
4788 Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4789 Test.zonename = name
4790 for method in dir(Test):
4791 if method.startswith('test_'):
4792 tests.append(Test(method))
4793 super().__init__(tests)
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004794
4795# Iran had a sub-minute UTC offset before 1946.
4796class IranTest(ZoneInfoTest):
Alexander Belopolsky07e2a0a2016-07-30 11:41:02 -04004797 zonename = 'Asia/Tehran'
Alexander Belopolsky5d0c5982016-07-22 18:47:04 -04004798
Alexander Belopolsky4719ae72016-07-24 14:39:28 -04004799def load_tests(loader, standard_tests, pattern):
4800 standard_tests.addTest(ZoneInfoCompleteTest())
4801 return standard_tests
4802
4803
Alexander Belopolskycf86e362010-07-23 19:25:47 +00004804if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05004805 unittest.main()