blob: 63bfed53965f73047e3748184f3ca9ecc01cf7b4 [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"""
5
Serhiy Storchakae28209f2015-11-16 11:12:58 +02006import copy
Antoine Pitrou392f4132014-10-03 11:25:30 +02007import decimal
Alexander Belopolskycf86e362010-07-23 19:25:47 +00008import sys
9import pickle
Raymond Hettinger5a2146a2014-07-25 14:59:48 -070010import random
Alexander Belopolskycf86e362010-07-23 19:25:47 +000011import unittest
12
13from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
14
15from test import support
16
17import datetime as datetime_module
18from datetime import MINYEAR, MAXYEAR
19from datetime import timedelta
20from datetime import tzinfo
21from datetime import time
22from datetime import timezone
23from datetime import date, datetime
24import time as _time
25
26# Needed by test_datetime
27import _strptime
28#
29
30
31pickle_choices = [(pickle, pickle, proto)
32 for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
33assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
34
35# An arbitrary collection of objects of non-datetime types, for testing
36# mixed-type comparisons.
37OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
38
39
40# XXX Copied from test_float.
41INF = float("inf")
42NAN = float("nan")
43
Alexander Belopolskycf86e362010-07-23 19:25:47 +000044
45#############################################################################
46# module tests
47
48class TestModule(unittest.TestCase):
49
50 def test_constants(self):
51 datetime = datetime_module
52 self.assertEqual(datetime.MINYEAR, 1)
53 self.assertEqual(datetime.MAXYEAR, 9999)
54
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -040055 def test_name_cleanup(self):
56 if '_Fast' not in str(self):
57 return
58 datetime = datetime_module
59 names = set(name for name in dir(datetime)
60 if not name.startswith('__') and not name.endswith('__'))
61 allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
62 'datetime_CAPI', 'time', 'timedelta', 'timezone',
63 'tzinfo'])
64 self.assertEqual(names - allowed, set([]))
65
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -050066 def test_divide_and_round(self):
67 if '_Fast' in str(self):
68 return
69 dar = datetime_module._divide_and_round
70
71 self.assertEqual(dar(-10, -3), 3)
72 self.assertEqual(dar(5, -2), -2)
73
74 # four cases: (2 signs of a) x (2 signs of b)
75 self.assertEqual(dar(7, 3), 2)
76 self.assertEqual(dar(-7, 3), -2)
77 self.assertEqual(dar(7, -3), -2)
78 self.assertEqual(dar(-7, -3), 2)
79
80 # ties to even - eight cases:
81 # (2 signs of a) x (2 signs of b) x (even / odd quotient)
82 self.assertEqual(dar(10, 4), 2)
83 self.assertEqual(dar(-10, 4), -2)
84 self.assertEqual(dar(10, -4), -2)
85 self.assertEqual(dar(-10, -4), 2)
86
87 self.assertEqual(dar(6, 4), 2)
88 self.assertEqual(dar(-6, 4), -2)
89 self.assertEqual(dar(6, -4), -2)
90 self.assertEqual(dar(-6, -4), 2)
91
92
Alexander Belopolskycf86e362010-07-23 19:25:47 +000093#############################################################################
94# tzinfo tests
95
96class FixedOffset(tzinfo):
97
98 def __init__(self, offset, name, dstoffset=42):
99 if isinstance(offset, int):
100 offset = timedelta(minutes=offset)
101 if isinstance(dstoffset, int):
102 dstoffset = timedelta(minutes=dstoffset)
103 self.__offset = offset
104 self.__name = name
105 self.__dstoffset = dstoffset
106 def __repr__(self):
107 return self.__name.lower()
108 def utcoffset(self, dt):
109 return self.__offset
110 def tzname(self, dt):
111 return self.__name
112 def dst(self, dt):
113 return self.__dstoffset
114
115class PicklableFixedOffset(FixedOffset):
116
117 def __init__(self, offset=None, name=None, dstoffset=None):
118 FixedOffset.__init__(self, offset, name, dstoffset)
119
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700120class _TZInfo(tzinfo):
121 def utcoffset(self, datetime_module):
122 return random.random()
123
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000124class TestTZInfo(unittest.TestCase):
125
Raymond Hettinger5a2146a2014-07-25 14:59:48 -0700126 def test_refcnt_crash_bug_22044(self):
127 tz1 = _TZInfo()
128 dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
129 with self.assertRaises(TypeError):
130 dt1.utcoffset()
131
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000132 def test_non_abstractness(self):
133 # In order to allow subclasses to get pickled, the C implementation
134 # wasn't able to get away with having __init__ raise
135 # NotImplementedError.
136 useless = tzinfo()
137 dt = datetime.max
138 self.assertRaises(NotImplementedError, useless.tzname, dt)
139 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
140 self.assertRaises(NotImplementedError, useless.dst, dt)
141
142 def test_subclass_must_override(self):
143 class NotEnough(tzinfo):
144 def __init__(self, offset, name):
145 self.__offset = offset
146 self.__name = name
147 self.assertTrue(issubclass(NotEnough, tzinfo))
148 ne = NotEnough(3, "NotByALongShot")
149 self.assertIsInstance(ne, tzinfo)
150
151 dt = datetime.now()
152 self.assertRaises(NotImplementedError, ne.tzname, dt)
153 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
154 self.assertRaises(NotImplementedError, ne.dst, dt)
155
156 def test_normal(self):
157 fo = FixedOffset(3, "Three")
158 self.assertIsInstance(fo, tzinfo)
159 for dt in datetime.now(), None:
160 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
161 self.assertEqual(fo.tzname(dt), "Three")
162 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
163
164 def test_pickling_base(self):
165 # There's no point to pickling tzinfo objects on their own (they
166 # carry no data), but they need to be picklable anyway else
167 # concrete subclasses can't be pickled.
168 orig = tzinfo.__new__(tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200169 self.assertIs(type(orig), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000170 for pickler, unpickler, proto in pickle_choices:
171 green = pickler.dumps(orig, proto)
172 derived = unpickler.loads(green)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200173 self.assertIs(type(derived), tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000174
175 def test_pickling_subclass(self):
176 # Make sure we can pickle/unpickle an instance of a subclass.
177 offset = timedelta(minutes=-300)
178 for otype, args in [
179 (PicklableFixedOffset, (offset, 'cookie')),
180 (timezone, (offset,)),
181 (timezone, (offset, "EST"))]:
182 orig = otype(*args)
183 oname = orig.tzname(None)
184 self.assertIsInstance(orig, tzinfo)
185 self.assertIs(type(orig), otype)
186 self.assertEqual(orig.utcoffset(None), offset)
187 self.assertEqual(orig.tzname(None), oname)
188 for pickler, unpickler, proto in pickle_choices:
189 green = pickler.dumps(orig, proto)
190 derived = unpickler.loads(green)
191 self.assertIsInstance(derived, tzinfo)
192 self.assertIs(type(derived), otype)
193 self.assertEqual(derived.utcoffset(None), offset)
194 self.assertEqual(derived.tzname(None), oname)
195
Alexander Belopolsky365ba8f2015-09-27 22:32:15 -0400196 def test_issue23600(self):
197 DSTDIFF = DSTOFFSET = timedelta(hours=1)
198
199 class UKSummerTime(tzinfo):
200 """Simple time zone which pretends to always be in summer time, since
201 that's what shows the failure.
202 """
203
204 def utcoffset(self, dt):
205 return DSTOFFSET
206
207 def dst(self, dt):
208 return DSTDIFF
209
210 def tzname(self, dt):
211 return 'UKSummerTime'
212
213 tz = UKSummerTime()
214 u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
215 t = tz.fromutc(u)
216 self.assertEqual(t - t.utcoffset(), u)
217
218
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000219class TestTimeZone(unittest.TestCase):
220
221 def setUp(self):
222 self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
223 self.EST = timezone(-timedelta(hours=5), 'EST')
224 self.DT = datetime(2010, 1, 1)
225
226 def test_str(self):
227 for tz in [self.ACDT, self.EST, timezone.utc,
228 timezone.min, timezone.max]:
229 self.assertEqual(str(tz), tz.tzname(None))
230
231 def test_repr(self):
232 datetime = datetime_module
233 for tz in [self.ACDT, self.EST, timezone.utc,
234 timezone.min, timezone.max]:
235 # test round-trip
236 tzrep = repr(tz)
237 self.assertEqual(tz, eval(tzrep))
238
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000239 def test_class_members(self):
240 limit = timedelta(hours=23, minutes=59)
241 self.assertEqual(timezone.utc.utcoffset(None), ZERO)
242 self.assertEqual(timezone.min.utcoffset(None), -limit)
243 self.assertEqual(timezone.max.utcoffset(None), limit)
244
245
246 def test_constructor(self):
Alexander Belopolsky1bcbaab2010-10-14 17:03:51 +0000247 self.assertIs(timezone.utc, timezone(timedelta(0)))
248 self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
249 self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000250 # invalid offsets
251 for invalid in [timedelta(microseconds=1), timedelta(1, 1),
252 timedelta(seconds=1), timedelta(1), -timedelta(1)]:
253 self.assertRaises(ValueError, timezone, invalid)
254 self.assertRaises(ValueError, timezone, -invalid)
255
256 with self.assertRaises(TypeError): timezone(None)
257 with self.assertRaises(TypeError): timezone(42)
258 with self.assertRaises(TypeError): timezone(ZERO, None)
259 with self.assertRaises(TypeError): timezone(ZERO, 42)
260 with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
261
262 def test_inheritance(self):
263 self.assertIsInstance(timezone.utc, tzinfo)
264 self.assertIsInstance(self.EST, tzinfo)
265
266 def test_utcoffset(self):
267 dummy = self.DT
268 for h in [0, 1.5, 12]:
269 offset = h * HOUR
270 self.assertEqual(offset, timezone(offset).utcoffset(dummy))
271 self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
272
273 with self.assertRaises(TypeError): self.EST.utcoffset('')
274 with self.assertRaises(TypeError): self.EST.utcoffset(5)
275
276
277 def test_dst(self):
Raymond Hettinger7beae8a2011-01-06 05:34:17 +0000278 self.assertIsNone(timezone.utc.dst(self.DT))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000279
280 with self.assertRaises(TypeError): self.EST.dst('')
281 with self.assertRaises(TypeError): self.EST.dst(5)
282
283 def test_tzname(self):
284 self.assertEqual('UTC+00:00', timezone(ZERO).tzname(None))
285 self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
286 self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
287 self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
288 self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
289
290 with self.assertRaises(TypeError): self.EST.tzname('')
291 with self.assertRaises(TypeError): self.EST.tzname(5)
292
293 def test_fromutc(self):
294 with self.assertRaises(ValueError):
295 timezone.utc.fromutc(self.DT)
296 with self.assertRaises(TypeError):
297 timezone.utc.fromutc('not datetime')
298 for tz in [self.EST, self.ACDT, Eastern]:
299 utctime = self.DT.replace(tzinfo=tz)
300 local = tz.fromutc(utctime)
301 self.assertEqual(local - utctime, tz.utcoffset(local))
302 self.assertEqual(local,
303 self.DT.replace(tzinfo=timezone.utc))
304
305 def test_comparison(self):
306 self.assertNotEqual(timezone(ZERO), timezone(HOUR))
307 self.assertEqual(timezone(HOUR), timezone(HOUR))
308 self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
309 with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
310 self.assertIn(timezone(ZERO), {timezone(ZERO)})
Georg Brandl0085a242012-09-22 09:23:12 +0200311 self.assertTrue(timezone(ZERO) != None)
312 self.assertFalse(timezone(ZERO) == None)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000313
314 def test_aware_datetime(self):
315 # test that timezone instances can be used by datetime
316 t = datetime(1, 1, 1)
317 for tz in [timezone.min, timezone.max, timezone.utc]:
318 self.assertEqual(tz.tzname(t),
319 t.replace(tzinfo=tz).tzname())
320 self.assertEqual(tz.utcoffset(t),
321 t.replace(tzinfo=tz).utcoffset())
322 self.assertEqual(tz.dst(t),
323 t.replace(tzinfo=tz).dst())
324
Serhiy Storchakae28209f2015-11-16 11:12:58 +0200325 def test_pickle(self):
326 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
327 for pickler, unpickler, proto in pickle_choices:
328 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
329 self.assertEqual(tz_copy, tz)
330 tz = timezone.utc
331 for pickler, unpickler, proto in pickle_choices:
332 tz_copy = unpickler.loads(pickler.dumps(tz, proto))
333 self.assertIs(tz_copy, tz)
334
335 def test_copy(self):
336 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
337 tz_copy = copy.copy(tz)
338 self.assertEqual(tz_copy, tz)
339 tz = timezone.utc
340 tz_copy = copy.copy(tz)
341 self.assertIs(tz_copy, tz)
342
343 def test_deepcopy(self):
344 for tz in self.ACDT, self.EST, timezone.min, timezone.max:
345 tz_copy = copy.deepcopy(tz)
346 self.assertEqual(tz_copy, tz)
347 tz = timezone.utc
348 tz_copy = copy.deepcopy(tz)
349 self.assertIs(tz_copy, tz)
350
351
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000352#############################################################################
Ezio Melotti85a86292013-08-17 16:57:41 +0300353# Base class for testing a particular aspect of timedelta, time, date and
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000354# datetime comparisons.
355
356class HarmlessMixedComparison:
357 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
358
359 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
360 # legit constructor.
361
362 def test_harmless_mixed_comparison(self):
363 me = self.theclass(1, 1, 1)
364
365 self.assertFalse(me == ())
366 self.assertTrue(me != ())
367 self.assertFalse(() == me)
368 self.assertTrue(() != me)
369
370 self.assertIn(me, [1, 20, [], me])
371 self.assertIn([], [me, 1, 20, []])
372
373 def test_harmful_mixed_comparison(self):
374 me = self.theclass(1, 1, 1)
375
376 self.assertRaises(TypeError, lambda: me < ())
377 self.assertRaises(TypeError, lambda: me <= ())
378 self.assertRaises(TypeError, lambda: me > ())
379 self.assertRaises(TypeError, lambda: me >= ())
380
381 self.assertRaises(TypeError, lambda: () < me)
382 self.assertRaises(TypeError, lambda: () <= me)
383 self.assertRaises(TypeError, lambda: () > me)
384 self.assertRaises(TypeError, lambda: () >= me)
385
386#############################################################################
387# timedelta tests
388
389class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
390
391 theclass = timedelta
392
393 def test_constructor(self):
394 eq = self.assertEqual
395 td = timedelta
396
397 # Check keyword args to constructor
398 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
399 milliseconds=0, microseconds=0))
400 eq(td(1), td(days=1))
401 eq(td(0, 1), td(seconds=1))
402 eq(td(0, 0, 1), td(microseconds=1))
403 eq(td(weeks=1), td(days=7))
404 eq(td(days=1), td(hours=24))
405 eq(td(hours=1), td(minutes=60))
406 eq(td(minutes=1), td(seconds=60))
407 eq(td(seconds=1), td(milliseconds=1000))
408 eq(td(milliseconds=1), td(microseconds=1000))
409
410 # Check float args to constructor
411 eq(td(weeks=1.0/7), td(days=1))
412 eq(td(days=1.0/24), td(hours=1))
413 eq(td(hours=1.0/60), td(minutes=1))
414 eq(td(minutes=1.0/60), td(seconds=1))
415 eq(td(seconds=0.001), td(milliseconds=1))
416 eq(td(milliseconds=0.001), td(microseconds=1))
417
418 def test_computations(self):
419 eq = self.assertEqual
420 td = timedelta
421
422 a = td(7) # One week
423 b = td(0, 60) # One minute
424 c = td(0, 0, 1000) # One millisecond
425 eq(a+b+c, td(7, 60, 1000))
426 eq(a-b, td(6, 24*3600 - 60))
427 eq(b.__rsub__(a), td(6, 24*3600 - 60))
428 eq(-a, td(-7))
429 eq(+a, td(7))
430 eq(-b, td(-1, 24*3600 - 60))
431 eq(-c, td(-1, 24*3600 - 1, 999000))
432 eq(abs(a), a)
433 eq(abs(-a), a)
434 eq(td(6, 24*3600), a)
435 eq(td(0, 0, 60*1000000), b)
436 eq(a*10, td(70))
437 eq(a*10, 10*a)
438 eq(a*10, 10*a)
439 eq(b*10, td(0, 600))
440 eq(10*b, td(0, 600))
441 eq(b*10, td(0, 600))
442 eq(c*10, td(0, 0, 10000))
443 eq(10*c, td(0, 0, 10000))
444 eq(c*10, td(0, 0, 10000))
445 eq(a*-1, -a)
446 eq(b*-2, -b-b)
447 eq(c*-2, -c+-c)
448 eq(b*(60*24), (b*60)*24)
449 eq(b*(60*24), (60*b)*24)
450 eq(c*1000, td(0, 1))
451 eq(1000*c, td(0, 1))
452 eq(a//7, td(1))
453 eq(b//10, td(0, 6))
454 eq(c//1000, td(0, 0, 1))
455 eq(a//10, td(0, 7*24*360))
456 eq(a//3600000, td(0, 0, 7*24*1000))
457 eq(a/0.5, td(14))
458 eq(b/0.5, td(0, 120))
459 eq(a/7, td(1))
460 eq(b/10, td(0, 6))
461 eq(c/1000, td(0, 0, 1))
462 eq(a/10, td(0, 7*24*360))
463 eq(a/3600000, td(0, 0, 7*24*1000))
464
465 # Multiplication by float
466 us = td(microseconds=1)
467 eq((3*us) * 0.5, 2*us)
468 eq((5*us) * 0.5, 2*us)
469 eq(0.5 * (3*us), 2*us)
470 eq(0.5 * (5*us), 2*us)
471 eq((-3*us) * 0.5, -2*us)
472 eq((-5*us) * 0.5, -2*us)
473
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500474 # Issue #23521
475 eq(td(seconds=1) * 0.123456, td(microseconds=123456))
476 eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
477
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000478 # Division by int and float
479 eq((3*us) / 2, 2*us)
480 eq((5*us) / 2, 2*us)
481 eq((-3*us) / 2.0, -2*us)
482 eq((-5*us) / 2.0, -2*us)
483 eq((3*us) / -2, -2*us)
484 eq((5*us) / -2, -2*us)
485 eq((3*us) / -2.0, -2*us)
486 eq((5*us) / -2.0, -2*us)
487 for i in range(-10, 10):
488 eq((i*us/3)//us, round(i/3))
489 for i in range(-10, 10):
490 eq((i*us/-3)//us, round(i/-3))
491
Alexander Belopolsky24d3dee2015-02-28 10:41:57 -0500492 # Issue #23521
493 eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
494
Alexander Belopolskyb6f5ec72011-04-05 20:07:38 -0400495 # Issue #11576
496 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
497 td(0, 0, 1))
498 eq(td(999999999, 1, 1) - td(999999999, 1, 0),
499 td(0, 0, 1))
500
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000501 def test_disallowed_computations(self):
502 a = timedelta(42)
503
504 # Add/sub ints or floats should be illegal
505 for i in 1, 1.0:
506 self.assertRaises(TypeError, lambda: a+i)
507 self.assertRaises(TypeError, lambda: a-i)
508 self.assertRaises(TypeError, lambda: i+a)
509 self.assertRaises(TypeError, lambda: i-a)
510
511 # Division of int by timedelta doesn't make sense.
512 # Division by zero doesn't make sense.
513 zero = 0
514 self.assertRaises(TypeError, lambda: zero // a)
515 self.assertRaises(ZeroDivisionError, lambda: a // zero)
516 self.assertRaises(ZeroDivisionError, lambda: a / zero)
517 self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
518 self.assertRaises(TypeError, lambda: a / '')
519
Eric Smith3ab08ca2010-12-04 15:17:38 +0000520 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000521 def test_disallowed_special(self):
522 a = timedelta(42)
523 self.assertRaises(ValueError, a.__mul__, NAN)
524 self.assertRaises(ValueError, a.__truediv__, NAN)
525
526 def test_basic_attributes(self):
527 days, seconds, us = 1, 7, 31
528 td = timedelta(days, seconds, us)
529 self.assertEqual(td.days, days)
530 self.assertEqual(td.seconds, seconds)
531 self.assertEqual(td.microseconds, us)
532
533 def test_total_seconds(self):
534 td = timedelta(days=365)
535 self.assertEqual(td.total_seconds(), 31536000.0)
536 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
537 td = timedelta(seconds=total_seconds)
538 self.assertEqual(td.total_seconds(), total_seconds)
539 # Issue8644: Test that td.total_seconds() has the same
540 # accuracy as td / timedelta(seconds=1).
541 for ms in [-1, -2, -123]:
542 td = timedelta(microseconds=ms)
543 self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
544
545 def test_carries(self):
546 t1 = timedelta(days=100,
547 weeks=-7,
548 hours=-24*(100-49),
549 minutes=-3,
550 seconds=12,
551 microseconds=(3*60 - 12) * 1e6 + 1)
552 t2 = timedelta(microseconds=1)
553 self.assertEqual(t1, t2)
554
555 def test_hash_equality(self):
556 t1 = timedelta(days=100,
557 weeks=-7,
558 hours=-24*(100-49),
559 minutes=-3,
560 seconds=12,
561 microseconds=(3*60 - 12) * 1000000)
562 t2 = timedelta()
563 self.assertEqual(hash(t1), hash(t2))
564
565 t1 += timedelta(weeks=7)
566 t2 += timedelta(days=7*7)
567 self.assertEqual(t1, t2)
568 self.assertEqual(hash(t1), hash(t2))
569
570 d = {t1: 1}
571 d[t2] = 2
572 self.assertEqual(len(d), 1)
573 self.assertEqual(d[t1], 2)
574
575 def test_pickling(self):
576 args = 12, 34, 56
577 orig = timedelta(*args)
578 for pickler, unpickler, proto in pickle_choices:
579 green = pickler.dumps(orig, proto)
580 derived = unpickler.loads(green)
581 self.assertEqual(orig, derived)
582
583 def test_compare(self):
584 t1 = timedelta(2, 3, 4)
585 t2 = timedelta(2, 3, 4)
586 self.assertEqual(t1, t2)
587 self.assertTrue(t1 <= t2)
588 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200589 self.assertFalse(t1 != t2)
590 self.assertFalse(t1 < t2)
591 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000592
593 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
594 t2 = timedelta(*args) # this is larger than t1
595 self.assertTrue(t1 < t2)
596 self.assertTrue(t2 > t1)
597 self.assertTrue(t1 <= t2)
598 self.assertTrue(t2 >= t1)
599 self.assertTrue(t1 != t2)
600 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200601 self.assertFalse(t1 == t2)
602 self.assertFalse(t2 == t1)
603 self.assertFalse(t1 > t2)
604 self.assertFalse(t2 < t1)
605 self.assertFalse(t1 >= t2)
606 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000607
608 for badarg in OTHERSTUFF:
609 self.assertEqual(t1 == badarg, False)
610 self.assertEqual(t1 != badarg, True)
611 self.assertEqual(badarg == t1, False)
612 self.assertEqual(badarg != t1, True)
613
614 self.assertRaises(TypeError, lambda: t1 <= badarg)
615 self.assertRaises(TypeError, lambda: t1 < badarg)
616 self.assertRaises(TypeError, lambda: t1 > badarg)
617 self.assertRaises(TypeError, lambda: t1 >= badarg)
618 self.assertRaises(TypeError, lambda: badarg <= t1)
619 self.assertRaises(TypeError, lambda: badarg < t1)
620 self.assertRaises(TypeError, lambda: badarg > t1)
621 self.assertRaises(TypeError, lambda: badarg >= t1)
622
623 def test_str(self):
624 td = timedelta
625 eq = self.assertEqual
626
627 eq(str(td(1)), "1 day, 0:00:00")
628 eq(str(td(-1)), "-1 day, 0:00:00")
629 eq(str(td(2)), "2 days, 0:00:00")
630 eq(str(td(-2)), "-2 days, 0:00:00")
631
632 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
633 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
634 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
635 "-210 days, 23:12:34")
636
637 eq(str(td(milliseconds=1)), "0:00:00.001000")
638 eq(str(td(microseconds=3)), "0:00:00.000003")
639
640 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
641 microseconds=999999)),
642 "999999999 days, 23:59:59.999999")
643
644 def test_repr(self):
645 name = 'datetime.' + self.theclass.__name__
646 self.assertEqual(repr(self.theclass(1)),
647 "%s(1)" % name)
648 self.assertEqual(repr(self.theclass(10, 2)),
649 "%s(10, 2)" % name)
650 self.assertEqual(repr(self.theclass(-10, 2, 400000)),
651 "%s(-10, 2, 400000)" % name)
652
653 def test_roundtrip(self):
654 for td in (timedelta(days=999999999, hours=23, minutes=59,
655 seconds=59, microseconds=999999),
656 timedelta(days=-999999999),
657 timedelta(days=-999999999, seconds=1),
658 timedelta(days=1, seconds=2, microseconds=3)):
659
660 # Verify td -> string -> td identity.
661 s = repr(td)
662 self.assertTrue(s.startswith('datetime.'))
663 s = s[9:]
664 td2 = eval(s)
665 self.assertEqual(td, td2)
666
667 # Verify identity via reconstructing from pieces.
668 td2 = timedelta(td.days, td.seconds, td.microseconds)
669 self.assertEqual(td, td2)
670
671 def test_resolution_info(self):
672 self.assertIsInstance(timedelta.min, timedelta)
673 self.assertIsInstance(timedelta.max, timedelta)
674 self.assertIsInstance(timedelta.resolution, timedelta)
675 self.assertTrue(timedelta.max > timedelta.min)
676 self.assertEqual(timedelta.min, timedelta(-999999999))
677 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
678 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
679
680 def test_overflow(self):
681 tiny = timedelta.resolution
682
683 td = timedelta.min + tiny
684 td -= tiny # no problem
685 self.assertRaises(OverflowError, td.__sub__, tiny)
686 self.assertRaises(OverflowError, td.__add__, -tiny)
687
688 td = timedelta.max - tiny
689 td += tiny # no problem
690 self.assertRaises(OverflowError, td.__add__, tiny)
691 self.assertRaises(OverflowError, td.__sub__, -tiny)
692
693 self.assertRaises(OverflowError, lambda: -timedelta.max)
694
695 day = timedelta(1)
696 self.assertRaises(OverflowError, day.__mul__, 10**9)
697 self.assertRaises(OverflowError, day.__mul__, 1e9)
698 self.assertRaises(OverflowError, day.__truediv__, 1e-20)
699 self.assertRaises(OverflowError, day.__truediv__, 1e-10)
700 self.assertRaises(OverflowError, day.__truediv__, 9e-10)
701
Eric Smith3ab08ca2010-12-04 15:17:38 +0000702 @support.requires_IEEE_754
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000703 def _test_overflow_special(self):
704 day = timedelta(1)
705 self.assertRaises(OverflowError, day.__mul__, INF)
706 self.assertRaises(OverflowError, day.__mul__, -INF)
707
708 def test_microsecond_rounding(self):
709 td = timedelta
710 eq = self.assertEqual
711
712 # Single-field rounding.
713 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
714 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -0400715 eq(td(milliseconds=0.5/1000), td(microseconds=0))
Victor Stinner511491a2015-09-18 14:42:05 +0200716 eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000717 eq(td(milliseconds=0.6/1000), td(microseconds=1))
718 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
Victor Stinner511491a2015-09-18 14:42:05 +0200719 eq(td(milliseconds=1.5/1000), td(microseconds=2))
720 eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -0400721 eq(td(seconds=0.5/10**6), td(microseconds=0))
Victor Stinner511491a2015-09-18 14:42:05 +0200722 eq(td(seconds=-0.5/10**6), td(microseconds=-0))
723 eq(td(seconds=1/2**7), td(microseconds=7812))
724 eq(td(seconds=-1/2**7), td(microseconds=-7812))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000725
726 # Rounding due to contributions from more than one field.
727 us_per_hour = 3600e6
728 us_per_day = us_per_hour * 24
729 eq(td(days=.4/us_per_day), td(0))
730 eq(td(hours=.2/us_per_hour), td(0))
731 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
732
733 eq(td(days=-.4/us_per_day), td(0))
734 eq(td(hours=-.2/us_per_hour), td(0))
735 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
736
Alexander Belopolsky790d2692013-08-04 14:51:35 -0400737 # Test for a patch in Issue 8860
738 eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
739 eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
740
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000741 def test_massive_normalization(self):
742 td = timedelta(microseconds=-1)
743 self.assertEqual((td.days, td.seconds, td.microseconds),
744 (-1, 24*3600-1, 999999))
745
746 def test_bool(self):
747 self.assertTrue(timedelta(1))
748 self.assertTrue(timedelta(0, 1))
749 self.assertTrue(timedelta(0, 0, 1))
750 self.assertTrue(timedelta(microseconds=1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200751 self.assertFalse(timedelta(0))
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000752
753 def test_subclass_timedelta(self):
754
755 class T(timedelta):
756 @staticmethod
757 def from_td(td):
758 return T(td.days, td.seconds, td.microseconds)
759
760 def as_hours(self):
761 sum = (self.days * 24 +
762 self.seconds / 3600.0 +
763 self.microseconds / 3600e6)
764 return round(sum)
765
766 t1 = T(days=1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200767 self.assertIs(type(t1), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000768 self.assertEqual(t1.as_hours(), 24)
769
770 t2 = T(days=-1, seconds=-3600)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200771 self.assertIs(type(t2), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000772 self.assertEqual(t2.as_hours(), -25)
773
774 t3 = t1 + t2
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200775 self.assertIs(type(t3), timedelta)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000776 t4 = T.from_td(t3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +0200777 self.assertIs(type(t4), T)
Alexander Belopolskycf86e362010-07-23 19:25:47 +0000778 self.assertEqual(t3.days, t4.days)
779 self.assertEqual(t3.seconds, t4.seconds)
780 self.assertEqual(t3.microseconds, t4.microseconds)
781 self.assertEqual(str(t3), str(t4))
782 self.assertEqual(t4.as_hours(), -1)
783
784 def test_division(self):
785 t = timedelta(hours=1, minutes=24, seconds=19)
786 second = timedelta(seconds=1)
787 self.assertEqual(t / second, 5059.0)
788 self.assertEqual(t // second, 5059)
789
790 t = timedelta(minutes=2, seconds=30)
791 minute = timedelta(minutes=1)
792 self.assertEqual(t / minute, 2.5)
793 self.assertEqual(t // minute, 2)
794
795 zerotd = timedelta(0)
796 self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
797 self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
798
799 # self.assertRaises(TypeError, truediv, t, 2)
800 # note: floor division of a timedelta by an integer *is*
801 # currently permitted.
802
803 def test_remainder(self):
804 t = timedelta(minutes=2, seconds=30)
805 minute = timedelta(minutes=1)
806 r = t % minute
807 self.assertEqual(r, timedelta(seconds=30))
808
809 t = timedelta(minutes=-2, seconds=30)
810 r = t % minute
811 self.assertEqual(r, timedelta(seconds=30))
812
813 zerotd = timedelta(0)
814 self.assertRaises(ZeroDivisionError, mod, t, zerotd)
815
816 self.assertRaises(TypeError, mod, t, 10)
817
818 def test_divmod(self):
819 t = timedelta(minutes=2, seconds=30)
820 minute = timedelta(minutes=1)
821 q, r = divmod(t, minute)
822 self.assertEqual(q, 2)
823 self.assertEqual(r, timedelta(seconds=30))
824
825 t = timedelta(minutes=-2, seconds=30)
826 q, r = divmod(t, minute)
827 self.assertEqual(q, -2)
828 self.assertEqual(r, timedelta(seconds=30))
829
830 zerotd = timedelta(0)
831 self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
832
833 self.assertRaises(TypeError, divmod, t, 10)
834
835
836#############################################################################
837# date tests
838
839class TestDateOnly(unittest.TestCase):
840 # Tests here won't pass if also run on datetime objects, so don't
841 # subclass this to test datetimes too.
842
843 def test_delta_non_days_ignored(self):
844 dt = date(2000, 1, 2)
845 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
846 microseconds=5)
847 days = timedelta(delta.days)
848 self.assertEqual(days, timedelta(1))
849
850 dt2 = dt + delta
851 self.assertEqual(dt2, dt + days)
852
853 dt2 = delta + dt
854 self.assertEqual(dt2, dt + days)
855
856 dt2 = dt - delta
857 self.assertEqual(dt2, dt - days)
858
859 delta = -delta
860 days = timedelta(delta.days)
861 self.assertEqual(days, timedelta(-2))
862
863 dt2 = dt + delta
864 self.assertEqual(dt2, dt + days)
865
866 dt2 = delta + dt
867 self.assertEqual(dt2, dt + days)
868
869 dt2 = dt - delta
870 self.assertEqual(dt2, dt - days)
871
872class SubclassDate(date):
873 sub_var = 1
874
875class TestDate(HarmlessMixedComparison, unittest.TestCase):
876 # Tests here should pass for both dates and datetimes, except for a
877 # few tests that TestDateTime overrides.
878
879 theclass = date
880
881 def test_basic_attributes(self):
882 dt = self.theclass(2002, 3, 1)
883 self.assertEqual(dt.year, 2002)
884 self.assertEqual(dt.month, 3)
885 self.assertEqual(dt.day, 1)
886
887 def test_roundtrip(self):
888 for dt in (self.theclass(1, 2, 3),
889 self.theclass.today()):
890 # Verify dt -> string -> date identity.
891 s = repr(dt)
892 self.assertTrue(s.startswith('datetime.'))
893 s = s[9:]
894 dt2 = eval(s)
895 self.assertEqual(dt, dt2)
896
897 # Verify identity via reconstructing from pieces.
898 dt2 = self.theclass(dt.year, dt.month, dt.day)
899 self.assertEqual(dt, dt2)
900
901 def test_ordinal_conversions(self):
902 # Check some fixed values.
903 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
904 (1, 12, 31, 365),
905 (2, 1, 1, 366),
906 # first example from "Calendrical Calculations"
907 (1945, 11, 12, 710347)]:
908 d = self.theclass(y, m, d)
909 self.assertEqual(n, d.toordinal())
910 fromord = self.theclass.fromordinal(n)
911 self.assertEqual(d, fromord)
912 if hasattr(fromord, "hour"):
913 # if we're checking something fancier than a date, verify
914 # the extra fields have been zeroed out
915 self.assertEqual(fromord.hour, 0)
916 self.assertEqual(fromord.minute, 0)
917 self.assertEqual(fromord.second, 0)
918 self.assertEqual(fromord.microsecond, 0)
919
920 # Check first and last days of year spottily across the whole
921 # range of years supported.
922 for year in range(MINYEAR, MAXYEAR+1, 7):
923 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
924 d = self.theclass(year, 1, 1)
925 n = d.toordinal()
926 d2 = self.theclass.fromordinal(n)
927 self.assertEqual(d, d2)
928 # Verify that moving back a day gets to the end of year-1.
929 if year > 1:
930 d = self.theclass.fromordinal(n-1)
931 d2 = self.theclass(year-1, 12, 31)
932 self.assertEqual(d, d2)
933 self.assertEqual(d2.toordinal(), n-1)
934
935 # Test every day in a leap-year and a non-leap year.
936 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
937 for year, isleap in (2000, True), (2002, False):
938 n = self.theclass(year, 1, 1).toordinal()
939 for month, maxday in zip(range(1, 13), dim):
940 if month == 2 and isleap:
941 maxday += 1
942 for day in range(1, maxday+1):
943 d = self.theclass(year, month, day)
944 self.assertEqual(d.toordinal(), n)
945 self.assertEqual(d, self.theclass.fromordinal(n))
946 n += 1
947
948 def test_extreme_ordinals(self):
949 a = self.theclass.min
950 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
951 aord = a.toordinal()
952 b = a.fromordinal(aord)
953 self.assertEqual(a, b)
954
955 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
956
957 b = a + timedelta(days=1)
958 self.assertEqual(b.toordinal(), aord + 1)
959 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
960
961 a = self.theclass.max
962 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
963 aord = a.toordinal()
964 b = a.fromordinal(aord)
965 self.assertEqual(a, b)
966
967 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
968
969 b = a - timedelta(days=1)
970 self.assertEqual(b.toordinal(), aord - 1)
971 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
972
973 def test_bad_constructor_arguments(self):
974 # bad years
975 self.theclass(MINYEAR, 1, 1) # no exception
976 self.theclass(MAXYEAR, 1, 1) # no exception
977 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
978 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
979 # bad months
980 self.theclass(2000, 1, 1) # no exception
981 self.theclass(2000, 12, 1) # no exception
982 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
983 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
984 # bad days
985 self.theclass(2000, 2, 29) # no exception
986 self.theclass(2004, 2, 29) # no exception
987 self.theclass(2400, 2, 29) # no exception
988 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
989 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
990 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
991 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
992 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
993 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
994
995 def test_hash_equality(self):
996 d = self.theclass(2000, 12, 31)
997 # same thing
998 e = self.theclass(2000, 12, 31)
999 self.assertEqual(d, e)
1000 self.assertEqual(hash(d), hash(e))
1001
1002 dic = {d: 1}
1003 dic[e] = 2
1004 self.assertEqual(len(dic), 1)
1005 self.assertEqual(dic[d], 2)
1006 self.assertEqual(dic[e], 2)
1007
1008 d = self.theclass(2001, 1, 1)
1009 # same thing
1010 e = self.theclass(2001, 1, 1)
1011 self.assertEqual(d, e)
1012 self.assertEqual(hash(d), hash(e))
1013
1014 dic = {d: 1}
1015 dic[e] = 2
1016 self.assertEqual(len(dic), 1)
1017 self.assertEqual(dic[d], 2)
1018 self.assertEqual(dic[e], 2)
1019
1020 def test_computations(self):
1021 a = self.theclass(2002, 1, 31)
1022 b = self.theclass(1956, 1, 31)
1023 c = self.theclass(2001,2,1)
1024
1025 diff = a-b
1026 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1027 self.assertEqual(diff.seconds, 0)
1028 self.assertEqual(diff.microseconds, 0)
1029
1030 day = timedelta(1)
1031 week = timedelta(7)
1032 a = self.theclass(2002, 3, 2)
1033 self.assertEqual(a + day, self.theclass(2002, 3, 3))
1034 self.assertEqual(day + a, self.theclass(2002, 3, 3))
1035 self.assertEqual(a - day, self.theclass(2002, 3, 1))
1036 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1037 self.assertEqual(a + week, self.theclass(2002, 3, 9))
1038 self.assertEqual(a - week, self.theclass(2002, 2, 23))
1039 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1040 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1041 self.assertEqual((a + week) - a, week)
1042 self.assertEqual((a + day) - a, day)
1043 self.assertEqual((a - week) - a, -week)
1044 self.assertEqual((a - day) - a, -day)
1045 self.assertEqual(a - (a + week), -week)
1046 self.assertEqual(a - (a + day), -day)
1047 self.assertEqual(a - (a - week), week)
1048 self.assertEqual(a - (a - day), day)
1049 self.assertEqual(c - (c - day), day)
1050
1051 # Add/sub ints or floats should be illegal
1052 for i in 1, 1.0:
1053 self.assertRaises(TypeError, lambda: a+i)
1054 self.assertRaises(TypeError, lambda: a-i)
1055 self.assertRaises(TypeError, lambda: i+a)
1056 self.assertRaises(TypeError, lambda: i-a)
1057
1058 # delta - date is senseless.
1059 self.assertRaises(TypeError, lambda: day - a)
1060 # mixing date and (delta or date) via * or // is senseless
1061 self.assertRaises(TypeError, lambda: day * a)
1062 self.assertRaises(TypeError, lambda: a * day)
1063 self.assertRaises(TypeError, lambda: day // a)
1064 self.assertRaises(TypeError, lambda: a // day)
1065 self.assertRaises(TypeError, lambda: a * a)
1066 self.assertRaises(TypeError, lambda: a // a)
1067 # date + date is senseless
1068 self.assertRaises(TypeError, lambda: a + a)
1069
1070 def test_overflow(self):
1071 tiny = self.theclass.resolution
1072
1073 for delta in [tiny, timedelta(1), timedelta(2)]:
1074 dt = self.theclass.min + delta
1075 dt -= delta # no problem
1076 self.assertRaises(OverflowError, dt.__sub__, delta)
1077 self.assertRaises(OverflowError, dt.__add__, -delta)
1078
1079 dt = self.theclass.max - delta
1080 dt += delta # no problem
1081 self.assertRaises(OverflowError, dt.__add__, delta)
1082 self.assertRaises(OverflowError, dt.__sub__, -delta)
1083
1084 def test_fromtimestamp(self):
1085 import time
1086
1087 # Try an arbitrary fixed value.
1088 year, month, day = 1999, 9, 19
1089 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1090 d = self.theclass.fromtimestamp(ts)
1091 self.assertEqual(d.year, year)
1092 self.assertEqual(d.month, month)
1093 self.assertEqual(d.day, day)
1094
1095 def test_insane_fromtimestamp(self):
1096 # It's possible that some platform maps time_t to double,
1097 # and that this test will fail there. This test should
1098 # exempt such platforms (provided they return reasonable
1099 # results!).
1100 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001101 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001102 insane)
1103
1104 def test_today(self):
1105 import time
1106
1107 # We claim that today() is like fromtimestamp(time.time()), so
1108 # prove it.
1109 for dummy in range(3):
1110 today = self.theclass.today()
1111 ts = time.time()
1112 todayagain = self.theclass.fromtimestamp(ts)
1113 if today == todayagain:
1114 break
1115 # There are several legit reasons that could fail:
1116 # 1. It recently became midnight, between the today() and the
1117 # time() calls.
1118 # 2. The platform time() has such fine resolution that we'll
1119 # never get the same value twice.
1120 # 3. The platform time() has poor resolution, and we just
1121 # happened to call today() right before a resolution quantum
1122 # boundary.
1123 # 4. The system clock got fiddled between calls.
1124 # In any case, wait a little while and try again.
1125 time.sleep(0.1)
1126
1127 # It worked or it didn't. If it didn't, assume it's reason #2, and
1128 # let the test pass if they're within half a second of each other.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001129 if today != todayagain:
1130 self.assertAlmostEqual(todayagain, today,
1131 delta=timedelta(seconds=0.5))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001132
1133 def test_weekday(self):
1134 for i in range(7):
1135 # March 4, 2002 is a Monday
1136 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1137 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1138 # January 2, 1956 is a Monday
1139 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1140 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1141
1142 def test_isocalendar(self):
1143 # Check examples from
1144 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1145 for i in range(7):
1146 d = self.theclass(2003, 12, 22+i)
1147 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1148 d = self.theclass(2003, 12, 29) + timedelta(i)
1149 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1150 d = self.theclass(2004, 1, 5+i)
1151 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1152 d = self.theclass(2009, 12, 21+i)
1153 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1154 d = self.theclass(2009, 12, 28) + timedelta(i)
1155 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1156 d = self.theclass(2010, 1, 4+i)
1157 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1158
1159 def test_iso_long_years(self):
1160 # Calculate long ISO years and compare to table from
1161 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1162 ISO_LONG_YEARS_TABLE = """
1163 4 32 60 88
1164 9 37 65 93
1165 15 43 71 99
1166 20 48 76
1167 26 54 82
1168
1169 105 133 161 189
1170 111 139 167 195
1171 116 144 172
1172 122 150 178
1173 128 156 184
1174
1175 201 229 257 285
1176 207 235 263 291
1177 212 240 268 296
1178 218 246 274
1179 224 252 280
1180
1181 303 331 359 387
1182 308 336 364 392
1183 314 342 370 398
1184 320 348 376
1185 325 353 381
1186 """
1187 iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1188 L = []
1189 for i in range(400):
1190 d = self.theclass(2000+i, 12, 31)
1191 d1 = self.theclass(1600+i, 12, 31)
1192 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1193 if d.isocalendar()[1] == 53:
1194 L.append(i)
1195 self.assertEqual(L, iso_long_years)
1196
1197 def test_isoformat(self):
1198 t = self.theclass(2, 3, 2)
1199 self.assertEqual(t.isoformat(), "0002-03-02")
1200
1201 def test_ctime(self):
1202 t = self.theclass(2002, 3, 2)
1203 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
1204
1205 def test_strftime(self):
1206 t = self.theclass(2005, 3, 2)
1207 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1208 self.assertEqual(t.strftime(""), "") # SF bug #761337
1209 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1210
1211 self.assertRaises(TypeError, t.strftime) # needs an arg
1212 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1213 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1214
1215 # test that unicode input is allowed (issue 2782)
1216 self.assertEqual(t.strftime("%m"), "03")
1217
1218 # A naive object replaces %z and %Z w/ empty strings.
1219 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1220
1221 #make sure that invalid format specifiers are handled correctly
1222 #self.assertRaises(ValueError, t.strftime, "%e")
1223 #self.assertRaises(ValueError, t.strftime, "%")
1224 #self.assertRaises(ValueError, t.strftime, "%#")
1225
1226 #oh well, some systems just ignore those invalid ones.
1227 #at least, excercise them to make sure that no crashes
1228 #are generated
1229 for f in ["%e", "%", "%#"]:
1230 try:
1231 t.strftime(f)
1232 except ValueError:
1233 pass
1234
1235 #check that this standard extension works
1236 t.strftime("%f")
1237
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001238 def test_format(self):
1239 dt = self.theclass(2007, 9, 10)
1240 self.assertEqual(dt.__format__(''), str(dt))
1241
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001242 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001243 dt.__format__(123)
1244
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001245 # check that a derived class's __str__() gets called
1246 class A(self.theclass):
1247 def __str__(self):
1248 return 'A'
1249 a = A(2007, 9, 10)
1250 self.assertEqual(a.__format__(''), 'A')
1251
1252 # check that a derived class's strftime gets called
1253 class B(self.theclass):
1254 def strftime(self, format_spec):
1255 return 'B'
1256 b = B(2007, 9, 10)
1257 self.assertEqual(b.__format__(''), str(dt))
1258
1259 for fmt in ["m:%m d:%d y:%y",
1260 "m:%m d:%d y:%y H:%H M:%M S:%S",
1261 "%z %Z",
1262 ]:
1263 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1264 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1265 self.assertEqual(b.__format__(fmt), 'B')
1266
1267 def test_resolution_info(self):
1268 # XXX: Should min and max respect subclassing?
1269 if issubclass(self.theclass, datetime):
1270 expected_class = datetime
1271 else:
1272 expected_class = date
1273 self.assertIsInstance(self.theclass.min, expected_class)
1274 self.assertIsInstance(self.theclass.max, expected_class)
1275 self.assertIsInstance(self.theclass.resolution, timedelta)
1276 self.assertTrue(self.theclass.max > self.theclass.min)
1277
1278 def test_extreme_timedelta(self):
1279 big = self.theclass.max - self.theclass.min
1280 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1281 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1282 # n == 315537897599999999 ~= 2**58.13
1283 justasbig = timedelta(0, 0, n)
1284 self.assertEqual(big, justasbig)
1285 self.assertEqual(self.theclass.min + big, self.theclass.max)
1286 self.assertEqual(self.theclass.max - big, self.theclass.min)
1287
1288 def test_timetuple(self):
1289 for i in range(7):
1290 # January 2, 1956 is a Monday (0)
1291 d = self.theclass(1956, 1, 2+i)
1292 t = d.timetuple()
1293 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1294 # February 1, 1956 is a Wednesday (2)
1295 d = self.theclass(1956, 2, 1+i)
1296 t = d.timetuple()
1297 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1298 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1299 # of the year.
1300 d = self.theclass(1956, 3, 1+i)
1301 t = d.timetuple()
1302 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1303 self.assertEqual(t.tm_year, 1956)
1304 self.assertEqual(t.tm_mon, 3)
1305 self.assertEqual(t.tm_mday, 1+i)
1306 self.assertEqual(t.tm_hour, 0)
1307 self.assertEqual(t.tm_min, 0)
1308 self.assertEqual(t.tm_sec, 0)
1309 self.assertEqual(t.tm_wday, (3+i)%7)
1310 self.assertEqual(t.tm_yday, 61+i)
1311 self.assertEqual(t.tm_isdst, -1)
1312
1313 def test_pickling(self):
1314 args = 6, 7, 23
1315 orig = self.theclass(*args)
1316 for pickler, unpickler, proto in pickle_choices:
1317 green = pickler.dumps(orig, proto)
1318 derived = unpickler.loads(green)
1319 self.assertEqual(orig, derived)
1320
1321 def test_compare(self):
1322 t1 = self.theclass(2, 3, 4)
1323 t2 = self.theclass(2, 3, 4)
1324 self.assertEqual(t1, t2)
1325 self.assertTrue(t1 <= t2)
1326 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001327 self.assertFalse(t1 != t2)
1328 self.assertFalse(t1 < t2)
1329 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001330
1331 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1332 t2 = self.theclass(*args) # this is larger than t1
1333 self.assertTrue(t1 < t2)
1334 self.assertTrue(t2 > t1)
1335 self.assertTrue(t1 <= t2)
1336 self.assertTrue(t2 >= t1)
1337 self.assertTrue(t1 != t2)
1338 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001339 self.assertFalse(t1 == t2)
1340 self.assertFalse(t2 == t1)
1341 self.assertFalse(t1 > t2)
1342 self.assertFalse(t2 < t1)
1343 self.assertFalse(t1 >= t2)
1344 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001345
1346 for badarg in OTHERSTUFF:
1347 self.assertEqual(t1 == badarg, False)
1348 self.assertEqual(t1 != badarg, True)
1349 self.assertEqual(badarg == t1, False)
1350 self.assertEqual(badarg != t1, True)
1351
1352 self.assertRaises(TypeError, lambda: t1 < badarg)
1353 self.assertRaises(TypeError, lambda: t1 > badarg)
1354 self.assertRaises(TypeError, lambda: t1 >= badarg)
1355 self.assertRaises(TypeError, lambda: badarg <= t1)
1356 self.assertRaises(TypeError, lambda: badarg < t1)
1357 self.assertRaises(TypeError, lambda: badarg > t1)
1358 self.assertRaises(TypeError, lambda: badarg >= t1)
1359
1360 def test_mixed_compare(self):
1361 our = self.theclass(2000, 4, 5)
1362
1363 # Our class can be compared for equality to other classes
1364 self.assertEqual(our == 1, False)
1365 self.assertEqual(1 == our, False)
1366 self.assertEqual(our != 1, True)
1367 self.assertEqual(1 != our, True)
1368
1369 # But the ordering is undefined
1370 self.assertRaises(TypeError, lambda: our < 1)
1371 self.assertRaises(TypeError, lambda: 1 < our)
1372
1373 # Repeat those tests with a different class
1374
1375 class SomeClass:
1376 pass
1377
1378 their = SomeClass()
1379 self.assertEqual(our == their, False)
1380 self.assertEqual(their == our, False)
1381 self.assertEqual(our != their, True)
1382 self.assertEqual(their != our, True)
1383 self.assertRaises(TypeError, lambda: our < their)
1384 self.assertRaises(TypeError, lambda: their < our)
1385
1386 # However, if the other class explicitly defines ordering
1387 # relative to our class, it is allowed to do so
1388
1389 class LargerThanAnything:
1390 def __lt__(self, other):
1391 return False
1392 def __le__(self, other):
1393 return isinstance(other, LargerThanAnything)
1394 def __eq__(self, other):
1395 return isinstance(other, LargerThanAnything)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001396 def __gt__(self, other):
1397 return not isinstance(other, LargerThanAnything)
1398 def __ge__(self, other):
1399 return True
1400
1401 their = LargerThanAnything()
1402 self.assertEqual(our == their, False)
1403 self.assertEqual(their == our, False)
1404 self.assertEqual(our != their, True)
1405 self.assertEqual(their != our, True)
1406 self.assertEqual(our < their, True)
1407 self.assertEqual(their < our, False)
1408
1409 def test_bool(self):
1410 # All dates are considered true.
1411 self.assertTrue(self.theclass.min)
1412 self.assertTrue(self.theclass.max)
1413
Alexander Belopolsky89da3492011-05-02 13:14:24 -04001414 def test_strftime_y2k(self):
1415 for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
Florent Xicluna49ce0682011-11-01 12:56:14 +01001416 d = self.theclass(y, 1, 1)
1417 # Issue 13305: For years < 1000, the value is not always
1418 # padded to 4 digits across platforms. The C standard
1419 # assumes year >= 1900, so it does not specify the number
1420 # of digits.
1421 if d.strftime("%Y") != '%04d' % y:
1422 # Year 42 returns '42', not padded
1423 self.assertEqual(d.strftime("%Y"), '%d' % y)
1424 # '0042' is obtained anyway
1425 self.assertEqual(d.strftime("%4Y"), '%04d' % y)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001426
1427 def test_replace(self):
1428 cls = self.theclass
1429 args = [1, 2, 3]
1430 base = cls(*args)
1431 self.assertEqual(base, base.replace())
1432
1433 i = 0
1434 for name, newval in (("year", 2),
1435 ("month", 3),
1436 ("day", 4)):
1437 newargs = args[:]
1438 newargs[i] = newval
1439 expected = cls(*newargs)
1440 got = base.replace(**{name: newval})
1441 self.assertEqual(expected, got)
1442 i += 1
1443
1444 # Out of bounds.
1445 base = cls(2000, 2, 29)
1446 self.assertRaises(ValueError, base.replace, year=2001)
1447
1448 def test_subclass_date(self):
1449
1450 class C(self.theclass):
1451 theAnswer = 42
1452
1453 def __new__(cls, *args, **kws):
1454 temp = kws.copy()
1455 extra = temp.pop('extra')
1456 result = self.theclass.__new__(cls, *args, **temp)
1457 result.extra = extra
1458 return result
1459
1460 def newmeth(self, start):
1461 return start + self.year + self.month
1462
1463 args = 2003, 4, 14
1464
1465 dt1 = self.theclass(*args)
1466 dt2 = C(*args, **{'extra': 7})
1467
1468 self.assertEqual(dt2.__class__, C)
1469 self.assertEqual(dt2.theAnswer, 42)
1470 self.assertEqual(dt2.extra, 7)
1471 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1472 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1473
1474 def test_pickling_subclass_date(self):
1475
1476 args = 6, 7, 23
1477 orig = SubclassDate(*args)
1478 for pickler, unpickler, proto in pickle_choices:
1479 green = pickler.dumps(orig, proto)
1480 derived = unpickler.loads(green)
1481 self.assertEqual(orig, derived)
1482
1483 def test_backdoor_resistance(self):
1484 # For fast unpickling, the constructor accepts a pickle byte string.
1485 # This is a low-overhead backdoor. A user can (by intent or
1486 # mistake) pass a string directly, which (if it's the right length)
1487 # will get treated like a pickle, and bypass the normal sanity
1488 # checks in the constructor. This can create insane objects.
1489 # The constructor doesn't want to burn the time to validate all
1490 # fields, but does check the month field. This stops, e.g.,
1491 # datetime.datetime('1995-03-25') from yielding an insane object.
1492 base = b'1995-03-25'
1493 if not issubclass(self.theclass, datetime):
1494 base = base[:4]
1495 for month_byte in b'9', b'\0', b'\r', b'\xff':
1496 self.assertRaises(TypeError, self.theclass,
1497 base[:2] + month_byte + base[3:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001498 if issubclass(self.theclass, datetime):
1499 # Good bytes, but bad tzinfo:
1500 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1501 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001502
1503 for ord_byte in range(1, 13):
1504 # This shouldn't blow up because of the month byte alone. If
1505 # the implementation changes to do more-careful checking, it may
1506 # blow up because other fields are insane.
1507 self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1508
1509#############################################################################
1510# datetime tests
1511
1512class SubclassDatetime(datetime):
1513 sub_var = 1
1514
1515class TestDateTime(TestDate):
1516
1517 theclass = datetime
1518
1519 def test_basic_attributes(self):
1520 dt = self.theclass(2002, 3, 1, 12, 0)
1521 self.assertEqual(dt.year, 2002)
1522 self.assertEqual(dt.month, 3)
1523 self.assertEqual(dt.day, 1)
1524 self.assertEqual(dt.hour, 12)
1525 self.assertEqual(dt.minute, 0)
1526 self.assertEqual(dt.second, 0)
1527 self.assertEqual(dt.microsecond, 0)
1528
1529 def test_basic_attributes_nonzero(self):
1530 # Make sure all attributes are non-zero so bugs in
1531 # bit-shifting access show up.
1532 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1533 self.assertEqual(dt.year, 2002)
1534 self.assertEqual(dt.month, 3)
1535 self.assertEqual(dt.day, 1)
1536 self.assertEqual(dt.hour, 12)
1537 self.assertEqual(dt.minute, 59)
1538 self.assertEqual(dt.second, 59)
1539 self.assertEqual(dt.microsecond, 8000)
1540
1541 def test_roundtrip(self):
1542 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1543 self.theclass.now()):
1544 # Verify dt -> string -> datetime identity.
1545 s = repr(dt)
1546 self.assertTrue(s.startswith('datetime.'))
1547 s = s[9:]
1548 dt2 = eval(s)
1549 self.assertEqual(dt, dt2)
1550
1551 # Verify identity via reconstructing from pieces.
1552 dt2 = self.theclass(dt.year, dt.month, dt.day,
1553 dt.hour, dt.minute, dt.second,
1554 dt.microsecond)
1555 self.assertEqual(dt, dt2)
1556
1557 def test_isoformat(self):
1558 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1559 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1560 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1561 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1562 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1563 # str is ISO format with the separator forced to a blank.
1564 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1565
1566 t = self.theclass(2, 3, 2)
1567 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1568 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1569 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1570 # str is ISO format with the separator forced to a blank.
1571 self.assertEqual(str(t), "0002-03-02 00:00:00")
1572
1573 def test_format(self):
1574 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1575 self.assertEqual(dt.__format__(''), str(dt))
1576
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02001577 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001578 dt.__format__(123)
1579
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001580 # check that a derived class's __str__() gets called
1581 class A(self.theclass):
1582 def __str__(self):
1583 return 'A'
1584 a = A(2007, 9, 10, 4, 5, 1, 123)
1585 self.assertEqual(a.__format__(''), 'A')
1586
1587 # check that a derived class's strftime gets called
1588 class B(self.theclass):
1589 def strftime(self, format_spec):
1590 return 'B'
1591 b = B(2007, 9, 10, 4, 5, 1, 123)
1592 self.assertEqual(b.__format__(''), str(dt))
1593
1594 for fmt in ["m:%m d:%d y:%y",
1595 "m:%m d:%d y:%y H:%H M:%M S:%S",
1596 "%z %Z",
1597 ]:
1598 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1599 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1600 self.assertEqual(b.__format__(fmt), 'B')
1601
1602 def test_more_ctime(self):
1603 # Test fields that TestDate doesn't touch.
1604 import time
1605
1606 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1607 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1608 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1609 # out. The difference is that t.ctime() produces " 2" for the day,
1610 # but platform ctime() produces "02" for the day. According to
1611 # C99, t.ctime() is correct here.
1612 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1613
1614 # So test a case where that difference doesn't matter.
1615 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1616 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1617
1618 def test_tz_independent_comparing(self):
1619 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1620 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1621 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1622 self.assertEqual(dt1, dt3)
1623 self.assertTrue(dt2 > dt3)
1624
1625 # Make sure comparison doesn't forget microseconds, and isn't done
1626 # via comparing a float timestamp (an IEEE double doesn't have enough
1627 # precision to span microsecond resolution across years 1 thru 9999,
1628 # so comparing via timestamp necessarily calls some distinct values
1629 # equal).
1630 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1631 us = timedelta(microseconds=1)
1632 dt2 = dt1 + us
1633 self.assertEqual(dt2 - dt1, us)
1634 self.assertTrue(dt1 < dt2)
1635
1636 def test_strftime_with_bad_tzname_replace(self):
1637 # verify ok if tzinfo.tzname().replace() returns a non-string
1638 class MyTzInfo(FixedOffset):
1639 def tzname(self, dt):
1640 class MyStr(str):
1641 def replace(self, *args):
1642 return None
1643 return MyStr('name')
1644 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1645 self.assertRaises(TypeError, t.strftime, '%Z')
1646
1647 def test_bad_constructor_arguments(self):
1648 # bad years
1649 self.theclass(MINYEAR, 1, 1) # no exception
1650 self.theclass(MAXYEAR, 1, 1) # no exception
1651 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1652 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1653 # bad months
1654 self.theclass(2000, 1, 1) # no exception
1655 self.theclass(2000, 12, 1) # no exception
1656 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1657 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1658 # bad days
1659 self.theclass(2000, 2, 29) # no exception
1660 self.theclass(2004, 2, 29) # no exception
1661 self.theclass(2400, 2, 29) # no exception
1662 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1663 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1664 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1665 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1666 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1667 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1668 # bad hours
1669 self.theclass(2000, 1, 31, 0) # no exception
1670 self.theclass(2000, 1, 31, 23) # no exception
1671 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1672 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1673 # bad minutes
1674 self.theclass(2000, 1, 31, 23, 0) # no exception
1675 self.theclass(2000, 1, 31, 23, 59) # no exception
1676 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1677 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1678 # bad seconds
1679 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1680 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1681 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1682 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1683 # bad microseconds
1684 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1685 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1686 self.assertRaises(ValueError, self.theclass,
1687 2000, 1, 31, 23, 59, 59, -1)
1688 self.assertRaises(ValueError, self.theclass,
1689 2000, 1, 31, 23, 59, 59,
1690 1000000)
1691
1692 def test_hash_equality(self):
1693 d = self.theclass(2000, 12, 31, 23, 30, 17)
1694 e = self.theclass(2000, 12, 31, 23, 30, 17)
1695 self.assertEqual(d, e)
1696 self.assertEqual(hash(d), hash(e))
1697
1698 dic = {d: 1}
1699 dic[e] = 2
1700 self.assertEqual(len(dic), 1)
1701 self.assertEqual(dic[d], 2)
1702 self.assertEqual(dic[e], 2)
1703
1704 d = self.theclass(2001, 1, 1, 0, 5, 17)
1705 e = self.theclass(2001, 1, 1, 0, 5, 17)
1706 self.assertEqual(d, e)
1707 self.assertEqual(hash(d), hash(e))
1708
1709 dic = {d: 1}
1710 dic[e] = 2
1711 self.assertEqual(len(dic), 1)
1712 self.assertEqual(dic[d], 2)
1713 self.assertEqual(dic[e], 2)
1714
1715 def test_computations(self):
1716 a = self.theclass(2002, 1, 31)
1717 b = self.theclass(1956, 1, 31)
1718 diff = a-b
1719 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1720 self.assertEqual(diff.seconds, 0)
1721 self.assertEqual(diff.microseconds, 0)
1722 a = self.theclass(2002, 3, 2, 17, 6)
1723 millisec = timedelta(0, 0, 1000)
1724 hour = timedelta(0, 3600)
1725 day = timedelta(1)
1726 week = timedelta(7)
1727 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1728 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1729 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1730 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1731 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1732 self.assertEqual(a - hour, a + -hour)
1733 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1734 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1735 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1736 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1737 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1738 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1739 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1740 self.assertEqual((a + week) - a, week)
1741 self.assertEqual((a + day) - a, day)
1742 self.assertEqual((a + hour) - a, hour)
1743 self.assertEqual((a + millisec) - a, millisec)
1744 self.assertEqual((a - week) - a, -week)
1745 self.assertEqual((a - day) - a, -day)
1746 self.assertEqual((a - hour) - a, -hour)
1747 self.assertEqual((a - millisec) - a, -millisec)
1748 self.assertEqual(a - (a + week), -week)
1749 self.assertEqual(a - (a + day), -day)
1750 self.assertEqual(a - (a + hour), -hour)
1751 self.assertEqual(a - (a + millisec), -millisec)
1752 self.assertEqual(a - (a - week), week)
1753 self.assertEqual(a - (a - day), day)
1754 self.assertEqual(a - (a - hour), hour)
1755 self.assertEqual(a - (a - millisec), millisec)
1756 self.assertEqual(a + (week + day + hour + millisec),
1757 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1758 self.assertEqual(a + (week + day + hour + millisec),
1759 (((a + week) + day) + hour) + millisec)
1760 self.assertEqual(a - (week + day + hour + millisec),
1761 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1762 self.assertEqual(a - (week + day + hour + millisec),
1763 (((a - week) - day) - hour) - millisec)
1764 # Add/sub ints or floats should be illegal
1765 for i in 1, 1.0:
1766 self.assertRaises(TypeError, lambda: a+i)
1767 self.assertRaises(TypeError, lambda: a-i)
1768 self.assertRaises(TypeError, lambda: i+a)
1769 self.assertRaises(TypeError, lambda: i-a)
1770
1771 # delta - datetime is senseless.
1772 self.assertRaises(TypeError, lambda: day - a)
1773 # mixing datetime and (delta or datetime) via * or // is senseless
1774 self.assertRaises(TypeError, lambda: day * a)
1775 self.assertRaises(TypeError, lambda: a * day)
1776 self.assertRaises(TypeError, lambda: day // a)
1777 self.assertRaises(TypeError, lambda: a // day)
1778 self.assertRaises(TypeError, lambda: a * a)
1779 self.assertRaises(TypeError, lambda: a // a)
1780 # datetime + datetime is senseless
1781 self.assertRaises(TypeError, lambda: a + a)
1782
1783 def test_pickling(self):
1784 args = 6, 7, 23, 20, 59, 1, 64**2
1785 orig = self.theclass(*args)
1786 for pickler, unpickler, proto in pickle_choices:
1787 green = pickler.dumps(orig, proto)
1788 derived = unpickler.loads(green)
1789 self.assertEqual(orig, derived)
1790
1791 def test_more_pickling(self):
1792 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001793 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1794 s = pickle.dumps(a, proto)
1795 b = pickle.loads(s)
1796 self.assertEqual(b.year, 2003)
1797 self.assertEqual(b.month, 2)
1798 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001799
1800 def test_pickling_subclass_datetime(self):
1801 args = 6, 7, 23, 20, 59, 1, 64**2
1802 orig = SubclassDatetime(*args)
1803 for pickler, unpickler, proto in pickle_choices:
1804 green = pickler.dumps(orig, proto)
1805 derived = unpickler.loads(green)
1806 self.assertEqual(orig, derived)
1807
1808 def test_more_compare(self):
1809 # The test_compare() inherited from TestDate covers the error cases.
1810 # We just want to test lexicographic ordering on the members datetime
1811 # has that date lacks.
1812 args = [2000, 11, 29, 20, 58, 16, 999998]
1813 t1 = self.theclass(*args)
1814 t2 = self.theclass(*args)
1815 self.assertEqual(t1, t2)
1816 self.assertTrue(t1 <= t2)
1817 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001818 self.assertFalse(t1 != t2)
1819 self.assertFalse(t1 < t2)
1820 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001821
1822 for i in range(len(args)):
1823 newargs = args[:]
1824 newargs[i] = args[i] + 1
1825 t2 = self.theclass(*newargs) # this is larger than t1
1826 self.assertTrue(t1 < t2)
1827 self.assertTrue(t2 > t1)
1828 self.assertTrue(t1 <= t2)
1829 self.assertTrue(t2 >= t1)
1830 self.assertTrue(t1 != t2)
1831 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001832 self.assertFalse(t1 == t2)
1833 self.assertFalse(t2 == t1)
1834 self.assertFalse(t1 > t2)
1835 self.assertFalse(t2 < t1)
1836 self.assertFalse(t1 >= t2)
1837 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001838
1839
1840 # A helper for timestamp constructor tests.
1841 def verify_field_equality(self, expected, got):
1842 self.assertEqual(expected.tm_year, got.year)
1843 self.assertEqual(expected.tm_mon, got.month)
1844 self.assertEqual(expected.tm_mday, got.day)
1845 self.assertEqual(expected.tm_hour, got.hour)
1846 self.assertEqual(expected.tm_min, got.minute)
1847 self.assertEqual(expected.tm_sec, got.second)
1848
1849 def test_fromtimestamp(self):
1850 import time
1851
1852 ts = time.time()
1853 expected = time.localtime(ts)
1854 got = self.theclass.fromtimestamp(ts)
1855 self.verify_field_equality(expected, got)
1856
1857 def test_utcfromtimestamp(self):
1858 import time
1859
1860 ts = time.time()
1861 expected = time.gmtime(ts)
1862 got = self.theclass.utcfromtimestamp(ts)
1863 self.verify_field_equality(expected, got)
1864
Alexander Belopolskya4415142012-06-08 12:33:09 -04001865 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1866 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1867 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1868 def test_timestamp_naive(self):
1869 t = self.theclass(1970, 1, 1)
1870 self.assertEqual(t.timestamp(), 18000.0)
1871 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1872 self.assertEqual(t.timestamp(),
1873 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001874 # Missing hour may produce platform-dependent result
Alexander Belopolskya4415142012-06-08 12:33:09 -04001875 t = self.theclass(2012, 3, 11, 2, 30)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001876 self.assertIn(self.theclass.fromtimestamp(t.timestamp()),
Alexander Belopolskyf6f56182012-06-08 13:00:27 -04001877 [t - timedelta(hours=1), t + timedelta(hours=1)])
Alexander Belopolskya4415142012-06-08 12:33:09 -04001878 # Ambiguous hour defaults to DST
1879 t = self.theclass(2012, 11, 4, 1, 30)
1880 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1881
1882 # Timestamp may raise an overflow error on some platforms
1883 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
1884 try:
1885 s = t.timestamp()
1886 except OverflowError:
1887 pass
1888 else:
1889 self.assertEqual(self.theclass.fromtimestamp(s), t)
1890
1891 def test_timestamp_aware(self):
1892 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1893 self.assertEqual(t.timestamp(), 0.0)
1894 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1895 self.assertEqual(t.timestamp(),
1896 3600 + 2*60 + 3 + 4*1e-6)
1897 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1898 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1899 self.assertEqual(t.timestamp(),
1900 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001901
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001902 def test_microsecond_rounding(self):
Alexander Belopolsky3e62f782010-09-21 16:30:56 +00001903 for fts in [self.theclass.fromtimestamp,
1904 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001905 zero = fts(0)
1906 self.assertEqual(zero.second, 0)
1907 self.assertEqual(zero.microsecond, 0)
Victor Stinner511491a2015-09-18 14:42:05 +02001908 one = fts(1e-6)
Victor Stinner8050ca92012-03-14 00:17:05 +01001909 try:
1910 minus_one = fts(-1e-6)
1911 except OSError:
1912 # localtime(-1) and gmtime(-1) is not supported on Windows
1913 pass
1914 else:
1915 self.assertEqual(minus_one.second, 59)
1916 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001917
Victor Stinner8050ca92012-03-14 00:17:05 +01001918 t = fts(-1e-8)
Victor Stinner511491a2015-09-18 14:42:05 +02001919 self.assertEqual(t, zero)
Victor Stinner8050ca92012-03-14 00:17:05 +01001920 t = fts(-9e-7)
1921 self.assertEqual(t, minus_one)
1922 t = fts(-1e-7)
Victor Stinner511491a2015-09-18 14:42:05 +02001923 self.assertEqual(t, zero)
1924 t = fts(-1/2**7)
1925 self.assertEqual(t.second, 59)
1926 self.assertEqual(t.microsecond, 992188)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001927
1928 t = fts(1e-7)
1929 self.assertEqual(t, zero)
1930 t = fts(9e-7)
Victor Stinner511491a2015-09-18 14:42:05 +02001931 self.assertEqual(t, one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001932 t = fts(0.99999949)
1933 self.assertEqual(t.second, 0)
1934 self.assertEqual(t.microsecond, 999999)
1935 t = fts(0.9999999)
Victor Stinner511491a2015-09-18 14:42:05 +02001936 self.assertEqual(t.second, 1)
1937 self.assertEqual(t.microsecond, 0)
1938 t = fts(1/2**7)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001939 self.assertEqual(t.second, 0)
Victor Stinner511491a2015-09-18 14:42:05 +02001940 self.assertEqual(t.microsecond, 7812)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001941
1942 def test_insane_fromtimestamp(self):
1943 # It's possible that some platform maps time_t to double,
1944 # and that this test will fail there. This test should
1945 # exempt such platforms (provided they return reasonable
1946 # results!).
1947 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001948 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001949 insane)
1950
1951 def test_insane_utcfromtimestamp(self):
1952 # It's possible that some platform maps time_t to double,
1953 # and that this test will fail there. This test should
1954 # exempt such platforms (provided they return reasonable
1955 # results!).
1956 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001957 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001958 insane)
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04001959
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001960 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1961 def test_negative_float_fromtimestamp(self):
1962 # The result is tz-dependent; at least test that this doesn't
1963 # fail (like it did before bug 1646728 was fixed).
1964 self.theclass.fromtimestamp(-1.05)
1965
1966 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1967 def test_negative_float_utcfromtimestamp(self):
1968 d = self.theclass.utcfromtimestamp(-1.05)
1969 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1970
1971 def test_utcnow(self):
1972 import time
1973
1974 # Call it a success if utcnow() and utcfromtimestamp() are within
1975 # a second of each other.
1976 tolerance = timedelta(seconds=1)
1977 for dummy in range(3):
1978 from_now = self.theclass.utcnow()
1979 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1980 if abs(from_timestamp - from_now) <= tolerance:
1981 break
1982 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001983 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001984
1985 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001986 string = '2004-12-01 13:02:47.197'
1987 format = '%Y-%m-%d %H:%M:%S.%f'
1988 expected = _strptime._strptime_datetime(self.theclass, string, format)
1989 got = self.theclass.strptime(string, format)
1990 self.assertEqual(expected, got)
1991 self.assertIs(type(expected), self.theclass)
1992 self.assertIs(type(got), self.theclass)
1993
1994 strptime = self.theclass.strptime
1995 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1996 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1997 # Only local timezone and UTC are supported
1998 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1999 (-_time.timezone, _time.tzname[0])):
2000 if tzseconds < 0:
2001 sign = '-'
2002 seconds = -tzseconds
2003 else:
2004 sign ='+'
2005 seconds = tzseconds
2006 hours, minutes = divmod(seconds//60, 60)
2007 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
2008 dt = strptime(dtstr, "%z %Z")
2009 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2010 self.assertEqual(dt.tzname(), tzname)
2011 # Can produce inconsistent datetime
2012 dtstr, fmt = "+1234 UTC", "%z %Z"
2013 dt = strptime(dtstr, fmt)
2014 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2015 self.assertEqual(dt.tzname(), 'UTC')
2016 # yet will roundtrip
2017 self.assertEqual(dt.strftime(fmt), dtstr)
2018
2019 # Produce naive datetime if no %z is provided
2020 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2021
2022 with self.assertRaises(ValueError): strptime("-2400", "%z")
2023 with self.assertRaises(ValueError): strptime("-000", "%z")
2024
2025 def test_more_timetuple(self):
2026 # This tests fields beyond those tested by the TestDate.test_timetuple.
2027 t = self.theclass(2004, 12, 31, 6, 22, 33)
2028 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2029 self.assertEqual(t.timetuple(),
2030 (t.year, t.month, t.day,
2031 t.hour, t.minute, t.second,
2032 t.weekday(),
2033 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2034 -1))
2035 tt = t.timetuple()
2036 self.assertEqual(tt.tm_year, t.year)
2037 self.assertEqual(tt.tm_mon, t.month)
2038 self.assertEqual(tt.tm_mday, t.day)
2039 self.assertEqual(tt.tm_hour, t.hour)
2040 self.assertEqual(tt.tm_min, t.minute)
2041 self.assertEqual(tt.tm_sec, t.second)
2042 self.assertEqual(tt.tm_wday, t.weekday())
2043 self.assertEqual(tt.tm_yday, t.toordinal() -
2044 date(t.year, 1, 1).toordinal() + 1)
2045 self.assertEqual(tt.tm_isdst, -1)
2046
2047 def test_more_strftime(self):
2048 # This tests fields beyond those tested by the TestDate.test_strftime.
2049 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2050 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2051 "12 31 04 000047 33 22 06 366")
2052
2053 def test_extract(self):
2054 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2055 self.assertEqual(dt.date(), date(2002, 3, 4))
2056 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2057
2058 def test_combine(self):
2059 d = date(2002, 3, 4)
2060 t = time(18, 45, 3, 1234)
2061 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2062 combine = self.theclass.combine
2063 dt = combine(d, t)
2064 self.assertEqual(dt, expected)
2065
2066 dt = combine(time=t, date=d)
2067 self.assertEqual(dt, expected)
2068
2069 self.assertEqual(d, dt.date())
2070 self.assertEqual(t, dt.time())
2071 self.assertEqual(dt, combine(dt.date(), dt.time()))
2072
2073 self.assertRaises(TypeError, combine) # need an arg
2074 self.assertRaises(TypeError, combine, d) # need two args
2075 self.assertRaises(TypeError, combine, t, d) # args reversed
2076 self.assertRaises(TypeError, combine, d, t, 1) # too many args
2077 self.assertRaises(TypeError, combine, "date", "time") # wrong types
2078 self.assertRaises(TypeError, combine, d, "time") # wrong type
2079 self.assertRaises(TypeError, combine, "date", t) # wrong type
2080
2081 def test_replace(self):
2082 cls = self.theclass
2083 args = [1, 2, 3, 4, 5, 6, 7]
2084 base = cls(*args)
2085 self.assertEqual(base, base.replace())
2086
2087 i = 0
2088 for name, newval in (("year", 2),
2089 ("month", 3),
2090 ("day", 4),
2091 ("hour", 5),
2092 ("minute", 6),
2093 ("second", 7),
2094 ("microsecond", 8)):
2095 newargs = args[:]
2096 newargs[i] = newval
2097 expected = cls(*newargs)
2098 got = base.replace(**{name: newval})
2099 self.assertEqual(expected, got)
2100 i += 1
2101
2102 # Out of bounds.
2103 base = cls(2000, 2, 29)
2104 self.assertRaises(ValueError, base.replace, year=2001)
2105
2106 def test_astimezone(self):
2107 # Pretty boring! The TZ test is more interesting here. astimezone()
2108 # simply can't be applied to a naive object.
2109 dt = self.theclass.now()
2110 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04002111 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002112 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2113 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2114 self.assertRaises(ValueError, dt.astimezone, f) # naive
2115 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
2116
2117 class Bogus(tzinfo):
2118 def utcoffset(self, dt): return None
2119 def dst(self, dt): return timedelta(0)
2120 bog = Bogus()
2121 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2122 self.assertRaises(ValueError,
2123 dt.replace(tzinfo=bog).astimezone, f)
2124
2125 class AlsoBogus(tzinfo):
2126 def utcoffset(self, dt): return timedelta(0)
2127 def dst(self, dt): return None
2128 alsobog = AlsoBogus()
2129 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2130
2131 def test_subclass_datetime(self):
2132
2133 class C(self.theclass):
2134 theAnswer = 42
2135
2136 def __new__(cls, *args, **kws):
2137 temp = kws.copy()
2138 extra = temp.pop('extra')
2139 result = self.theclass.__new__(cls, *args, **temp)
2140 result.extra = extra
2141 return result
2142
2143 def newmeth(self, start):
2144 return start + self.year + self.month + self.second
2145
2146 args = 2003, 4, 14, 12, 13, 41
2147
2148 dt1 = self.theclass(*args)
2149 dt2 = C(*args, **{'extra': 7})
2150
2151 self.assertEqual(dt2.__class__, C)
2152 self.assertEqual(dt2.theAnswer, 42)
2153 self.assertEqual(dt2.extra, 7)
2154 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2155 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2156 dt1.second - 7)
2157
2158class TestSubclassDateTime(TestDateTime):
2159 theclass = SubclassDatetime
2160 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002161 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002162 def test_roundtrip(self):
2163 pass
2164
2165class SubclassTime(time):
2166 sub_var = 1
2167
2168class TestTime(HarmlessMixedComparison, unittest.TestCase):
2169
2170 theclass = time
2171
2172 def test_basic_attributes(self):
2173 t = self.theclass(12, 0)
2174 self.assertEqual(t.hour, 12)
2175 self.assertEqual(t.minute, 0)
2176 self.assertEqual(t.second, 0)
2177 self.assertEqual(t.microsecond, 0)
2178
2179 def test_basic_attributes_nonzero(self):
2180 # Make sure all attributes are non-zero so bugs in
2181 # bit-shifting access show up.
2182 t = self.theclass(12, 59, 59, 8000)
2183 self.assertEqual(t.hour, 12)
2184 self.assertEqual(t.minute, 59)
2185 self.assertEqual(t.second, 59)
2186 self.assertEqual(t.microsecond, 8000)
2187
2188 def test_roundtrip(self):
2189 t = self.theclass(1, 2, 3, 4)
2190
2191 # Verify t -> string -> time identity.
2192 s = repr(t)
2193 self.assertTrue(s.startswith('datetime.'))
2194 s = s[9:]
2195 t2 = eval(s)
2196 self.assertEqual(t, t2)
2197
2198 # Verify identity via reconstructing from pieces.
2199 t2 = self.theclass(t.hour, t.minute, t.second,
2200 t.microsecond)
2201 self.assertEqual(t, t2)
2202
2203 def test_comparing(self):
2204 args = [1, 2, 3, 4]
2205 t1 = self.theclass(*args)
2206 t2 = self.theclass(*args)
2207 self.assertEqual(t1, t2)
2208 self.assertTrue(t1 <= t2)
2209 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002210 self.assertFalse(t1 != t2)
2211 self.assertFalse(t1 < t2)
2212 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002213
2214 for i in range(len(args)):
2215 newargs = args[:]
2216 newargs[i] = args[i] + 1
2217 t2 = self.theclass(*newargs) # this is larger than t1
2218 self.assertTrue(t1 < t2)
2219 self.assertTrue(t2 > t1)
2220 self.assertTrue(t1 <= t2)
2221 self.assertTrue(t2 >= t1)
2222 self.assertTrue(t1 != t2)
2223 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002224 self.assertFalse(t1 == t2)
2225 self.assertFalse(t2 == t1)
2226 self.assertFalse(t1 > t2)
2227 self.assertFalse(t2 < t1)
2228 self.assertFalse(t1 >= t2)
2229 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002230
2231 for badarg in OTHERSTUFF:
2232 self.assertEqual(t1 == badarg, False)
2233 self.assertEqual(t1 != badarg, True)
2234 self.assertEqual(badarg == t1, False)
2235 self.assertEqual(badarg != t1, True)
2236
2237 self.assertRaises(TypeError, lambda: t1 <= badarg)
2238 self.assertRaises(TypeError, lambda: t1 < badarg)
2239 self.assertRaises(TypeError, lambda: t1 > badarg)
2240 self.assertRaises(TypeError, lambda: t1 >= badarg)
2241 self.assertRaises(TypeError, lambda: badarg <= t1)
2242 self.assertRaises(TypeError, lambda: badarg < t1)
2243 self.assertRaises(TypeError, lambda: badarg > t1)
2244 self.assertRaises(TypeError, lambda: badarg >= t1)
2245
2246 def test_bad_constructor_arguments(self):
2247 # bad hours
2248 self.theclass(0, 0) # no exception
2249 self.theclass(23, 0) # no exception
2250 self.assertRaises(ValueError, self.theclass, -1, 0)
2251 self.assertRaises(ValueError, self.theclass, 24, 0)
2252 # bad minutes
2253 self.theclass(23, 0) # no exception
2254 self.theclass(23, 59) # no exception
2255 self.assertRaises(ValueError, self.theclass, 23, -1)
2256 self.assertRaises(ValueError, self.theclass, 23, 60)
2257 # bad seconds
2258 self.theclass(23, 59, 0) # no exception
2259 self.theclass(23, 59, 59) # no exception
2260 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2261 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2262 # bad microseconds
2263 self.theclass(23, 59, 59, 0) # no exception
2264 self.theclass(23, 59, 59, 999999) # no exception
2265 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2266 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2267
2268 def test_hash_equality(self):
2269 d = self.theclass(23, 30, 17)
2270 e = self.theclass(23, 30, 17)
2271 self.assertEqual(d, e)
2272 self.assertEqual(hash(d), hash(e))
2273
2274 dic = {d: 1}
2275 dic[e] = 2
2276 self.assertEqual(len(dic), 1)
2277 self.assertEqual(dic[d], 2)
2278 self.assertEqual(dic[e], 2)
2279
2280 d = self.theclass(0, 5, 17)
2281 e = self.theclass(0, 5, 17)
2282 self.assertEqual(d, e)
2283 self.assertEqual(hash(d), hash(e))
2284
2285 dic = {d: 1}
2286 dic[e] = 2
2287 self.assertEqual(len(dic), 1)
2288 self.assertEqual(dic[d], 2)
2289 self.assertEqual(dic[e], 2)
2290
2291 def test_isoformat(self):
2292 t = self.theclass(4, 5, 1, 123)
2293 self.assertEqual(t.isoformat(), "04:05:01.000123")
2294 self.assertEqual(t.isoformat(), str(t))
2295
2296 t = self.theclass()
2297 self.assertEqual(t.isoformat(), "00:00:00")
2298 self.assertEqual(t.isoformat(), str(t))
2299
2300 t = self.theclass(microsecond=1)
2301 self.assertEqual(t.isoformat(), "00:00:00.000001")
2302 self.assertEqual(t.isoformat(), str(t))
2303
2304 t = self.theclass(microsecond=10)
2305 self.assertEqual(t.isoformat(), "00:00:00.000010")
2306 self.assertEqual(t.isoformat(), str(t))
2307
2308 t = self.theclass(microsecond=100)
2309 self.assertEqual(t.isoformat(), "00:00:00.000100")
2310 self.assertEqual(t.isoformat(), str(t))
2311
2312 t = self.theclass(microsecond=1000)
2313 self.assertEqual(t.isoformat(), "00:00:00.001000")
2314 self.assertEqual(t.isoformat(), str(t))
2315
2316 t = self.theclass(microsecond=10000)
2317 self.assertEqual(t.isoformat(), "00:00:00.010000")
2318 self.assertEqual(t.isoformat(), str(t))
2319
2320 t = self.theclass(microsecond=100000)
2321 self.assertEqual(t.isoformat(), "00:00:00.100000")
2322 self.assertEqual(t.isoformat(), str(t))
2323
2324 def test_1653736(self):
2325 # verify it doesn't accept extra keyword arguments
2326 t = self.theclass(second=1)
2327 self.assertRaises(TypeError, t.isoformat, foo=3)
2328
2329 def test_strftime(self):
2330 t = self.theclass(1, 2, 3, 4)
2331 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2332 # A naive object replaces %z and %Z with empty strings.
2333 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2334
2335 def test_format(self):
2336 t = self.theclass(1, 2, 3, 4)
2337 self.assertEqual(t.__format__(''), str(t))
2338
Serhiy Storchaka0c0d5372016-02-08 09:25:53 +02002339 with self.assertRaisesRegex(TypeError, 'must be str, not int'):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002340 t.__format__(123)
2341
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002342 # check that a derived class's __str__() gets called
2343 class A(self.theclass):
2344 def __str__(self):
2345 return 'A'
2346 a = A(1, 2, 3, 4)
2347 self.assertEqual(a.__format__(''), 'A')
2348
2349 # check that a derived class's strftime gets called
2350 class B(self.theclass):
2351 def strftime(self, format_spec):
2352 return 'B'
2353 b = B(1, 2, 3, 4)
2354 self.assertEqual(b.__format__(''), str(t))
2355
2356 for fmt in ['%H %M %S',
2357 ]:
2358 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2359 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2360 self.assertEqual(b.__format__(fmt), 'B')
2361
2362 def test_str(self):
2363 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2364 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2365 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2366 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2367 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2368
2369 def test_repr(self):
2370 name = 'datetime.' + self.theclass.__name__
2371 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2372 "%s(1, 2, 3, 4)" % name)
2373 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2374 "%s(10, 2, 3, 4000)" % name)
2375 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2376 "%s(0, 2, 3, 400000)" % name)
2377 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2378 "%s(12, 2, 3)" % name)
2379 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2380 "%s(23, 15)" % name)
2381
2382 def test_resolution_info(self):
2383 self.assertIsInstance(self.theclass.min, self.theclass)
2384 self.assertIsInstance(self.theclass.max, self.theclass)
2385 self.assertIsInstance(self.theclass.resolution, timedelta)
2386 self.assertTrue(self.theclass.max > self.theclass.min)
2387
2388 def test_pickling(self):
2389 args = 20, 59, 16, 64**2
2390 orig = self.theclass(*args)
2391 for pickler, unpickler, proto in pickle_choices:
2392 green = pickler.dumps(orig, proto)
2393 derived = unpickler.loads(green)
2394 self.assertEqual(orig, derived)
2395
2396 def test_pickling_subclass_time(self):
2397 args = 20, 59, 16, 64**2
2398 orig = SubclassTime(*args)
2399 for pickler, unpickler, proto in pickle_choices:
2400 green = pickler.dumps(orig, proto)
2401 derived = unpickler.loads(green)
2402 self.assertEqual(orig, derived)
2403
2404 def test_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002405 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002406 cls = self.theclass
2407 self.assertTrue(cls(1))
2408 self.assertTrue(cls(0, 1))
2409 self.assertTrue(cls(0, 0, 1))
2410 self.assertTrue(cls(0, 0, 0, 1))
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002411 self.assertTrue(cls(0))
2412 self.assertTrue(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002413
2414 def test_replace(self):
2415 cls = self.theclass
2416 args = [1, 2, 3, 4]
2417 base = cls(*args)
2418 self.assertEqual(base, base.replace())
2419
2420 i = 0
2421 for name, newval in (("hour", 5),
2422 ("minute", 6),
2423 ("second", 7),
2424 ("microsecond", 8)):
2425 newargs = args[:]
2426 newargs[i] = newval
2427 expected = cls(*newargs)
2428 got = base.replace(**{name: newval})
2429 self.assertEqual(expected, got)
2430 i += 1
2431
2432 # Out of bounds.
2433 base = cls(1)
2434 self.assertRaises(ValueError, base.replace, hour=24)
2435 self.assertRaises(ValueError, base.replace, minute=-1)
2436 self.assertRaises(ValueError, base.replace, second=100)
2437 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2438
2439 def test_subclass_time(self):
2440
2441 class C(self.theclass):
2442 theAnswer = 42
2443
2444 def __new__(cls, *args, **kws):
2445 temp = kws.copy()
2446 extra = temp.pop('extra')
2447 result = self.theclass.__new__(cls, *args, **temp)
2448 result.extra = extra
2449 return result
2450
2451 def newmeth(self, start):
2452 return start + self.hour + self.second
2453
2454 args = 4, 5, 6
2455
2456 dt1 = self.theclass(*args)
2457 dt2 = C(*args, **{'extra': 7})
2458
2459 self.assertEqual(dt2.__class__, C)
2460 self.assertEqual(dt2.theAnswer, 42)
2461 self.assertEqual(dt2.extra, 7)
2462 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2463 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2464
2465 def test_backdoor_resistance(self):
2466 # see TestDate.test_backdoor_resistance().
2467 base = '2:59.0'
2468 for hour_byte in ' ', '9', chr(24), '\xff':
2469 self.assertRaises(TypeError, self.theclass,
2470 hour_byte + base[1:])
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002471 # Good bytes, but bad tzinfo:
2472 with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2473 self.theclass(bytes([1] * len(base)), 'EST')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002474
2475# A mixin for classes with a tzinfo= argument. Subclasses must define
2476# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2477# must be legit (which is true for time and datetime).
2478class TZInfoBase:
2479
2480 def test_argument_passing(self):
2481 cls = self.theclass
2482 # A datetime passes itself on, a time passes None.
2483 class introspective(tzinfo):
2484 def tzname(self, dt): return dt and "real" or "none"
2485 def utcoffset(self, dt):
2486 return timedelta(minutes = dt and 42 or -42)
2487 dst = utcoffset
2488
2489 obj = cls(1, 2, 3, tzinfo=introspective())
2490
2491 expected = cls is time and "none" or "real"
2492 self.assertEqual(obj.tzname(), expected)
2493
2494 expected = timedelta(minutes=(cls is time and -42 or 42))
2495 self.assertEqual(obj.utcoffset(), expected)
2496 self.assertEqual(obj.dst(), expected)
2497
2498 def test_bad_tzinfo_classes(self):
2499 cls = self.theclass
2500 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2501
2502 class NiceTry(object):
2503 def __init__(self): pass
2504 def utcoffset(self, dt): pass
2505 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2506
2507 class BetterTry(tzinfo):
2508 def __init__(self): pass
2509 def utcoffset(self, dt): pass
2510 b = BetterTry()
2511 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002512 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002513
2514 def test_utc_offset_out_of_bounds(self):
2515 class Edgy(tzinfo):
2516 def __init__(self, offset):
2517 self.offset = timedelta(minutes=offset)
2518 def utcoffset(self, dt):
2519 return self.offset
2520
2521 cls = self.theclass
2522 for offset, legit in ((-1440, False),
2523 (-1439, True),
2524 (1439, True),
2525 (1440, False)):
2526 if cls is time:
2527 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2528 elif cls is datetime:
2529 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2530 else:
2531 assert 0, "impossible"
2532 if legit:
2533 aofs = abs(offset)
2534 h, m = divmod(aofs, 60)
2535 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2536 if isinstance(t, datetime):
2537 t = t.timetz()
2538 self.assertEqual(str(t), "01:02:03" + tag)
2539 else:
2540 self.assertRaises(ValueError, str, t)
2541
2542 def test_tzinfo_classes(self):
2543 cls = self.theclass
2544 class C1(tzinfo):
2545 def utcoffset(self, dt): return None
2546 def dst(self, dt): return None
2547 def tzname(self, dt): return None
2548 for t in (cls(1, 1, 1),
2549 cls(1, 1, 1, tzinfo=None),
2550 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002551 self.assertIsNone(t.utcoffset())
2552 self.assertIsNone(t.dst())
2553 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002554
2555 class C3(tzinfo):
2556 def utcoffset(self, dt): return timedelta(minutes=-1439)
2557 def dst(self, dt): return timedelta(minutes=1439)
2558 def tzname(self, dt): return "aname"
2559 t = cls(1, 1, 1, tzinfo=C3())
2560 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2561 self.assertEqual(t.dst(), timedelta(minutes=1439))
2562 self.assertEqual(t.tzname(), "aname")
2563
2564 # Wrong types.
2565 class C4(tzinfo):
2566 def utcoffset(self, dt): return "aname"
2567 def dst(self, dt): return 7
2568 def tzname(self, dt): return 0
2569 t = cls(1, 1, 1, tzinfo=C4())
2570 self.assertRaises(TypeError, t.utcoffset)
2571 self.assertRaises(TypeError, t.dst)
2572 self.assertRaises(TypeError, t.tzname)
2573
2574 # Offset out of range.
2575 class C6(tzinfo):
2576 def utcoffset(self, dt): return timedelta(hours=-24)
2577 def dst(self, dt): return timedelta(hours=24)
2578 t = cls(1, 1, 1, tzinfo=C6())
2579 self.assertRaises(ValueError, t.utcoffset)
2580 self.assertRaises(ValueError, t.dst)
2581
2582 # Not a whole number of minutes.
2583 class C7(tzinfo):
2584 def utcoffset(self, dt): return timedelta(seconds=61)
2585 def dst(self, dt): return timedelta(microseconds=-81)
2586 t = cls(1, 1, 1, tzinfo=C7())
2587 self.assertRaises(ValueError, t.utcoffset)
2588 self.assertRaises(ValueError, t.dst)
2589
2590 def test_aware_compare(self):
2591 cls = self.theclass
2592
2593 # Ensure that utcoffset() gets ignored if the comparands have
2594 # the same tzinfo member.
2595 class OperandDependentOffset(tzinfo):
2596 def utcoffset(self, t):
2597 if t.minute < 10:
2598 # d0 and d1 equal after adjustment
2599 return timedelta(minutes=t.minute)
2600 else:
2601 # d2 off in the weeds
2602 return timedelta(minutes=59)
2603
2604 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2605 d0 = base.replace(minute=3)
2606 d1 = base.replace(minute=9)
2607 d2 = base.replace(minute=11)
2608 for x in d0, d1, d2:
2609 for y in d0, d1, d2:
2610 for op in lt, le, gt, ge, eq, ne:
2611 got = op(x, y)
2612 expected = op(x.minute, y.minute)
2613 self.assertEqual(got, expected)
2614
2615 # However, if they're different members, uctoffset is not ignored.
2616 # Note that a time can't actually have an operand-depedent offset,
2617 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2618 # so skip this test for time.
2619 if cls is not time:
2620 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2621 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2622 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2623 for x in d0, d1, d2:
2624 for y in d0, d1, d2:
2625 got = (x > y) - (x < y)
2626 if (x is d0 or x is d1) and (y is d0 or y is d1):
2627 expected = 0
2628 elif x is y is d2:
2629 expected = 0
2630 elif x is d2:
2631 expected = -1
2632 else:
2633 assert y is d2
2634 expected = 1
2635 self.assertEqual(got, expected)
2636
2637
2638# Testing time objects with a non-None tzinfo.
2639class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2640 theclass = time
2641
2642 def test_empty(self):
2643 t = self.theclass()
2644 self.assertEqual(t.hour, 0)
2645 self.assertEqual(t.minute, 0)
2646 self.assertEqual(t.second, 0)
2647 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002648 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002649
2650 def test_zones(self):
2651 est = FixedOffset(-300, "EST", 1)
2652 utc = FixedOffset(0, "UTC", -2)
2653 met = FixedOffset(60, "MET", 3)
2654 t1 = time( 7, 47, tzinfo=est)
2655 t2 = time(12, 47, tzinfo=utc)
2656 t3 = time(13, 47, tzinfo=met)
2657 t4 = time(microsecond=40)
2658 t5 = time(microsecond=40, tzinfo=utc)
2659
2660 self.assertEqual(t1.tzinfo, est)
2661 self.assertEqual(t2.tzinfo, utc)
2662 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002663 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002664 self.assertEqual(t5.tzinfo, utc)
2665
2666 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2667 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2668 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002669 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002670 self.assertRaises(TypeError, t1.utcoffset, "no args")
2671
2672 self.assertEqual(t1.tzname(), "EST")
2673 self.assertEqual(t2.tzname(), "UTC")
2674 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002675 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002676 self.assertRaises(TypeError, t1.tzname, "no args")
2677
2678 self.assertEqual(t1.dst(), timedelta(minutes=1))
2679 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2680 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002681 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002682 self.assertRaises(TypeError, t1.dst, "no args")
2683
2684 self.assertEqual(hash(t1), hash(t2))
2685 self.assertEqual(hash(t1), hash(t3))
2686 self.assertEqual(hash(t2), hash(t3))
2687
2688 self.assertEqual(t1, t2)
2689 self.assertEqual(t1, t3)
2690 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002691 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002692 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2693 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2694
2695 self.assertEqual(str(t1), "07:47:00-05:00")
2696 self.assertEqual(str(t2), "12:47:00+00:00")
2697 self.assertEqual(str(t3), "13:47:00+01:00")
2698 self.assertEqual(str(t4), "00:00:00.000040")
2699 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2700
2701 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2702 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2703 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2704 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2705 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2706
2707 d = 'datetime.time'
2708 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2709 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2710 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2711 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2712 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2713
2714 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2715 "07:47:00 %Z=EST %z=-0500")
2716 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2717 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2718
2719 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2720 t1 = time(23, 59, tzinfo=yuck)
2721 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2722 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2723
2724 # Check that an invalid tzname result raises an exception.
2725 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002726 tz = 42
2727 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002728 t = time(2, 3, 4, tzinfo=Badtzname())
2729 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2730 self.assertRaises(TypeError, t.strftime, "%Z")
2731
Alexander Belopolskye239d232010-12-08 23:31:48 +00002732 # Issue #6697:
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04002733 if '_Fast' in str(self):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002734 Badtzname.tz = '\ud800'
2735 self.assertRaises(ValueError, t.strftime, "%Z")
2736
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002737 def test_hash_edge_cases(self):
2738 # Offsets that overflow a basic time.
2739 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2740 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2741 self.assertEqual(hash(t1), hash(t2))
2742
2743 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2744 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2745 self.assertEqual(hash(t1), hash(t2))
2746
2747 def test_pickling(self):
2748 # Try one without a tzinfo.
2749 args = 20, 59, 16, 64**2
2750 orig = self.theclass(*args)
2751 for pickler, unpickler, proto in pickle_choices:
2752 green = pickler.dumps(orig, proto)
2753 derived = unpickler.loads(green)
2754 self.assertEqual(orig, derived)
2755
2756 # Try one with a tzinfo.
2757 tinfo = PicklableFixedOffset(-300, 'cookie')
2758 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2759 for pickler, unpickler, proto in pickle_choices:
2760 green = pickler.dumps(orig, proto)
2761 derived = unpickler.loads(green)
2762 self.assertEqual(orig, derived)
2763 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2764 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2765 self.assertEqual(derived.tzname(), 'cookie')
2766
2767 def test_more_bool(self):
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002768 # time is always True.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002769 cls = self.theclass
2770
2771 t = cls(0, tzinfo=FixedOffset(-300, ""))
2772 self.assertTrue(t)
2773
2774 t = cls(5, tzinfo=FixedOffset(-300, ""))
2775 self.assertTrue(t)
2776
2777 t = cls(5, tzinfo=FixedOffset(300, ""))
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002778 self.assertTrue(t)
2779
Benjamin Petersonee6bdc02014-03-20 18:00:35 -05002780 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2781 self.assertTrue(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002782
2783 def test_replace(self):
2784 cls = self.theclass
2785 z100 = FixedOffset(100, "+100")
2786 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2787 args = [1, 2, 3, 4, z100]
2788 base = cls(*args)
2789 self.assertEqual(base, base.replace())
2790
2791 i = 0
2792 for name, newval in (("hour", 5),
2793 ("minute", 6),
2794 ("second", 7),
2795 ("microsecond", 8),
2796 ("tzinfo", zm200)):
2797 newargs = args[:]
2798 newargs[i] = newval
2799 expected = cls(*newargs)
2800 got = base.replace(**{name: newval})
2801 self.assertEqual(expected, got)
2802 i += 1
2803
2804 # Ensure we can get rid of a tzinfo.
2805 self.assertEqual(base.tzname(), "+100")
2806 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002807 self.assertIsNone(base2.tzinfo)
2808 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002809
2810 # Ensure we can add one.
2811 base3 = base2.replace(tzinfo=z100)
2812 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002813 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002814
2815 # Out of bounds.
2816 base = cls(1)
2817 self.assertRaises(ValueError, base.replace, hour=24)
2818 self.assertRaises(ValueError, base.replace, minute=-1)
2819 self.assertRaises(ValueError, base.replace, second=100)
2820 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2821
2822 def test_mixed_compare(self):
2823 t1 = time(1, 2, 3)
2824 t2 = time(1, 2, 3)
2825 self.assertEqual(t1, t2)
2826 t2 = t2.replace(tzinfo=None)
2827 self.assertEqual(t1, t2)
2828 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2829 self.assertEqual(t1, t2)
2830 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002831 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002832
2833 # In time w/ identical tzinfo objects, utcoffset is ignored.
2834 class Varies(tzinfo):
2835 def __init__(self):
2836 self.offset = timedelta(minutes=22)
2837 def utcoffset(self, t):
2838 self.offset += timedelta(minutes=1)
2839 return self.offset
2840
2841 v = Varies()
2842 t1 = t2.replace(tzinfo=v)
2843 t2 = t2.replace(tzinfo=v)
2844 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2845 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2846 self.assertEqual(t1, t2)
2847
2848 # But if they're not identical, it isn't ignored.
2849 t2 = t2.replace(tzinfo=Varies())
2850 self.assertTrue(t1 < t2) # t1's offset counter still going up
2851
2852 def test_subclass_timetz(self):
2853
2854 class C(self.theclass):
2855 theAnswer = 42
2856
2857 def __new__(cls, *args, **kws):
2858 temp = kws.copy()
2859 extra = temp.pop('extra')
2860 result = self.theclass.__new__(cls, *args, **temp)
2861 result.extra = extra
2862 return result
2863
2864 def newmeth(self, start):
2865 return start + self.hour + self.second
2866
2867 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2868
2869 dt1 = self.theclass(*args)
2870 dt2 = C(*args, **{'extra': 7})
2871
2872 self.assertEqual(dt2.__class__, C)
2873 self.assertEqual(dt2.theAnswer, 42)
2874 self.assertEqual(dt2.extra, 7)
2875 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2876 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2877
2878
2879# Testing datetime objects with a non-None tzinfo.
2880
2881class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2882 theclass = datetime
2883
2884 def test_trivial(self):
2885 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2886 self.assertEqual(dt.year, 1)
2887 self.assertEqual(dt.month, 2)
2888 self.assertEqual(dt.day, 3)
2889 self.assertEqual(dt.hour, 4)
2890 self.assertEqual(dt.minute, 5)
2891 self.assertEqual(dt.second, 6)
2892 self.assertEqual(dt.microsecond, 7)
2893 self.assertEqual(dt.tzinfo, None)
2894
2895 def test_even_more_compare(self):
2896 # The test_compare() and test_more_compare() inherited from TestDate
2897 # and TestDateTime covered non-tzinfo cases.
2898
2899 # Smallest possible after UTC adjustment.
2900 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2901 # Largest possible after UTC adjustment.
2902 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2903 tzinfo=FixedOffset(-1439, ""))
2904
2905 # Make sure those compare correctly, and w/o overflow.
2906 self.assertTrue(t1 < t2)
2907 self.assertTrue(t1 != t2)
2908 self.assertTrue(t2 > t1)
2909
2910 self.assertEqual(t1, t1)
2911 self.assertEqual(t2, t2)
2912
2913 # Equal afer adjustment.
2914 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2915 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2916 self.assertEqual(t1, t2)
2917
2918 # Change t1 not to subtract a minute, and t1 should be larger.
2919 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2920 self.assertTrue(t1 > t2)
2921
2922 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2923 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2924 self.assertTrue(t1 < t2)
2925
2926 # Back to the original t1, but make seconds resolve it.
2927 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2928 second=1)
2929 self.assertTrue(t1 > t2)
2930
2931 # Likewise, but make microseconds resolve it.
2932 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2933 microsecond=1)
2934 self.assertTrue(t1 > t2)
2935
Alexander Belopolsky08313822012-06-15 20:19:47 -04002936 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002937 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002938 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002939 self.assertEqual(t2, t2)
2940
2941 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2942 class Naive(tzinfo):
2943 def utcoffset(self, dt): return None
2944 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002945 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002946 self.assertEqual(t2, t2)
2947
2948 # OTOH, it's OK to compare two of these mixing the two ways of being
2949 # naive.
2950 t1 = self.theclass(5, 6, 7)
2951 self.assertEqual(t1, t2)
2952
2953 # Try a bogus uctoffset.
2954 class Bogus(tzinfo):
2955 def utcoffset(self, dt):
2956 return timedelta(minutes=1440) # out of bounds
2957 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2958 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2959 self.assertRaises(ValueError, lambda: t1 == t2)
2960
2961 def test_pickling(self):
2962 # Try one without a tzinfo.
2963 args = 6, 7, 23, 20, 59, 1, 64**2
2964 orig = self.theclass(*args)
2965 for pickler, unpickler, proto in pickle_choices:
2966 green = pickler.dumps(orig, proto)
2967 derived = unpickler.loads(green)
2968 self.assertEqual(orig, derived)
2969
2970 # Try one with a tzinfo.
2971 tinfo = PicklableFixedOffset(-300, 'cookie')
2972 orig = self.theclass(*args, **{'tzinfo': tinfo})
2973 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2974 for pickler, unpickler, proto in pickle_choices:
2975 green = pickler.dumps(orig, proto)
2976 derived = unpickler.loads(green)
2977 self.assertEqual(orig, derived)
2978 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2979 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2980 self.assertEqual(derived.tzname(), 'cookie')
2981
2982 def test_extreme_hashes(self):
2983 # If an attempt is made to hash these via subtracting the offset
2984 # then hashing a datetime object, OverflowError results. The
2985 # Python implementation used to blow up here.
2986 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2987 hash(t)
2988 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2989 tzinfo=FixedOffset(-1439, ""))
2990 hash(t)
2991
2992 # OTOH, an OOB offset should blow up.
2993 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2994 self.assertRaises(ValueError, hash, t)
2995
2996 def test_zones(self):
2997 est = FixedOffset(-300, "EST")
2998 utc = FixedOffset(0, "UTC")
2999 met = FixedOffset(60, "MET")
3000 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
3001 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3002 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3003 self.assertEqual(t1.tzinfo, est)
3004 self.assertEqual(t2.tzinfo, utc)
3005 self.assertEqual(t3.tzinfo, met)
3006 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3007 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3008 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3009 self.assertEqual(t1.tzname(), "EST")
3010 self.assertEqual(t2.tzname(), "UTC")
3011 self.assertEqual(t3.tzname(), "MET")
3012 self.assertEqual(hash(t1), hash(t2))
3013 self.assertEqual(hash(t1), hash(t3))
3014 self.assertEqual(hash(t2), hash(t3))
3015 self.assertEqual(t1, t2)
3016 self.assertEqual(t1, t3)
3017 self.assertEqual(t2, t3)
3018 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3019 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3020 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3021 d = 'datetime.datetime(2002, 3, 19, '
3022 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3023 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3024 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3025
3026 def test_combine(self):
3027 met = FixedOffset(60, "MET")
3028 d = date(2002, 3, 4)
3029 tz = time(18, 45, 3, 1234, tzinfo=met)
3030 dt = datetime.combine(d, tz)
3031 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3032 tzinfo=met))
3033
3034 def test_extract(self):
3035 met = FixedOffset(60, "MET")
3036 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3037 self.assertEqual(dt.date(), date(2002, 3, 4))
3038 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3039 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3040
3041 def test_tz_aware_arithmetic(self):
3042 import random
3043
3044 now = self.theclass.now()
3045 tz55 = FixedOffset(-330, "west 5:30")
3046 timeaware = now.time().replace(tzinfo=tz55)
3047 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003048 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003049 self.assertEqual(nowaware.timetz(), timeaware)
3050
3051 # Can't mix aware and non-aware.
3052 self.assertRaises(TypeError, lambda: now - nowaware)
3053 self.assertRaises(TypeError, lambda: nowaware - now)
3054
3055 # And adding datetime's doesn't make sense, aware or not.
3056 self.assertRaises(TypeError, lambda: now + nowaware)
3057 self.assertRaises(TypeError, lambda: nowaware + now)
3058 self.assertRaises(TypeError, lambda: nowaware + nowaware)
3059
3060 # Subtracting should yield 0.
3061 self.assertEqual(now - now, timedelta(0))
3062 self.assertEqual(nowaware - nowaware, timedelta(0))
3063
3064 # Adding a delta should preserve tzinfo.
3065 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3066 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003067 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003068 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003069 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003070 self.assertEqual(nowawareplus, nowawareplus2)
3071
3072 # that - delta should be what we started with, and that - what we
3073 # started with should be delta.
3074 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003075 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003076 self.assertEqual(nowaware, diff)
3077 self.assertRaises(TypeError, lambda: delta - nowawareplus)
3078 self.assertEqual(nowawareplus - nowaware, delta)
3079
3080 # Make up a random timezone.
3081 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3082 # Attach it to nowawareplus.
3083 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003084 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003085 # Make sure the difference takes the timezone adjustments into account.
3086 got = nowaware - nowawareplus
3087 # Expected: (nowaware base - nowaware offset) -
3088 # (nowawareplus base - nowawareplus offset) =
3089 # (nowaware base - nowawareplus base) +
3090 # (nowawareplus offset - nowaware offset) =
3091 # -delta + nowawareplus offset - nowaware offset
3092 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3093 self.assertEqual(got, expected)
3094
3095 # Try max possible difference.
3096 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3097 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3098 tzinfo=FixedOffset(-1439, "max"))
3099 maxdiff = max - min
3100 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3101 timedelta(minutes=2*1439))
3102 # Different tzinfo, but the same offset
3103 tza = timezone(HOUR, 'A')
3104 tzb = timezone(HOUR, 'B')
3105 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3106 self.assertEqual(delta, self.theclass.min - self.theclass.max)
3107
3108 def test_tzinfo_now(self):
3109 meth = self.theclass.now
3110 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3111 base = meth()
3112 # Try with and without naming the keyword.
3113 off42 = FixedOffset(42, "42")
3114 another = meth(off42)
3115 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003116 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003117 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3118 # Bad argument with and w/o naming the keyword.
3119 self.assertRaises(TypeError, meth, 16)
3120 self.assertRaises(TypeError, meth, tzinfo=16)
3121 # Bad keyword name.
3122 self.assertRaises(TypeError, meth, tinfo=off42)
3123 # Too many args.
3124 self.assertRaises(TypeError, meth, off42, off42)
3125
3126 # We don't know which time zone we're in, and don't have a tzinfo
3127 # class to represent it, so seeing whether a tz argument actually
3128 # does a conversion is tricky.
3129 utc = FixedOffset(0, "utc", 0)
3130 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3131 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3132 for dummy in range(3):
3133 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003134 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003135 utcnow = datetime.utcnow().replace(tzinfo=utc)
3136 now2 = utcnow.astimezone(weirdtz)
3137 if abs(now - now2) < timedelta(seconds=30):
3138 break
3139 # Else the code is broken, or more than 30 seconds passed between
3140 # calls; assuming the latter, just try again.
3141 else:
3142 # Three strikes and we're out.
3143 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3144
3145 def test_tzinfo_fromtimestamp(self):
3146 import time
3147 meth = self.theclass.fromtimestamp
3148 ts = time.time()
3149 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3150 base = meth(ts)
3151 # Try with and without naming the keyword.
3152 off42 = FixedOffset(42, "42")
3153 another = meth(ts, off42)
3154 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003155 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003156 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3157 # Bad argument with and w/o naming the keyword.
3158 self.assertRaises(TypeError, meth, ts, 16)
3159 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3160 # Bad keyword name.
3161 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3162 # Too many args.
3163 self.assertRaises(TypeError, meth, ts, off42, off42)
3164 # Too few args.
3165 self.assertRaises(TypeError, meth)
3166
3167 # Try to make sure tz= actually does some conversion.
3168 timestamp = 1000000000
3169 utcdatetime = datetime.utcfromtimestamp(timestamp)
3170 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3171 # But on some flavor of Mac, it's nowhere near that. So we can't have
3172 # any idea here what time that actually is, we can only test that
3173 # relative changes match.
3174 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3175 tz = FixedOffset(utcoffset, "tz", 0)
3176 expected = utcdatetime + utcoffset
3177 got = datetime.fromtimestamp(timestamp, tz)
3178 self.assertEqual(expected, got.replace(tzinfo=None))
3179
3180 def test_tzinfo_utcnow(self):
3181 meth = self.theclass.utcnow
3182 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3183 base = meth()
3184 # Try with and without naming the keyword; for whatever reason,
3185 # utcnow() doesn't accept a tzinfo argument.
3186 off42 = FixedOffset(42, "42")
3187 self.assertRaises(TypeError, meth, off42)
3188 self.assertRaises(TypeError, meth, tzinfo=off42)
3189
3190 def test_tzinfo_utcfromtimestamp(self):
3191 import time
3192 meth = self.theclass.utcfromtimestamp
3193 ts = time.time()
3194 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3195 base = meth(ts)
3196 # Try with and without naming the keyword; for whatever reason,
3197 # utcfromtimestamp() doesn't accept a tzinfo argument.
3198 off42 = FixedOffset(42, "42")
3199 self.assertRaises(TypeError, meth, ts, off42)
3200 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3201
3202 def test_tzinfo_timetuple(self):
3203 # TestDateTime tested most of this. datetime adds a twist to the
3204 # DST flag.
3205 class DST(tzinfo):
3206 def __init__(self, dstvalue):
3207 if isinstance(dstvalue, int):
3208 dstvalue = timedelta(minutes=dstvalue)
3209 self.dstvalue = dstvalue
3210 def dst(self, dt):
3211 return self.dstvalue
3212
3213 cls = self.theclass
3214 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3215 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3216 t = d.timetuple()
3217 self.assertEqual(1, t.tm_year)
3218 self.assertEqual(1, t.tm_mon)
3219 self.assertEqual(1, t.tm_mday)
3220 self.assertEqual(10, t.tm_hour)
3221 self.assertEqual(20, t.tm_min)
3222 self.assertEqual(30, t.tm_sec)
3223 self.assertEqual(0, t.tm_wday)
3224 self.assertEqual(1, t.tm_yday)
3225 self.assertEqual(flag, t.tm_isdst)
3226
3227 # dst() returns wrong type.
3228 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3229
3230 # dst() at the edge.
3231 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3232 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3233
3234 # dst() out of range.
3235 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3236 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3237
3238 def test_utctimetuple(self):
3239 class DST(tzinfo):
3240 def __init__(self, dstvalue=0):
3241 if isinstance(dstvalue, int):
3242 dstvalue = timedelta(minutes=dstvalue)
3243 self.dstvalue = dstvalue
3244 def dst(self, dt):
3245 return self.dstvalue
3246
3247 cls = self.theclass
3248 # This can't work: DST didn't implement utcoffset.
3249 self.assertRaises(NotImplementedError,
3250 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3251
3252 class UOFS(DST):
3253 def __init__(self, uofs, dofs=None):
3254 DST.__init__(self, dofs)
3255 self.uofs = timedelta(minutes=uofs)
3256 def utcoffset(self, dt):
3257 return self.uofs
3258
3259 for dstvalue in -33, 33, 0, None:
3260 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3261 t = d.utctimetuple()
3262 self.assertEqual(d.year, t.tm_year)
3263 self.assertEqual(d.month, t.tm_mon)
3264 self.assertEqual(d.day, t.tm_mday)
3265 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3266 self.assertEqual(13, t.tm_min)
3267 self.assertEqual(d.second, t.tm_sec)
3268 self.assertEqual(d.weekday(), t.tm_wday)
3269 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3270 t.tm_yday)
3271 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3272 # is never in effect for a UTC time.
3273 self.assertEqual(0, t.tm_isdst)
3274
3275 # For naive datetime, utctimetuple == timetuple except for isdst
3276 d = cls(1, 2, 3, 10, 20, 30, 40)
3277 t = d.utctimetuple()
3278 self.assertEqual(t[:-1], d.timetuple()[:-1])
3279 self.assertEqual(0, t.tm_isdst)
3280 # Same if utcoffset is None
3281 class NOFS(DST):
3282 def utcoffset(self, dt):
3283 return None
3284 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3285 t = d.utctimetuple()
3286 self.assertEqual(t[:-1], d.timetuple()[:-1])
3287 self.assertEqual(0, t.tm_isdst)
3288 # Check that bad tzinfo is detected
3289 class BOFS(DST):
3290 def utcoffset(self, dt):
3291 return "EST"
3292 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3293 self.assertRaises(TypeError, d.utctimetuple)
3294
3295 # Check that utctimetuple() is the same as
3296 # astimezone(utc).timetuple()
3297 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3298 for tz in [timezone.min, timezone.utc, timezone.max]:
3299 dtz = d.replace(tzinfo=tz)
3300 self.assertEqual(dtz.utctimetuple()[:-1],
3301 dtz.astimezone(timezone.utc).timetuple()[:-1])
3302 # At the edges, UTC adjustment can produce years out-of-range
3303 # for a datetime object. Ensure that an OverflowError is
3304 # raised.
3305 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3306 # That goes back 1 minute less than a full day.
3307 self.assertRaises(OverflowError, tiny.utctimetuple)
3308
3309 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3310 # That goes forward 1 minute less than a full day.
3311 self.assertRaises(OverflowError, huge.utctimetuple)
3312 # More overflow cases
3313 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3314 self.assertRaises(OverflowError, tiny.utctimetuple)
3315 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3316 self.assertRaises(OverflowError, huge.utctimetuple)
3317
3318 def test_tzinfo_isoformat(self):
3319 zero = FixedOffset(0, "+00:00")
3320 plus = FixedOffset(220, "+03:40")
3321 minus = FixedOffset(-231, "-03:51")
3322 unknown = FixedOffset(None, "")
3323
3324 cls = self.theclass
3325 datestr = '0001-02-03'
3326 for ofs in None, zero, plus, minus, unknown:
3327 for us in 0, 987001:
3328 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3329 timestr = '04:05:59' + (us and '.987001' or '')
3330 ofsstr = ofs is not None and d.tzname() or ''
3331 tailstr = timestr + ofsstr
3332 iso = d.isoformat()
3333 self.assertEqual(iso, datestr + 'T' + tailstr)
3334 self.assertEqual(iso, d.isoformat('T'))
3335 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3336 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3337 self.assertEqual(str(d), datestr + ' ' + tailstr)
3338
3339 def test_replace(self):
3340 cls = self.theclass
3341 z100 = FixedOffset(100, "+100")
3342 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3343 args = [1, 2, 3, 4, 5, 6, 7, z100]
3344 base = cls(*args)
3345 self.assertEqual(base, base.replace())
3346
3347 i = 0
3348 for name, newval in (("year", 2),
3349 ("month", 3),
3350 ("day", 4),
3351 ("hour", 5),
3352 ("minute", 6),
3353 ("second", 7),
3354 ("microsecond", 8),
3355 ("tzinfo", zm200)):
3356 newargs = args[:]
3357 newargs[i] = newval
3358 expected = cls(*newargs)
3359 got = base.replace(**{name: newval})
3360 self.assertEqual(expected, got)
3361 i += 1
3362
3363 # Ensure we can get rid of a tzinfo.
3364 self.assertEqual(base.tzname(), "+100")
3365 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003366 self.assertIsNone(base2.tzinfo)
3367 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003368
3369 # Ensure we can add one.
3370 base3 = base2.replace(tzinfo=z100)
3371 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003372 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003373
3374 # Out of bounds.
3375 base = cls(2000, 2, 29)
3376 self.assertRaises(ValueError, base.replace, year=2001)
3377
3378 def test_more_astimezone(self):
3379 # The inherited test_astimezone covered some trivial and error cases.
3380 fnone = FixedOffset(None, "None")
3381 f44m = FixedOffset(44, "44")
3382 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3383
3384 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003385 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003386 # Replacing with degenerate tzinfo raises an exception.
3387 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003388 # Replacing with same tzinfo makes no change.
3389 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003390 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003391 self.assertEqual(x.date(), dt.date())
3392 self.assertEqual(x.time(), dt.time())
3393
3394 # Replacing with different tzinfo does adjust.
3395 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003396 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003397 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3398 expected = dt - dt.utcoffset() # in effect, convert to UTC
3399 expected += fm5h.utcoffset(dt) # and from there to local time
3400 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3401 self.assertEqual(got.date(), expected.date())
3402 self.assertEqual(got.time(), expected.time())
3403 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003404 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003405 self.assertEqual(got, expected)
3406
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003407 @support.run_with_tz('UTC')
3408 def test_astimezone_default_utc(self):
3409 dt = self.theclass.now(timezone.utc)
3410 self.assertEqual(dt.astimezone(None), dt)
3411 self.assertEqual(dt.astimezone(), dt)
3412
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003413 # Note that offset in TZ variable has the opposite sign to that
3414 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003415 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3416 def test_astimezone_default_eastern(self):
3417 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3418 local = dt.astimezone()
3419 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003420 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003421 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3422 local = dt.astimezone()
3423 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003424 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003425
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003426 def test_aware_subtract(self):
3427 cls = self.theclass
3428
3429 # Ensure that utcoffset() is ignored when the operands have the
3430 # same tzinfo member.
3431 class OperandDependentOffset(tzinfo):
3432 def utcoffset(self, t):
3433 if t.minute < 10:
3434 # d0 and d1 equal after adjustment
3435 return timedelta(minutes=t.minute)
3436 else:
3437 # d2 off in the weeds
3438 return timedelta(minutes=59)
3439
3440 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3441 d0 = base.replace(minute=3)
3442 d1 = base.replace(minute=9)
3443 d2 = base.replace(minute=11)
3444 for x in d0, d1, d2:
3445 for y in d0, d1, d2:
3446 got = x - y
3447 expected = timedelta(minutes=x.minute - y.minute)
3448 self.assertEqual(got, expected)
3449
3450 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3451 # ignored.
3452 base = cls(8, 9, 10, 11, 12, 13, 14)
3453 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3454 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3455 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3456 for x in d0, d1, d2:
3457 for y in d0, d1, d2:
3458 got = x - y
3459 if (x is d0 or x is d1) and (y is d0 or y is d1):
3460 expected = timedelta(0)
3461 elif x is y is d2:
3462 expected = timedelta(0)
3463 elif x is d2:
3464 expected = timedelta(minutes=(11-59)-0)
3465 else:
3466 assert y is d2
3467 expected = timedelta(minutes=0-(11-59))
3468 self.assertEqual(got, expected)
3469
3470 def test_mixed_compare(self):
3471 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3472 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3473 self.assertEqual(t1, t2)
3474 t2 = t2.replace(tzinfo=None)
3475 self.assertEqual(t1, t2)
3476 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3477 self.assertEqual(t1, t2)
3478 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003479 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003480
3481 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3482 class Varies(tzinfo):
3483 def __init__(self):
3484 self.offset = timedelta(minutes=22)
3485 def utcoffset(self, t):
3486 self.offset += timedelta(minutes=1)
3487 return self.offset
3488
3489 v = Varies()
3490 t1 = t2.replace(tzinfo=v)
3491 t2 = t2.replace(tzinfo=v)
3492 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3493 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3494 self.assertEqual(t1, t2)
3495
3496 # But if they're not identical, it isn't ignored.
3497 t2 = t2.replace(tzinfo=Varies())
3498 self.assertTrue(t1 < t2) # t1's offset counter still going up
3499
3500 def test_subclass_datetimetz(self):
3501
3502 class C(self.theclass):
3503 theAnswer = 42
3504
3505 def __new__(cls, *args, **kws):
3506 temp = kws.copy()
3507 extra = temp.pop('extra')
3508 result = self.theclass.__new__(cls, *args, **temp)
3509 result.extra = extra
3510 return result
3511
3512 def newmeth(self, start):
3513 return start + self.hour + self.year
3514
3515 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3516
3517 dt1 = self.theclass(*args)
3518 dt2 = C(*args, **{'extra': 7})
3519
3520 self.assertEqual(dt2.__class__, C)
3521 self.assertEqual(dt2.theAnswer, 42)
3522 self.assertEqual(dt2.extra, 7)
3523 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3524 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3525
3526# Pain to set up DST-aware tzinfo classes.
3527
3528def first_sunday_on_or_after(dt):
3529 days_to_go = 6 - dt.weekday()
3530 if days_to_go:
3531 dt += timedelta(days_to_go)
3532 return dt
3533
3534ZERO = timedelta(0)
3535MINUTE = timedelta(minutes=1)
3536HOUR = timedelta(hours=1)
3537DAY = timedelta(days=1)
3538# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3539DSTSTART = datetime(1, 4, 1, 2)
3540# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3541# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3542# being standard time on that day, there is no spelling in local time of
3543# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3544DSTEND = datetime(1, 10, 25, 1)
3545
3546class USTimeZone(tzinfo):
3547
3548 def __init__(self, hours, reprname, stdname, dstname):
3549 self.stdoffset = timedelta(hours=hours)
3550 self.reprname = reprname
3551 self.stdname = stdname
3552 self.dstname = dstname
3553
3554 def __repr__(self):
3555 return self.reprname
3556
3557 def tzname(self, dt):
3558 if self.dst(dt):
3559 return self.dstname
3560 else:
3561 return self.stdname
3562
3563 def utcoffset(self, dt):
3564 return self.stdoffset + self.dst(dt)
3565
3566 def dst(self, dt):
3567 if dt is None or dt.tzinfo is None:
3568 # An exception instead may be sensible here, in one or more of
3569 # the cases.
3570 return ZERO
3571 assert dt.tzinfo is self
3572
3573 # Find first Sunday in April.
3574 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3575 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3576
3577 # Find last Sunday in October.
3578 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3579 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3580
3581 # Can't compare naive to aware objects, so strip the timezone from
3582 # dt first.
3583 if start <= dt.replace(tzinfo=None) < end:
3584 return HOUR
3585 else:
3586 return ZERO
3587
3588Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3589Central = USTimeZone(-6, "Central", "CST", "CDT")
3590Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3591Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3592utc_real = FixedOffset(0, "UTC", 0)
3593# For better test coverage, we want another flavor of UTC that's west of
3594# the Eastern and Pacific timezones.
3595utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3596
3597class TestTimezoneConversions(unittest.TestCase):
3598 # The DST switch times for 2002, in std time.
3599 dston = datetime(2002, 4, 7, 2)
3600 dstoff = datetime(2002, 10, 27, 1)
3601
3602 theclass = datetime
3603
3604 # Check a time that's inside DST.
3605 def checkinside(self, dt, tz, utc, dston, dstoff):
3606 self.assertEqual(dt.dst(), HOUR)
3607
3608 # Conversion to our own timezone is always an identity.
3609 self.assertEqual(dt.astimezone(tz), dt)
3610
3611 asutc = dt.astimezone(utc)
3612 there_and_back = asutc.astimezone(tz)
3613
3614 # Conversion to UTC and back isn't always an identity here,
3615 # because there are redundant spellings (in local time) of
3616 # UTC time when DST begins: the clock jumps from 1:59:59
3617 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3618 # make sense then. The classes above treat 2:MM:SS as
3619 # daylight time then (it's "after 2am"), really an alias
3620 # for 1:MM:SS standard time. The latter form is what
3621 # conversion back from UTC produces.
3622 if dt.date() == dston.date() and dt.hour == 2:
3623 # We're in the redundant hour, and coming back from
3624 # UTC gives the 1:MM:SS standard-time spelling.
3625 self.assertEqual(there_and_back + HOUR, dt)
3626 # Although during was considered to be in daylight
3627 # time, there_and_back is not.
3628 self.assertEqual(there_and_back.dst(), ZERO)
3629 # They're the same times in UTC.
3630 self.assertEqual(there_and_back.astimezone(utc),
3631 dt.astimezone(utc))
3632 else:
3633 # We're not in the redundant hour.
3634 self.assertEqual(dt, there_and_back)
3635
3636 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003637 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003638 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3639 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3640 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3641 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3642 # expressed in local time. Nevertheless, we want conversion back
3643 # from UTC to mimic the local clock's "repeat an hour" behavior.
3644 nexthour_utc = asutc + HOUR
3645 nexthour_tz = nexthour_utc.astimezone(tz)
3646 if dt.date() == dstoff.date() and dt.hour == 0:
3647 # We're in the hour before the last DST hour. The last DST hour
3648 # is ineffable. We want the conversion back to repeat 1:MM.
3649 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3650 nexthour_utc += HOUR
3651 nexthour_tz = nexthour_utc.astimezone(tz)
3652 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3653 else:
3654 self.assertEqual(nexthour_tz - dt, HOUR)
3655
3656 # Check a time that's outside DST.
3657 def checkoutside(self, dt, tz, utc):
3658 self.assertEqual(dt.dst(), ZERO)
3659
3660 # Conversion to our own timezone is always an identity.
3661 self.assertEqual(dt.astimezone(tz), dt)
3662
3663 # Converting to UTC and back is an identity too.
3664 asutc = dt.astimezone(utc)
3665 there_and_back = asutc.astimezone(tz)
3666 self.assertEqual(dt, there_and_back)
3667
3668 def convert_between_tz_and_utc(self, tz, utc):
3669 dston = self.dston.replace(tzinfo=tz)
3670 # Because 1:MM on the day DST ends is taken as being standard time,
3671 # there is no spelling in tz for the last hour of daylight time.
3672 # For purposes of the test, the last hour of DST is 0:MM, which is
3673 # taken as being daylight time (and 1:MM is taken as being standard
3674 # time).
3675 dstoff = self.dstoff.replace(tzinfo=tz)
3676 for delta in (timedelta(weeks=13),
3677 DAY,
3678 HOUR,
3679 timedelta(minutes=1),
3680 timedelta(microseconds=1)):
3681
3682 self.checkinside(dston, tz, utc, dston, dstoff)
3683 for during in dston + delta, dstoff - delta:
3684 self.checkinside(during, tz, utc, dston, dstoff)
3685
3686 self.checkoutside(dstoff, tz, utc)
3687 for outside in dston - delta, dstoff + delta:
3688 self.checkoutside(outside, tz, utc)
3689
3690 def test_easy(self):
3691 # Despite the name of this test, the endcases are excruciating.
3692 self.convert_between_tz_and_utc(Eastern, utc_real)
3693 self.convert_between_tz_and_utc(Pacific, utc_real)
3694 self.convert_between_tz_and_utc(Eastern, utc_fake)
3695 self.convert_between_tz_and_utc(Pacific, utc_fake)
3696 # The next is really dancing near the edge. It works because
3697 # Pacific and Eastern are far enough apart that their "problem
3698 # hours" don't overlap.
3699 self.convert_between_tz_and_utc(Eastern, Pacific)
3700 self.convert_between_tz_and_utc(Pacific, Eastern)
3701 # OTOH, these fail! Don't enable them. The difficulty is that
3702 # the edge case tests assume that every hour is representable in
3703 # the "utc" class. This is always true for a fixed-offset tzinfo
3704 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3705 # For these adjacent DST-aware time zones, the range of time offsets
3706 # tested ends up creating hours in the one that aren't representable
3707 # in the other. For the same reason, we would see failures in the
3708 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3709 # offset deltas in convert_between_tz_and_utc().
3710 #
3711 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3712 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3713
3714 def test_tricky(self):
3715 # 22:00 on day before daylight starts.
3716 fourback = self.dston - timedelta(hours=4)
3717 ninewest = FixedOffset(-9*60, "-0900", 0)
3718 fourback = fourback.replace(tzinfo=ninewest)
3719 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3720 # 2", we should get the 3 spelling.
3721 # If we plug 22:00 the day before into Eastern, it "looks like std
3722 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3723 # to 22:00 lands on 2:00, which makes no sense in local time (the
3724 # local clock jumps from 1 to 3). The point here is to make sure we
3725 # get the 3 spelling.
3726 expected = self.dston.replace(hour=3)
3727 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3728 self.assertEqual(expected, got)
3729
3730 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3731 # case we want the 1:00 spelling.
3732 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3733 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3734 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3735 # spelling.
3736 expected = self.dston.replace(hour=1)
3737 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3738 self.assertEqual(expected, got)
3739
3740 # Now on the day DST ends, we want "repeat an hour" behavior.
3741 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3742 # EST 23:MM 0:MM 1:MM 2:MM
3743 # EDT 0:MM 1:MM 2:MM 3:MM
3744 # wall 0:MM 1:MM 1:MM 2:MM against these
3745 for utc in utc_real, utc_fake:
3746 for tz in Eastern, Pacific:
3747 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3748 # Convert that to UTC.
3749 first_std_hour -= tz.utcoffset(None)
3750 # Adjust for possibly fake UTC.
3751 asutc = first_std_hour + utc.utcoffset(None)
3752 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3753 # tz=Eastern.
3754 asutcbase = asutc.replace(tzinfo=utc)
3755 for tzhour in (0, 1, 1, 2):
3756 expectedbase = self.dstoff.replace(hour=tzhour)
3757 for minute in 0, 30, 59:
3758 expected = expectedbase.replace(minute=minute)
3759 asutc = asutcbase.replace(minute=minute)
3760 astz = asutc.astimezone(tz)
3761 self.assertEqual(astz.replace(tzinfo=None), expected)
3762 asutcbase += HOUR
3763
3764
3765 def test_bogus_dst(self):
3766 class ok(tzinfo):
3767 def utcoffset(self, dt): return HOUR
3768 def dst(self, dt): return HOUR
3769
3770 now = self.theclass.now().replace(tzinfo=utc_real)
3771 # Doesn't blow up.
3772 now.astimezone(ok())
3773
3774 # Does blow up.
3775 class notok(ok):
3776 def dst(self, dt): return None
3777 self.assertRaises(ValueError, now.astimezone, notok())
3778
3779 # Sometimes blow up. In the following, tzinfo.dst()
3780 # implementation may return None or not None depending on
3781 # whether DST is assumed to be in effect. In this situation,
3782 # a ValueError should be raised by astimezone().
3783 class tricky_notok(ok):
3784 def dst(self, dt):
3785 if dt.year == 2000:
3786 return None
3787 else:
3788 return 10*HOUR
3789 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3790 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3791
3792 def test_fromutc(self):
3793 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3794 now = datetime.utcnow().replace(tzinfo=utc_real)
3795 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3796 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3797 enow = Eastern.fromutc(now) # doesn't blow up
3798 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3799 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3800 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3801
3802 # Always converts UTC to standard time.
3803 class FauxUSTimeZone(USTimeZone):
3804 def fromutc(self, dt):
3805 return dt + self.stdoffset
3806 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3807
3808 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3809 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3810 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3811
3812 # Check around DST start.
3813 start = self.dston.replace(hour=4, tzinfo=Eastern)
3814 fstart = start.replace(tzinfo=FEastern)
3815 for wall in 23, 0, 1, 3, 4, 5:
3816 expected = start.replace(hour=wall)
3817 if wall == 23:
3818 expected -= timedelta(days=1)
3819 got = Eastern.fromutc(start)
3820 self.assertEqual(expected, got)
3821
3822 expected = fstart + FEastern.stdoffset
3823 got = FEastern.fromutc(fstart)
3824 self.assertEqual(expected, got)
3825
3826 # Ensure astimezone() calls fromutc() too.
3827 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3828 self.assertEqual(expected, got)
3829
3830 start += HOUR
3831 fstart += HOUR
3832
3833 # Check around DST end.
3834 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3835 fstart = start.replace(tzinfo=FEastern)
3836 for wall in 0, 1, 1, 2, 3, 4:
3837 expected = start.replace(hour=wall)
3838 got = Eastern.fromutc(start)
3839 self.assertEqual(expected, got)
3840
3841 expected = fstart + FEastern.stdoffset
3842 got = FEastern.fromutc(fstart)
3843 self.assertEqual(expected, got)
3844
3845 # Ensure astimezone() calls fromutc() too.
3846 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3847 self.assertEqual(expected, got)
3848
3849 start += HOUR
3850 fstart += HOUR
3851
3852
3853#############################################################################
3854# oddballs
3855
3856class Oddballs(unittest.TestCase):
3857
3858 def test_bug_1028306(self):
3859 # Trying to compare a date to a datetime should act like a mixed-
3860 # type comparison, despite that datetime is a subclass of date.
3861 as_date = date.today()
3862 as_datetime = datetime.combine(as_date, time())
3863 self.assertTrue(as_date != as_datetime)
3864 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003865 self.assertFalse(as_date == as_datetime)
3866 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003867 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3868 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3869 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3870 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3871 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3872 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3873 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3874 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3875
3876 # Neverthelss, comparison should work with the base-class (date)
3877 # projection if use of a date method is forced.
3878 self.assertEqual(as_date.__eq__(as_datetime), True)
3879 different_day = (as_date.day + 1) % 20 + 1
3880 as_different = as_datetime.replace(day= different_day)
3881 self.assertEqual(as_date.__eq__(as_different), False)
3882
3883 # And date should compare with other subclasses of date. If a
3884 # subclass wants to stop this, it's up to the subclass to do so.
3885 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3886 self.assertEqual(as_date, date_sc)
3887 self.assertEqual(date_sc, as_date)
3888
3889 # Ditto for datetimes.
3890 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3891 as_date.day, 0, 0, 0)
3892 self.assertEqual(as_datetime, datetime_sc)
3893 self.assertEqual(datetime_sc, as_datetime)
3894
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003895 def test_extra_attributes(self):
3896 for x in [date.today(),
3897 time(),
3898 datetime.utcnow(),
3899 timedelta(),
3900 tzinfo(),
3901 timezone(timedelta())]:
3902 with self.assertRaises(AttributeError):
3903 x.abc = 1
3904
3905 def test_check_arg_types(self):
Alexander Belopolsky6c7a4182014-09-28 19:11:56 -04003906 class Number:
3907 def __init__(self, value):
3908 self.value = value
3909 def __int__(self):
3910 return self.value
3911
3912 for xx in [decimal.Decimal(10),
3913 decimal.Decimal('10.9'),
3914 Number(10)]:
3915 self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
3916 datetime(xx, xx, xx, xx, xx, xx, xx))
3917
3918 with self.assertRaisesRegex(TypeError, '^an integer is required '
3919 '\(got type str\)$'):
3920 datetime(10, 10, '10')
3921
3922 f10 = Number(10.9)
3923 with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
3924 '\(type float\)$'):
3925 datetime(10, 10, f10)
3926
3927 class Float(float):
3928 pass
3929 s10 = Float(10.9)
3930 with self.assertRaisesRegex(TypeError, '^integer argument expected, '
3931 'got float$'):
3932 datetime(10, 10, s10)
3933
3934 with self.assertRaises(TypeError):
3935 datetime(10., 10, 10)
3936 with self.assertRaises(TypeError):
3937 datetime(10, 10., 10)
3938 with self.assertRaises(TypeError):
3939 datetime(10, 10, 10.)
3940 with self.assertRaises(TypeError):
3941 datetime(10, 10, 10, 10.)
3942 with self.assertRaises(TypeError):
3943 datetime(10, 10, 10, 10, 10.)
3944 with self.assertRaises(TypeError):
3945 datetime(10, 10, 10, 10, 10, 10.)
3946 with self.assertRaises(TypeError):
3947 datetime(10, 10, 10, 10, 10, 10, 10.)
3948
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003949if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -05003950 unittest.main()