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