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