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