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