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