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