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