blob: f42b0f907c5cedebd79cad6e2f646c3fa5473b0e [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)
Serhiy Storchakabad12572014-12-15 14:03:42 +02001685 for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1686 s = pickle.dumps(a, proto)
1687 b = pickle.loads(s)
1688 self.assertEqual(b.year, 2003)
1689 self.assertEqual(b.month, 2)
1690 self.assertEqual(b.day, 7)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001691
1692 def test_pickling_subclass_datetime(self):
1693 args = 6, 7, 23, 20, 59, 1, 64**2
1694 orig = SubclassDatetime(*args)
1695 for pickler, unpickler, proto in pickle_choices:
1696 green = pickler.dumps(orig, proto)
1697 derived = unpickler.loads(green)
1698 self.assertEqual(orig, derived)
1699
1700 def test_more_compare(self):
1701 # The test_compare() inherited from TestDate covers the error cases.
1702 # We just want to test lexicographic ordering on the members datetime
1703 # has that date lacks.
1704 args = [2000, 11, 29, 20, 58, 16, 999998]
1705 t1 = self.theclass(*args)
1706 t2 = self.theclass(*args)
1707 self.assertEqual(t1, t2)
1708 self.assertTrue(t1 <= t2)
1709 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001710 self.assertFalse(t1 != t2)
1711 self.assertFalse(t1 < t2)
1712 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001713
1714 for i in range(len(args)):
1715 newargs = args[:]
1716 newargs[i] = args[i] + 1
1717 t2 = self.theclass(*newargs) # this is larger than t1
1718 self.assertTrue(t1 < t2)
1719 self.assertTrue(t2 > t1)
1720 self.assertTrue(t1 <= t2)
1721 self.assertTrue(t2 >= t1)
1722 self.assertTrue(t1 != t2)
1723 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001724 self.assertFalse(t1 == t2)
1725 self.assertFalse(t2 == t1)
1726 self.assertFalse(t1 > t2)
1727 self.assertFalse(t2 < t1)
1728 self.assertFalse(t1 >= t2)
1729 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001730
1731
1732 # A helper for timestamp constructor tests.
1733 def verify_field_equality(self, expected, got):
1734 self.assertEqual(expected.tm_year, got.year)
1735 self.assertEqual(expected.tm_mon, got.month)
1736 self.assertEqual(expected.tm_mday, got.day)
1737 self.assertEqual(expected.tm_hour, got.hour)
1738 self.assertEqual(expected.tm_min, got.minute)
1739 self.assertEqual(expected.tm_sec, got.second)
1740
1741 def test_fromtimestamp(self):
1742 import time
1743
1744 ts = time.time()
1745 expected = time.localtime(ts)
1746 got = self.theclass.fromtimestamp(ts)
1747 self.verify_field_equality(expected, got)
1748
1749 def test_utcfromtimestamp(self):
1750 import time
1751
1752 ts = time.time()
1753 expected = time.gmtime(ts)
1754 got = self.theclass.utcfromtimestamp(ts)
1755 self.verify_field_equality(expected, got)
1756
Alexander Belopolskya4415142012-06-08 12:33:09 -04001757 # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1758 # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1759 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1760 def test_timestamp_naive(self):
1761 t = self.theclass(1970, 1, 1)
1762 self.assertEqual(t.timestamp(), 18000.0)
1763 t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1764 self.assertEqual(t.timestamp(),
1765 18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001766 # Missing hour may produce platform-dependent result
Alexander Belopolskya4415142012-06-08 12:33:09 -04001767 t = self.theclass(2012, 3, 11, 2, 30)
Alexander Belopolsky0c687e52012-06-08 12:58:31 -04001768 self.assertIn(self.theclass.fromtimestamp(t.timestamp()),
Alexander Belopolskyf6f56182012-06-08 13:00:27 -04001769 [t - timedelta(hours=1), t + timedelta(hours=1)])
Alexander Belopolskya4415142012-06-08 12:33:09 -04001770 # Ambiguous hour defaults to DST
1771 t = self.theclass(2012, 11, 4, 1, 30)
1772 self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1773
1774 # Timestamp may raise an overflow error on some platforms
1775 for t in [self.theclass(1,1,1), self.theclass(9999,12,12)]:
1776 try:
1777 s = t.timestamp()
1778 except OverflowError:
1779 pass
1780 else:
1781 self.assertEqual(self.theclass.fromtimestamp(s), t)
1782
1783 def test_timestamp_aware(self):
1784 t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1785 self.assertEqual(t.timestamp(), 0.0)
1786 t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1787 self.assertEqual(t.timestamp(),
1788 3600 + 2*60 + 3 + 4*1e-6)
1789 t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1790 tzinfo=timezone(timedelta(hours=-5), 'EST'))
1791 self.assertEqual(t.timestamp(),
1792 18000 + 3600 + 2*60 + 3 + 4*1e-6)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001793 def test_microsecond_rounding(self):
Alexander Belopolsky3e62f782010-09-21 16:30:56 +00001794 for fts in [self.theclass.fromtimestamp,
1795 self.theclass.utcfromtimestamp]:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001796 zero = fts(0)
1797 self.assertEqual(zero.second, 0)
1798 self.assertEqual(zero.microsecond, 0)
Victor Stinner8050ca92012-03-14 00:17:05 +01001799 try:
1800 minus_one = fts(-1e-6)
1801 except OSError:
1802 # localtime(-1) and gmtime(-1) is not supported on Windows
1803 pass
1804 else:
1805 self.assertEqual(minus_one.second, 59)
1806 self.assertEqual(minus_one.microsecond, 999999)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001807
Victor Stinner8050ca92012-03-14 00:17:05 +01001808 t = fts(-1e-8)
1809 self.assertEqual(t, minus_one)
1810 t = fts(-9e-7)
1811 self.assertEqual(t, minus_one)
1812 t = fts(-1e-7)
1813 self.assertEqual(t, minus_one)
Victor Stinner5d272cc2012-03-13 13:35:55 +01001814
1815 t = fts(1e-7)
1816 self.assertEqual(t, zero)
1817 t = fts(9e-7)
1818 self.assertEqual(t, zero)
1819 t = fts(0.99999949)
1820 self.assertEqual(t.second, 0)
1821 self.assertEqual(t.microsecond, 999999)
1822 t = fts(0.9999999)
1823 self.assertEqual(t.second, 0)
1824 self.assertEqual(t.microsecond, 999999)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001825
1826 def test_insane_fromtimestamp(self):
1827 # It's possible that some platform maps time_t to double,
1828 # and that this test will fail there. This test should
1829 # exempt such platforms (provided they return reasonable
1830 # results!).
1831 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001832 self.assertRaises(OverflowError, self.theclass.fromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001833 insane)
1834
1835 def test_insane_utcfromtimestamp(self):
1836 # It's possible that some platform maps time_t to double,
1837 # and that this test will fail there. This test should
1838 # exempt such platforms (provided they return reasonable
1839 # results!).
1840 for insane in -1e200, 1e200:
Victor Stinner5d272cc2012-03-13 13:35:55 +01001841 self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001842 insane)
1843 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1844 def test_negative_float_fromtimestamp(self):
1845 # The result is tz-dependent; at least test that this doesn't
1846 # fail (like it did before bug 1646728 was fixed).
1847 self.theclass.fromtimestamp(-1.05)
1848
1849 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1850 def test_negative_float_utcfromtimestamp(self):
1851 d = self.theclass.utcfromtimestamp(-1.05)
1852 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1853
1854 def test_utcnow(self):
1855 import time
1856
1857 # Call it a success if utcnow() and utcfromtimestamp() are within
1858 # a second of each other.
1859 tolerance = timedelta(seconds=1)
1860 for dummy in range(3):
1861 from_now = self.theclass.utcnow()
1862 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1863 if abs(from_timestamp - from_now) <= tolerance:
1864 break
1865 # Else try again a few times.
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02001866 self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001867
1868 def test_strptime(self):
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001869 string = '2004-12-01 13:02:47.197'
1870 format = '%Y-%m-%d %H:%M:%S.%f'
1871 expected = _strptime._strptime_datetime(self.theclass, string, format)
1872 got = self.theclass.strptime(string, format)
1873 self.assertEqual(expected, got)
1874 self.assertIs(type(expected), self.theclass)
1875 self.assertIs(type(got), self.theclass)
1876
1877 strptime = self.theclass.strptime
1878 self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1879 self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1880 # Only local timezone and UTC are supported
1881 for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1882 (-_time.timezone, _time.tzname[0])):
1883 if tzseconds < 0:
1884 sign = '-'
1885 seconds = -tzseconds
1886 else:
1887 sign ='+'
1888 seconds = tzseconds
1889 hours, minutes = divmod(seconds//60, 60)
1890 dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1891 dt = strptime(dtstr, "%z %Z")
1892 self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1893 self.assertEqual(dt.tzname(), tzname)
1894 # Can produce inconsistent datetime
1895 dtstr, fmt = "+1234 UTC", "%z %Z"
1896 dt = strptime(dtstr, fmt)
1897 self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1898 self.assertEqual(dt.tzname(), 'UTC')
1899 # yet will roundtrip
1900 self.assertEqual(dt.strftime(fmt), dtstr)
1901
1902 # Produce naive datetime if no %z is provided
1903 self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1904
1905 with self.assertRaises(ValueError): strptime("-2400", "%z")
1906 with self.assertRaises(ValueError): strptime("-000", "%z")
1907
1908 def test_more_timetuple(self):
1909 # This tests fields beyond those tested by the TestDate.test_timetuple.
1910 t = self.theclass(2004, 12, 31, 6, 22, 33)
1911 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1912 self.assertEqual(t.timetuple(),
1913 (t.year, t.month, t.day,
1914 t.hour, t.minute, t.second,
1915 t.weekday(),
1916 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1917 -1))
1918 tt = t.timetuple()
1919 self.assertEqual(tt.tm_year, t.year)
1920 self.assertEqual(tt.tm_mon, t.month)
1921 self.assertEqual(tt.tm_mday, t.day)
1922 self.assertEqual(tt.tm_hour, t.hour)
1923 self.assertEqual(tt.tm_min, t.minute)
1924 self.assertEqual(tt.tm_sec, t.second)
1925 self.assertEqual(tt.tm_wday, t.weekday())
1926 self.assertEqual(tt.tm_yday, t.toordinal() -
1927 date(t.year, 1, 1).toordinal() + 1)
1928 self.assertEqual(tt.tm_isdst, -1)
1929
1930 def test_more_strftime(self):
1931 # This tests fields beyond those tested by the TestDate.test_strftime.
1932 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1933 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1934 "12 31 04 000047 33 22 06 366")
1935
1936 def test_extract(self):
1937 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1938 self.assertEqual(dt.date(), date(2002, 3, 4))
1939 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1940
1941 def test_combine(self):
1942 d = date(2002, 3, 4)
1943 t = time(18, 45, 3, 1234)
1944 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1945 combine = self.theclass.combine
1946 dt = combine(d, t)
1947 self.assertEqual(dt, expected)
1948
1949 dt = combine(time=t, date=d)
1950 self.assertEqual(dt, expected)
1951
1952 self.assertEqual(d, dt.date())
1953 self.assertEqual(t, dt.time())
1954 self.assertEqual(dt, combine(dt.date(), dt.time()))
1955
1956 self.assertRaises(TypeError, combine) # need an arg
1957 self.assertRaises(TypeError, combine, d) # need two args
1958 self.assertRaises(TypeError, combine, t, d) # args reversed
1959 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1960 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1961 self.assertRaises(TypeError, combine, d, "time") # wrong type
1962 self.assertRaises(TypeError, combine, "date", t) # wrong type
1963
1964 def test_replace(self):
1965 cls = self.theclass
1966 args = [1, 2, 3, 4, 5, 6, 7]
1967 base = cls(*args)
1968 self.assertEqual(base, base.replace())
1969
1970 i = 0
1971 for name, newval in (("year", 2),
1972 ("month", 3),
1973 ("day", 4),
1974 ("hour", 5),
1975 ("minute", 6),
1976 ("second", 7),
1977 ("microsecond", 8)):
1978 newargs = args[:]
1979 newargs[i] = newval
1980 expected = cls(*newargs)
1981 got = base.replace(**{name: newval})
1982 self.assertEqual(expected, got)
1983 i += 1
1984
1985 # Out of bounds.
1986 base = cls(2000, 2, 29)
1987 self.assertRaises(ValueError, base.replace, year=2001)
1988
1989 def test_astimezone(self):
1990 # Pretty boring! The TZ test is more interesting here. astimezone()
1991 # simply can't be applied to a naive object.
1992 dt = self.theclass.now()
1993 f = FixedOffset(44, "")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04001994 self.assertRaises(ValueError, dt.astimezone) # naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00001995 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1996 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1997 self.assertRaises(ValueError, dt.astimezone, f) # naive
1998 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
1999
2000 class Bogus(tzinfo):
2001 def utcoffset(self, dt): return None
2002 def dst(self, dt): return timedelta(0)
2003 bog = Bogus()
2004 self.assertRaises(ValueError, dt.astimezone, bog) # naive
2005 self.assertRaises(ValueError,
2006 dt.replace(tzinfo=bog).astimezone, f)
2007
2008 class AlsoBogus(tzinfo):
2009 def utcoffset(self, dt): return timedelta(0)
2010 def dst(self, dt): return None
2011 alsobog = AlsoBogus()
2012 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2013
2014 def test_subclass_datetime(self):
2015
2016 class C(self.theclass):
2017 theAnswer = 42
2018
2019 def __new__(cls, *args, **kws):
2020 temp = kws.copy()
2021 extra = temp.pop('extra')
2022 result = self.theclass.__new__(cls, *args, **temp)
2023 result.extra = extra
2024 return result
2025
2026 def newmeth(self, start):
2027 return start + self.year + self.month + self.second
2028
2029 args = 2003, 4, 14, 12, 13, 41
2030
2031 dt1 = self.theclass(*args)
2032 dt2 = C(*args, **{'extra': 7})
2033
2034 self.assertEqual(dt2.__class__, C)
2035 self.assertEqual(dt2.theAnswer, 42)
2036 self.assertEqual(dt2.extra, 7)
2037 self.assertEqual(dt1.toordinal(), dt2.toordinal())
2038 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2039 dt1.second - 7)
2040
2041class TestSubclassDateTime(TestDateTime):
2042 theclass = SubclassDatetime
2043 # Override tests not designed for subclass
Zachary Ware9fe6d862013-12-08 00:20:35 -06002044 @unittest.skip('not appropriate for subclasses')
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002045 def test_roundtrip(self):
2046 pass
2047
2048class SubclassTime(time):
2049 sub_var = 1
2050
2051class TestTime(HarmlessMixedComparison, unittest.TestCase):
2052
2053 theclass = time
2054
2055 def test_basic_attributes(self):
2056 t = self.theclass(12, 0)
2057 self.assertEqual(t.hour, 12)
2058 self.assertEqual(t.minute, 0)
2059 self.assertEqual(t.second, 0)
2060 self.assertEqual(t.microsecond, 0)
2061
2062 def test_basic_attributes_nonzero(self):
2063 # Make sure all attributes are non-zero so bugs in
2064 # bit-shifting access show up.
2065 t = self.theclass(12, 59, 59, 8000)
2066 self.assertEqual(t.hour, 12)
2067 self.assertEqual(t.minute, 59)
2068 self.assertEqual(t.second, 59)
2069 self.assertEqual(t.microsecond, 8000)
2070
2071 def test_roundtrip(self):
2072 t = self.theclass(1, 2, 3, 4)
2073
2074 # Verify t -> string -> time identity.
2075 s = repr(t)
2076 self.assertTrue(s.startswith('datetime.'))
2077 s = s[9:]
2078 t2 = eval(s)
2079 self.assertEqual(t, t2)
2080
2081 # Verify identity via reconstructing from pieces.
2082 t2 = self.theclass(t.hour, t.minute, t.second,
2083 t.microsecond)
2084 self.assertEqual(t, t2)
2085
2086 def test_comparing(self):
2087 args = [1, 2, 3, 4]
2088 t1 = self.theclass(*args)
2089 t2 = self.theclass(*args)
2090 self.assertEqual(t1, t2)
2091 self.assertTrue(t1 <= t2)
2092 self.assertTrue(t1 >= t2)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002093 self.assertFalse(t1 != t2)
2094 self.assertFalse(t1 < t2)
2095 self.assertFalse(t1 > t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002096
2097 for i in range(len(args)):
2098 newargs = args[:]
2099 newargs[i] = args[i] + 1
2100 t2 = self.theclass(*newargs) # this is larger than t1
2101 self.assertTrue(t1 < t2)
2102 self.assertTrue(t2 > t1)
2103 self.assertTrue(t1 <= t2)
2104 self.assertTrue(t2 >= t1)
2105 self.assertTrue(t1 != t2)
2106 self.assertTrue(t2 != t1)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002107 self.assertFalse(t1 == t2)
2108 self.assertFalse(t2 == t1)
2109 self.assertFalse(t1 > t2)
2110 self.assertFalse(t2 < t1)
2111 self.assertFalse(t1 >= t2)
2112 self.assertFalse(t2 <= t1)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002113
2114 for badarg in OTHERSTUFF:
2115 self.assertEqual(t1 == badarg, False)
2116 self.assertEqual(t1 != badarg, True)
2117 self.assertEqual(badarg == t1, False)
2118 self.assertEqual(badarg != t1, True)
2119
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: t1 >= badarg)
2124 self.assertRaises(TypeError, lambda: badarg <= t1)
2125 self.assertRaises(TypeError, lambda: badarg < t1)
2126 self.assertRaises(TypeError, lambda: badarg > t1)
2127 self.assertRaises(TypeError, lambda: badarg >= t1)
2128
2129 def test_bad_constructor_arguments(self):
2130 # bad hours
2131 self.theclass(0, 0) # no exception
2132 self.theclass(23, 0) # no exception
2133 self.assertRaises(ValueError, self.theclass, -1, 0)
2134 self.assertRaises(ValueError, self.theclass, 24, 0)
2135 # bad minutes
2136 self.theclass(23, 0) # no exception
2137 self.theclass(23, 59) # no exception
2138 self.assertRaises(ValueError, self.theclass, 23, -1)
2139 self.assertRaises(ValueError, self.theclass, 23, 60)
2140 # bad seconds
2141 self.theclass(23, 59, 0) # no exception
2142 self.theclass(23, 59, 59) # no exception
2143 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2144 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2145 # bad microseconds
2146 self.theclass(23, 59, 59, 0) # no exception
2147 self.theclass(23, 59, 59, 999999) # no exception
2148 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2149 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2150
2151 def test_hash_equality(self):
2152 d = self.theclass(23, 30, 17)
2153 e = self.theclass(23, 30, 17)
2154 self.assertEqual(d, e)
2155 self.assertEqual(hash(d), hash(e))
2156
2157 dic = {d: 1}
2158 dic[e] = 2
2159 self.assertEqual(len(dic), 1)
2160 self.assertEqual(dic[d], 2)
2161 self.assertEqual(dic[e], 2)
2162
2163 d = self.theclass(0, 5, 17)
2164 e = self.theclass(0, 5, 17)
2165 self.assertEqual(d, e)
2166 self.assertEqual(hash(d), hash(e))
2167
2168 dic = {d: 1}
2169 dic[e] = 2
2170 self.assertEqual(len(dic), 1)
2171 self.assertEqual(dic[d], 2)
2172 self.assertEqual(dic[e], 2)
2173
2174 def test_isoformat(self):
2175 t = self.theclass(4, 5, 1, 123)
2176 self.assertEqual(t.isoformat(), "04:05:01.000123")
2177 self.assertEqual(t.isoformat(), str(t))
2178
2179 t = self.theclass()
2180 self.assertEqual(t.isoformat(), "00:00:00")
2181 self.assertEqual(t.isoformat(), str(t))
2182
2183 t = self.theclass(microsecond=1)
2184 self.assertEqual(t.isoformat(), "00:00:00.000001")
2185 self.assertEqual(t.isoformat(), str(t))
2186
2187 t = self.theclass(microsecond=10)
2188 self.assertEqual(t.isoformat(), "00:00:00.000010")
2189 self.assertEqual(t.isoformat(), str(t))
2190
2191 t = self.theclass(microsecond=100)
2192 self.assertEqual(t.isoformat(), "00:00:00.000100")
2193 self.assertEqual(t.isoformat(), str(t))
2194
2195 t = self.theclass(microsecond=1000)
2196 self.assertEqual(t.isoformat(), "00:00:00.001000")
2197 self.assertEqual(t.isoformat(), str(t))
2198
2199 t = self.theclass(microsecond=10000)
2200 self.assertEqual(t.isoformat(), "00:00:00.010000")
2201 self.assertEqual(t.isoformat(), str(t))
2202
2203 t = self.theclass(microsecond=100000)
2204 self.assertEqual(t.isoformat(), "00:00:00.100000")
2205 self.assertEqual(t.isoformat(), str(t))
2206
2207 def test_1653736(self):
2208 # verify it doesn't accept extra keyword arguments
2209 t = self.theclass(second=1)
2210 self.assertRaises(TypeError, t.isoformat, foo=3)
2211
2212 def test_strftime(self):
2213 t = self.theclass(1, 2, 3, 4)
2214 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2215 # A naive object replaces %z and %Z with empty strings.
2216 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2217
2218 def test_format(self):
2219 t = self.theclass(1, 2, 3, 4)
2220 self.assertEqual(t.__format__(''), str(t))
2221
2222 # check that a derived class's __str__() gets called
2223 class A(self.theclass):
2224 def __str__(self):
2225 return 'A'
2226 a = A(1, 2, 3, 4)
2227 self.assertEqual(a.__format__(''), 'A')
2228
2229 # check that a derived class's strftime gets called
2230 class B(self.theclass):
2231 def strftime(self, format_spec):
2232 return 'B'
2233 b = B(1, 2, 3, 4)
2234 self.assertEqual(b.__format__(''), str(t))
2235
2236 for fmt in ['%H %M %S',
2237 ]:
2238 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2239 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2240 self.assertEqual(b.__format__(fmt), 'B')
2241
2242 def test_str(self):
2243 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2244 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2245 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2246 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2247 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2248
2249 def test_repr(self):
2250 name = 'datetime.' + self.theclass.__name__
2251 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2252 "%s(1, 2, 3, 4)" % name)
2253 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2254 "%s(10, 2, 3, 4000)" % name)
2255 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2256 "%s(0, 2, 3, 400000)" % name)
2257 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2258 "%s(12, 2, 3)" % name)
2259 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2260 "%s(23, 15)" % name)
2261
2262 def test_resolution_info(self):
2263 self.assertIsInstance(self.theclass.min, self.theclass)
2264 self.assertIsInstance(self.theclass.max, self.theclass)
2265 self.assertIsInstance(self.theclass.resolution, timedelta)
2266 self.assertTrue(self.theclass.max > self.theclass.min)
2267
2268 def test_pickling(self):
2269 args = 20, 59, 16, 64**2
2270 orig = self.theclass(*args)
2271 for pickler, unpickler, proto in pickle_choices:
2272 green = pickler.dumps(orig, proto)
2273 derived = unpickler.loads(green)
2274 self.assertEqual(orig, derived)
2275
2276 def test_pickling_subclass_time(self):
2277 args = 20, 59, 16, 64**2
2278 orig = SubclassTime(*args)
2279 for pickler, unpickler, proto in pickle_choices:
2280 green = pickler.dumps(orig, proto)
2281 derived = unpickler.loads(green)
2282 self.assertEqual(orig, derived)
2283
2284 def test_bool(self):
2285 cls = self.theclass
2286 self.assertTrue(cls(1))
2287 self.assertTrue(cls(0, 1))
2288 self.assertTrue(cls(0, 0, 1))
2289 self.assertTrue(cls(0, 0, 0, 1))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002290 self.assertFalse(cls(0))
2291 self.assertFalse(cls())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002292
2293 def test_replace(self):
2294 cls = self.theclass
2295 args = [1, 2, 3, 4]
2296 base = cls(*args)
2297 self.assertEqual(base, base.replace())
2298
2299 i = 0
2300 for name, newval in (("hour", 5),
2301 ("minute", 6),
2302 ("second", 7),
2303 ("microsecond", 8)):
2304 newargs = args[:]
2305 newargs[i] = newval
2306 expected = cls(*newargs)
2307 got = base.replace(**{name: newval})
2308 self.assertEqual(expected, got)
2309 i += 1
2310
2311 # Out of bounds.
2312 base = cls(1)
2313 self.assertRaises(ValueError, base.replace, hour=24)
2314 self.assertRaises(ValueError, base.replace, minute=-1)
2315 self.assertRaises(ValueError, base.replace, second=100)
2316 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2317
2318 def test_subclass_time(self):
2319
2320 class C(self.theclass):
2321 theAnswer = 42
2322
2323 def __new__(cls, *args, **kws):
2324 temp = kws.copy()
2325 extra = temp.pop('extra')
2326 result = self.theclass.__new__(cls, *args, **temp)
2327 result.extra = extra
2328 return result
2329
2330 def newmeth(self, start):
2331 return start + self.hour + self.second
2332
2333 args = 4, 5, 6
2334
2335 dt1 = self.theclass(*args)
2336 dt2 = C(*args, **{'extra': 7})
2337
2338 self.assertEqual(dt2.__class__, C)
2339 self.assertEqual(dt2.theAnswer, 42)
2340 self.assertEqual(dt2.extra, 7)
2341 self.assertEqual(dt1.isoformat(), dt2.isoformat())
2342 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2343
2344 def test_backdoor_resistance(self):
2345 # see TestDate.test_backdoor_resistance().
2346 base = '2:59.0'
2347 for hour_byte in ' ', '9', chr(24), '\xff':
2348 self.assertRaises(TypeError, self.theclass,
2349 hour_byte + base[1:])
2350
2351# A mixin for classes with a tzinfo= argument. Subclasses must define
2352# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
2353# must be legit (which is true for time and datetime).
2354class TZInfoBase:
2355
2356 def test_argument_passing(self):
2357 cls = self.theclass
2358 # A datetime passes itself on, a time passes None.
2359 class introspective(tzinfo):
2360 def tzname(self, dt): return dt and "real" or "none"
2361 def utcoffset(self, dt):
2362 return timedelta(minutes = dt and 42 or -42)
2363 dst = utcoffset
2364
2365 obj = cls(1, 2, 3, tzinfo=introspective())
2366
2367 expected = cls is time and "none" or "real"
2368 self.assertEqual(obj.tzname(), expected)
2369
2370 expected = timedelta(minutes=(cls is time and -42 or 42))
2371 self.assertEqual(obj.utcoffset(), expected)
2372 self.assertEqual(obj.dst(), expected)
2373
2374 def test_bad_tzinfo_classes(self):
2375 cls = self.theclass
2376 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2377
2378 class NiceTry(object):
2379 def __init__(self): pass
2380 def utcoffset(self, dt): pass
2381 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2382
2383 class BetterTry(tzinfo):
2384 def __init__(self): pass
2385 def utcoffset(self, dt): pass
2386 b = BetterTry()
2387 t = cls(1, 1, 1, tzinfo=b)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002388 self.assertIs(t.tzinfo, b)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002389
2390 def test_utc_offset_out_of_bounds(self):
2391 class Edgy(tzinfo):
2392 def __init__(self, offset):
2393 self.offset = timedelta(minutes=offset)
2394 def utcoffset(self, dt):
2395 return self.offset
2396
2397 cls = self.theclass
2398 for offset, legit in ((-1440, False),
2399 (-1439, True),
2400 (1439, True),
2401 (1440, False)):
2402 if cls is time:
2403 t = cls(1, 2, 3, tzinfo=Edgy(offset))
2404 elif cls is datetime:
2405 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2406 else:
2407 assert 0, "impossible"
2408 if legit:
2409 aofs = abs(offset)
2410 h, m = divmod(aofs, 60)
2411 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2412 if isinstance(t, datetime):
2413 t = t.timetz()
2414 self.assertEqual(str(t), "01:02:03" + tag)
2415 else:
2416 self.assertRaises(ValueError, str, t)
2417
2418 def test_tzinfo_classes(self):
2419 cls = self.theclass
2420 class C1(tzinfo):
2421 def utcoffset(self, dt): return None
2422 def dst(self, dt): return None
2423 def tzname(self, dt): return None
2424 for t in (cls(1, 1, 1),
2425 cls(1, 1, 1, tzinfo=None),
2426 cls(1, 1, 1, tzinfo=C1())):
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002427 self.assertIsNone(t.utcoffset())
2428 self.assertIsNone(t.dst())
2429 self.assertIsNone(t.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002430
2431 class C3(tzinfo):
2432 def utcoffset(self, dt): return timedelta(minutes=-1439)
2433 def dst(self, dt): return timedelta(minutes=1439)
2434 def tzname(self, dt): return "aname"
2435 t = cls(1, 1, 1, tzinfo=C3())
2436 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2437 self.assertEqual(t.dst(), timedelta(minutes=1439))
2438 self.assertEqual(t.tzname(), "aname")
2439
2440 # Wrong types.
2441 class C4(tzinfo):
2442 def utcoffset(self, dt): return "aname"
2443 def dst(self, dt): return 7
2444 def tzname(self, dt): return 0
2445 t = cls(1, 1, 1, tzinfo=C4())
2446 self.assertRaises(TypeError, t.utcoffset)
2447 self.assertRaises(TypeError, t.dst)
2448 self.assertRaises(TypeError, t.tzname)
2449
2450 # Offset out of range.
2451 class C6(tzinfo):
2452 def utcoffset(self, dt): return timedelta(hours=-24)
2453 def dst(self, dt): return timedelta(hours=24)
2454 t = cls(1, 1, 1, tzinfo=C6())
2455 self.assertRaises(ValueError, t.utcoffset)
2456 self.assertRaises(ValueError, t.dst)
2457
2458 # Not a whole number of minutes.
2459 class C7(tzinfo):
2460 def utcoffset(self, dt): return timedelta(seconds=61)
2461 def dst(self, dt): return timedelta(microseconds=-81)
2462 t = cls(1, 1, 1, tzinfo=C7())
2463 self.assertRaises(ValueError, t.utcoffset)
2464 self.assertRaises(ValueError, t.dst)
2465
2466 def test_aware_compare(self):
2467 cls = self.theclass
2468
2469 # Ensure that utcoffset() gets ignored if the comparands have
2470 # the same tzinfo member.
2471 class OperandDependentOffset(tzinfo):
2472 def utcoffset(self, t):
2473 if t.minute < 10:
2474 # d0 and d1 equal after adjustment
2475 return timedelta(minutes=t.minute)
2476 else:
2477 # d2 off in the weeds
2478 return timedelta(minutes=59)
2479
2480 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2481 d0 = base.replace(minute=3)
2482 d1 = base.replace(minute=9)
2483 d2 = base.replace(minute=11)
2484 for x in d0, d1, d2:
2485 for y in d0, d1, d2:
2486 for op in lt, le, gt, ge, eq, ne:
2487 got = op(x, y)
2488 expected = op(x.minute, y.minute)
2489 self.assertEqual(got, expected)
2490
2491 # However, if they're different members, uctoffset is not ignored.
2492 # Note that a time can't actually have an operand-depedent offset,
2493 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2494 # so skip this test for time.
2495 if cls is not time:
2496 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2497 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2498 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2499 for x in d0, d1, d2:
2500 for y in d0, d1, d2:
2501 got = (x > y) - (x < y)
2502 if (x is d0 or x is d1) and (y is d0 or y is d1):
2503 expected = 0
2504 elif x is y is d2:
2505 expected = 0
2506 elif x is d2:
2507 expected = -1
2508 else:
2509 assert y is d2
2510 expected = 1
2511 self.assertEqual(got, expected)
2512
2513
2514# Testing time objects with a non-None tzinfo.
2515class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2516 theclass = time
2517
2518 def test_empty(self):
2519 t = self.theclass()
2520 self.assertEqual(t.hour, 0)
2521 self.assertEqual(t.minute, 0)
2522 self.assertEqual(t.second, 0)
2523 self.assertEqual(t.microsecond, 0)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002524 self.assertIsNone(t.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002525
2526 def test_zones(self):
2527 est = FixedOffset(-300, "EST", 1)
2528 utc = FixedOffset(0, "UTC", -2)
2529 met = FixedOffset(60, "MET", 3)
2530 t1 = time( 7, 47, tzinfo=est)
2531 t2 = time(12, 47, tzinfo=utc)
2532 t3 = time(13, 47, tzinfo=met)
2533 t4 = time(microsecond=40)
2534 t5 = time(microsecond=40, tzinfo=utc)
2535
2536 self.assertEqual(t1.tzinfo, est)
2537 self.assertEqual(t2.tzinfo, utc)
2538 self.assertEqual(t3.tzinfo, met)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002539 self.assertIsNone(t4.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002540 self.assertEqual(t5.tzinfo, utc)
2541
2542 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2543 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2544 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002545 self.assertIsNone(t4.utcoffset())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002546 self.assertRaises(TypeError, t1.utcoffset, "no args")
2547
2548 self.assertEqual(t1.tzname(), "EST")
2549 self.assertEqual(t2.tzname(), "UTC")
2550 self.assertEqual(t3.tzname(), "MET")
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002551 self.assertIsNone(t4.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002552 self.assertRaises(TypeError, t1.tzname, "no args")
2553
2554 self.assertEqual(t1.dst(), timedelta(minutes=1))
2555 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2556 self.assertEqual(t3.dst(), timedelta(minutes=3))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002557 self.assertIsNone(t4.dst())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002558 self.assertRaises(TypeError, t1.dst, "no args")
2559
2560 self.assertEqual(hash(t1), hash(t2))
2561 self.assertEqual(hash(t1), hash(t3))
2562 self.assertEqual(hash(t2), hash(t3))
2563
2564 self.assertEqual(t1, t2)
2565 self.assertEqual(t1, t3)
2566 self.assertEqual(t2, t3)
Alexander Belopolsky08313822012-06-15 20:19:47 -04002567 self.assertNotEqual(t4, t5) # mixed tz-aware & naive
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002568 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2569 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2570
2571 self.assertEqual(str(t1), "07:47:00-05:00")
2572 self.assertEqual(str(t2), "12:47:00+00:00")
2573 self.assertEqual(str(t3), "13:47:00+01:00")
2574 self.assertEqual(str(t4), "00:00:00.000040")
2575 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2576
2577 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2578 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2579 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2580 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2581 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2582
2583 d = 'datetime.time'
2584 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2585 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2586 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2587 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2588 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2589
2590 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2591 "07:47:00 %Z=EST %z=-0500")
2592 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2593 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2594
2595 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2596 t1 = time(23, 59, tzinfo=yuck)
2597 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2598 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2599
2600 # Check that an invalid tzname result raises an exception.
2601 class Badtzname(tzinfo):
Alexander Belopolskye239d232010-12-08 23:31:48 +00002602 tz = 42
2603 def tzname(self, dt): return self.tz
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002604 t = time(2, 3, 4, tzinfo=Badtzname())
2605 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2606 self.assertRaises(TypeError, t.strftime, "%Z")
2607
Alexander Belopolskye239d232010-12-08 23:31:48 +00002608 # Issue #6697:
2609 if '_Fast' in str(type(self)):
2610 Badtzname.tz = '\ud800'
2611 self.assertRaises(ValueError, t.strftime, "%Z")
2612
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002613 def test_hash_edge_cases(self):
2614 # Offsets that overflow a basic time.
2615 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2616 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2617 self.assertEqual(hash(t1), hash(t2))
2618
2619 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2620 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2621 self.assertEqual(hash(t1), hash(t2))
2622
2623 def test_pickling(self):
2624 # Try one without a tzinfo.
2625 args = 20, 59, 16, 64**2
2626 orig = self.theclass(*args)
2627 for pickler, unpickler, proto in pickle_choices:
2628 green = pickler.dumps(orig, proto)
2629 derived = unpickler.loads(green)
2630 self.assertEqual(orig, derived)
2631
2632 # Try one with a tzinfo.
2633 tinfo = PicklableFixedOffset(-300, 'cookie')
2634 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2635 for pickler, unpickler, proto in pickle_choices:
2636 green = pickler.dumps(orig, proto)
2637 derived = unpickler.loads(green)
2638 self.assertEqual(orig, derived)
2639 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2640 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2641 self.assertEqual(derived.tzname(), 'cookie')
2642
2643 def test_more_bool(self):
2644 # Test cases with non-None tzinfo.
2645 cls = self.theclass
2646
2647 t = cls(0, tzinfo=FixedOffset(-300, ""))
2648 self.assertTrue(t)
2649
2650 t = cls(5, tzinfo=FixedOffset(-300, ""))
2651 self.assertTrue(t)
2652
2653 t = cls(5, tzinfo=FixedOffset(300, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002654 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002655
2656 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002657 self.assertFalse(t)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002658
2659 # Mostly ensuring this doesn't overflow internally.
2660 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2661 self.assertTrue(t)
2662
2663 # But this should yield a value error -- the utcoffset is bogus.
2664 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2665 self.assertRaises(ValueError, lambda: bool(t))
2666
2667 # Likewise.
2668 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2669 self.assertRaises(ValueError, lambda: bool(t))
2670
2671 def test_replace(self):
2672 cls = self.theclass
2673 z100 = FixedOffset(100, "+100")
2674 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2675 args = [1, 2, 3, 4, z100]
2676 base = cls(*args)
2677 self.assertEqual(base, base.replace())
2678
2679 i = 0
2680 for name, newval in (("hour", 5),
2681 ("minute", 6),
2682 ("second", 7),
2683 ("microsecond", 8),
2684 ("tzinfo", zm200)):
2685 newargs = args[:]
2686 newargs[i] = newval
2687 expected = cls(*newargs)
2688 got = base.replace(**{name: newval})
2689 self.assertEqual(expected, got)
2690 i += 1
2691
2692 # Ensure we can get rid of a tzinfo.
2693 self.assertEqual(base.tzname(), "+100")
2694 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002695 self.assertIsNone(base2.tzinfo)
2696 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002697
2698 # Ensure we can add one.
2699 base3 = base2.replace(tzinfo=z100)
2700 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002701 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002702
2703 # Out of bounds.
2704 base = cls(1)
2705 self.assertRaises(ValueError, base.replace, hour=24)
2706 self.assertRaises(ValueError, base.replace, minute=-1)
2707 self.assertRaises(ValueError, base.replace, second=100)
2708 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2709
2710 def test_mixed_compare(self):
2711 t1 = time(1, 2, 3)
2712 t2 = time(1, 2, 3)
2713 self.assertEqual(t1, t2)
2714 t2 = t2.replace(tzinfo=None)
2715 self.assertEqual(t1, t2)
2716 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2717 self.assertEqual(t1, t2)
2718 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04002719 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002720
2721 # In time w/ identical tzinfo objects, utcoffset is ignored.
2722 class Varies(tzinfo):
2723 def __init__(self):
2724 self.offset = timedelta(minutes=22)
2725 def utcoffset(self, t):
2726 self.offset += timedelta(minutes=1)
2727 return self.offset
2728
2729 v = Varies()
2730 t1 = t2.replace(tzinfo=v)
2731 t2 = t2.replace(tzinfo=v)
2732 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2733 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2734 self.assertEqual(t1, t2)
2735
2736 # But if they're not identical, it isn't ignored.
2737 t2 = t2.replace(tzinfo=Varies())
2738 self.assertTrue(t1 < t2) # t1's offset counter still going up
2739
2740 def test_subclass_timetz(self):
2741
2742 class C(self.theclass):
2743 theAnswer = 42
2744
2745 def __new__(cls, *args, **kws):
2746 temp = kws.copy()
2747 extra = temp.pop('extra')
2748 result = self.theclass.__new__(cls, *args, **temp)
2749 result.extra = extra
2750 return result
2751
2752 def newmeth(self, start):
2753 return start + self.hour + self.second
2754
2755 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2756
2757 dt1 = self.theclass(*args)
2758 dt2 = C(*args, **{'extra': 7})
2759
2760 self.assertEqual(dt2.__class__, C)
2761 self.assertEqual(dt2.theAnswer, 42)
2762 self.assertEqual(dt2.extra, 7)
2763 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2764 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2765
2766
2767# Testing datetime objects with a non-None tzinfo.
2768
2769class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2770 theclass = datetime
2771
2772 def test_trivial(self):
2773 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2774 self.assertEqual(dt.year, 1)
2775 self.assertEqual(dt.month, 2)
2776 self.assertEqual(dt.day, 3)
2777 self.assertEqual(dt.hour, 4)
2778 self.assertEqual(dt.minute, 5)
2779 self.assertEqual(dt.second, 6)
2780 self.assertEqual(dt.microsecond, 7)
2781 self.assertEqual(dt.tzinfo, None)
2782
2783 def test_even_more_compare(self):
2784 # The test_compare() and test_more_compare() inherited from TestDate
2785 # and TestDateTime covered non-tzinfo cases.
2786
2787 # Smallest possible after UTC adjustment.
2788 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2789 # Largest possible after UTC adjustment.
2790 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2791 tzinfo=FixedOffset(-1439, ""))
2792
2793 # Make sure those compare correctly, and w/o overflow.
2794 self.assertTrue(t1 < t2)
2795 self.assertTrue(t1 != t2)
2796 self.assertTrue(t2 > t1)
2797
2798 self.assertEqual(t1, t1)
2799 self.assertEqual(t2, t2)
2800
2801 # Equal afer adjustment.
2802 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2803 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2804 self.assertEqual(t1, t2)
2805
2806 # Change t1 not to subtract a minute, and t1 should be larger.
2807 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2808 self.assertTrue(t1 > t2)
2809
2810 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2811 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2812 self.assertTrue(t1 < t2)
2813
2814 # Back to the original t1, but make seconds resolve it.
2815 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2816 second=1)
2817 self.assertTrue(t1 > t2)
2818
2819 # Likewise, but make microseconds resolve it.
2820 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2821 microsecond=1)
2822 self.assertTrue(t1 > t2)
2823
Alexander Belopolsky08313822012-06-15 20:19:47 -04002824 # Make t2 naive and it should differ.
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002825 t2 = self.theclass.min
Alexander Belopolsky08313822012-06-15 20:19:47 -04002826 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002827 self.assertEqual(t2, t2)
2828
2829 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2830 class Naive(tzinfo):
2831 def utcoffset(self, dt): return None
2832 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
Alexander Belopolsky08313822012-06-15 20:19:47 -04002833 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002834 self.assertEqual(t2, t2)
2835
2836 # OTOH, it's OK to compare two of these mixing the two ways of being
2837 # naive.
2838 t1 = self.theclass(5, 6, 7)
2839 self.assertEqual(t1, t2)
2840
2841 # Try a bogus uctoffset.
2842 class Bogus(tzinfo):
2843 def utcoffset(self, dt):
2844 return timedelta(minutes=1440) # out of bounds
2845 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2846 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2847 self.assertRaises(ValueError, lambda: t1 == t2)
2848
2849 def test_pickling(self):
2850 # Try one without a tzinfo.
2851 args = 6, 7, 23, 20, 59, 1, 64**2
2852 orig = self.theclass(*args)
2853 for pickler, unpickler, proto in pickle_choices:
2854 green = pickler.dumps(orig, proto)
2855 derived = unpickler.loads(green)
2856 self.assertEqual(orig, derived)
2857
2858 # Try one with a tzinfo.
2859 tinfo = PicklableFixedOffset(-300, 'cookie')
2860 orig = self.theclass(*args, **{'tzinfo': tinfo})
2861 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2862 for pickler, unpickler, proto in pickle_choices:
2863 green = pickler.dumps(orig, proto)
2864 derived = unpickler.loads(green)
2865 self.assertEqual(orig, derived)
2866 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2867 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2868 self.assertEqual(derived.tzname(), 'cookie')
2869
2870 def test_extreme_hashes(self):
2871 # If an attempt is made to hash these via subtracting the offset
2872 # then hashing a datetime object, OverflowError results. The
2873 # Python implementation used to blow up here.
2874 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2875 hash(t)
2876 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2877 tzinfo=FixedOffset(-1439, ""))
2878 hash(t)
2879
2880 # OTOH, an OOB offset should blow up.
2881 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2882 self.assertRaises(ValueError, hash, t)
2883
2884 def test_zones(self):
2885 est = FixedOffset(-300, "EST")
2886 utc = FixedOffset(0, "UTC")
2887 met = FixedOffset(60, "MET")
2888 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2889 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2890 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2891 self.assertEqual(t1.tzinfo, est)
2892 self.assertEqual(t2.tzinfo, utc)
2893 self.assertEqual(t3.tzinfo, met)
2894 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2895 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2896 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2897 self.assertEqual(t1.tzname(), "EST")
2898 self.assertEqual(t2.tzname(), "UTC")
2899 self.assertEqual(t3.tzname(), "MET")
2900 self.assertEqual(hash(t1), hash(t2))
2901 self.assertEqual(hash(t1), hash(t3))
2902 self.assertEqual(hash(t2), hash(t3))
2903 self.assertEqual(t1, t2)
2904 self.assertEqual(t1, t3)
2905 self.assertEqual(t2, t3)
2906 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2907 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2908 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2909 d = 'datetime.datetime(2002, 3, 19, '
2910 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2911 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2912 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2913
2914 def test_combine(self):
2915 met = FixedOffset(60, "MET")
2916 d = date(2002, 3, 4)
2917 tz = time(18, 45, 3, 1234, tzinfo=met)
2918 dt = datetime.combine(d, tz)
2919 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2920 tzinfo=met))
2921
2922 def test_extract(self):
2923 met = FixedOffset(60, "MET")
2924 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2925 self.assertEqual(dt.date(), date(2002, 3, 4))
2926 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2927 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2928
2929 def test_tz_aware_arithmetic(self):
2930 import random
2931
2932 now = self.theclass.now()
2933 tz55 = FixedOffset(-330, "west 5:30")
2934 timeaware = now.time().replace(tzinfo=tz55)
2935 nowaware = self.theclass.combine(now.date(), timeaware)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002936 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002937 self.assertEqual(nowaware.timetz(), timeaware)
2938
2939 # Can't mix aware and non-aware.
2940 self.assertRaises(TypeError, lambda: now - nowaware)
2941 self.assertRaises(TypeError, lambda: nowaware - now)
2942
2943 # And adding datetime's doesn't make sense, aware or not.
2944 self.assertRaises(TypeError, lambda: now + nowaware)
2945 self.assertRaises(TypeError, lambda: nowaware + now)
2946 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2947
2948 # Subtracting should yield 0.
2949 self.assertEqual(now - now, timedelta(0))
2950 self.assertEqual(nowaware - nowaware, timedelta(0))
2951
2952 # Adding a delta should preserve tzinfo.
2953 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2954 nowawareplus = nowaware + delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002955 self.assertIs(nowaware.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002956 nowawareplus2 = delta + nowaware
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002957 self.assertIs(nowawareplus2.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002958 self.assertEqual(nowawareplus, nowawareplus2)
2959
2960 # that - delta should be what we started with, and that - what we
2961 # started with should be delta.
2962 diff = nowawareplus - delta
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002963 self.assertIs(diff.tzinfo, tz55)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002964 self.assertEqual(nowaware, diff)
2965 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2966 self.assertEqual(nowawareplus - nowaware, delta)
2967
2968 # Make up a random timezone.
2969 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2970 # Attach it to nowawareplus.
2971 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02002972 self.assertIs(nowawareplus.tzinfo, tzr)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00002973 # Make sure the difference takes the timezone adjustments into account.
2974 got = nowaware - nowawareplus
2975 # Expected: (nowaware base - nowaware offset) -
2976 # (nowawareplus base - nowawareplus offset) =
2977 # (nowaware base - nowawareplus base) +
2978 # (nowawareplus offset - nowaware offset) =
2979 # -delta + nowawareplus offset - nowaware offset
2980 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2981 self.assertEqual(got, expected)
2982
2983 # Try max possible difference.
2984 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2985 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2986 tzinfo=FixedOffset(-1439, "max"))
2987 maxdiff = max - min
2988 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2989 timedelta(minutes=2*1439))
2990 # Different tzinfo, but the same offset
2991 tza = timezone(HOUR, 'A')
2992 tzb = timezone(HOUR, 'B')
2993 delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
2994 self.assertEqual(delta, self.theclass.min - self.theclass.max)
2995
2996 def test_tzinfo_now(self):
2997 meth = self.theclass.now
2998 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2999 base = meth()
3000 # Try with and without naming the keyword.
3001 off42 = FixedOffset(42, "42")
3002 another = meth(off42)
3003 again = meth(tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003004 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003005 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3006 # Bad argument with and w/o naming the keyword.
3007 self.assertRaises(TypeError, meth, 16)
3008 self.assertRaises(TypeError, meth, tzinfo=16)
3009 # Bad keyword name.
3010 self.assertRaises(TypeError, meth, tinfo=off42)
3011 # Too many args.
3012 self.assertRaises(TypeError, meth, off42, off42)
3013
3014 # We don't know which time zone we're in, and don't have a tzinfo
3015 # class to represent it, so seeing whether a tz argument actually
3016 # does a conversion is tricky.
3017 utc = FixedOffset(0, "utc", 0)
3018 for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3019 timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3020 for dummy in range(3):
3021 now = datetime.now(weirdtz)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003022 self.assertIs(now.tzinfo, weirdtz)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003023 utcnow = datetime.utcnow().replace(tzinfo=utc)
3024 now2 = utcnow.astimezone(weirdtz)
3025 if abs(now - now2) < timedelta(seconds=30):
3026 break
3027 # Else the code is broken, or more than 30 seconds passed between
3028 # calls; assuming the latter, just try again.
3029 else:
3030 # Three strikes and we're out.
3031 self.fail("utcnow(), now(tz), or astimezone() may be broken")
3032
3033 def test_tzinfo_fromtimestamp(self):
3034 import time
3035 meth = self.theclass.fromtimestamp
3036 ts = time.time()
3037 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3038 base = meth(ts)
3039 # Try with and without naming the keyword.
3040 off42 = FixedOffset(42, "42")
3041 another = meth(ts, off42)
3042 again = meth(ts, tz=off42)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003043 self.assertIs(another.tzinfo, again.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003044 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3045 # Bad argument with and w/o naming the keyword.
3046 self.assertRaises(TypeError, meth, ts, 16)
3047 self.assertRaises(TypeError, meth, ts, tzinfo=16)
3048 # Bad keyword name.
3049 self.assertRaises(TypeError, meth, ts, tinfo=off42)
3050 # Too many args.
3051 self.assertRaises(TypeError, meth, ts, off42, off42)
3052 # Too few args.
3053 self.assertRaises(TypeError, meth)
3054
3055 # Try to make sure tz= actually does some conversion.
3056 timestamp = 1000000000
3057 utcdatetime = datetime.utcfromtimestamp(timestamp)
3058 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3059 # But on some flavor of Mac, it's nowhere near that. So we can't have
3060 # any idea here what time that actually is, we can only test that
3061 # relative changes match.
3062 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3063 tz = FixedOffset(utcoffset, "tz", 0)
3064 expected = utcdatetime + utcoffset
3065 got = datetime.fromtimestamp(timestamp, tz)
3066 self.assertEqual(expected, got.replace(tzinfo=None))
3067
3068 def test_tzinfo_utcnow(self):
3069 meth = self.theclass.utcnow
3070 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3071 base = meth()
3072 # Try with and without naming the keyword; for whatever reason,
3073 # utcnow() doesn't accept a tzinfo argument.
3074 off42 = FixedOffset(42, "42")
3075 self.assertRaises(TypeError, meth, off42)
3076 self.assertRaises(TypeError, meth, tzinfo=off42)
3077
3078 def test_tzinfo_utcfromtimestamp(self):
3079 import time
3080 meth = self.theclass.utcfromtimestamp
3081 ts = time.time()
3082 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3083 base = meth(ts)
3084 # Try with and without naming the keyword; for whatever reason,
3085 # utcfromtimestamp() doesn't accept a tzinfo argument.
3086 off42 = FixedOffset(42, "42")
3087 self.assertRaises(TypeError, meth, ts, off42)
3088 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3089
3090 def test_tzinfo_timetuple(self):
3091 # TestDateTime tested most of this. datetime adds a twist to the
3092 # DST flag.
3093 class DST(tzinfo):
3094 def __init__(self, dstvalue):
3095 if isinstance(dstvalue, int):
3096 dstvalue = timedelta(minutes=dstvalue)
3097 self.dstvalue = dstvalue
3098 def dst(self, dt):
3099 return self.dstvalue
3100
3101 cls = self.theclass
3102 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3103 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3104 t = d.timetuple()
3105 self.assertEqual(1, t.tm_year)
3106 self.assertEqual(1, t.tm_mon)
3107 self.assertEqual(1, t.tm_mday)
3108 self.assertEqual(10, t.tm_hour)
3109 self.assertEqual(20, t.tm_min)
3110 self.assertEqual(30, t.tm_sec)
3111 self.assertEqual(0, t.tm_wday)
3112 self.assertEqual(1, t.tm_yday)
3113 self.assertEqual(flag, t.tm_isdst)
3114
3115 # dst() returns wrong type.
3116 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3117
3118 # dst() at the edge.
3119 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3120 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3121
3122 # dst() out of range.
3123 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3124 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3125
3126 def test_utctimetuple(self):
3127 class DST(tzinfo):
3128 def __init__(self, dstvalue=0):
3129 if isinstance(dstvalue, int):
3130 dstvalue = timedelta(minutes=dstvalue)
3131 self.dstvalue = dstvalue
3132 def dst(self, dt):
3133 return self.dstvalue
3134
3135 cls = self.theclass
3136 # This can't work: DST didn't implement utcoffset.
3137 self.assertRaises(NotImplementedError,
3138 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3139
3140 class UOFS(DST):
3141 def __init__(self, uofs, dofs=None):
3142 DST.__init__(self, dofs)
3143 self.uofs = timedelta(minutes=uofs)
3144 def utcoffset(self, dt):
3145 return self.uofs
3146
3147 for dstvalue in -33, 33, 0, None:
3148 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3149 t = d.utctimetuple()
3150 self.assertEqual(d.year, t.tm_year)
3151 self.assertEqual(d.month, t.tm_mon)
3152 self.assertEqual(d.day, t.tm_mday)
3153 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3154 self.assertEqual(13, t.tm_min)
3155 self.assertEqual(d.second, t.tm_sec)
3156 self.assertEqual(d.weekday(), t.tm_wday)
3157 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3158 t.tm_yday)
3159 # Ensure tm_isdst is 0 regardless of what dst() says: DST
3160 # is never in effect for a UTC time.
3161 self.assertEqual(0, t.tm_isdst)
3162
3163 # For naive datetime, utctimetuple == timetuple except for isdst
3164 d = cls(1, 2, 3, 10, 20, 30, 40)
3165 t = d.utctimetuple()
3166 self.assertEqual(t[:-1], d.timetuple()[:-1])
3167 self.assertEqual(0, t.tm_isdst)
3168 # Same if utcoffset is None
3169 class NOFS(DST):
3170 def utcoffset(self, dt):
3171 return None
3172 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3173 t = d.utctimetuple()
3174 self.assertEqual(t[:-1], d.timetuple()[:-1])
3175 self.assertEqual(0, t.tm_isdst)
3176 # Check that bad tzinfo is detected
3177 class BOFS(DST):
3178 def utcoffset(self, dt):
3179 return "EST"
3180 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3181 self.assertRaises(TypeError, d.utctimetuple)
3182
3183 # Check that utctimetuple() is the same as
3184 # astimezone(utc).timetuple()
3185 d = cls(2010, 11, 13, 14, 15, 16, 171819)
3186 for tz in [timezone.min, timezone.utc, timezone.max]:
3187 dtz = d.replace(tzinfo=tz)
3188 self.assertEqual(dtz.utctimetuple()[:-1],
3189 dtz.astimezone(timezone.utc).timetuple()[:-1])
3190 # At the edges, UTC adjustment can produce years out-of-range
3191 # for a datetime object. Ensure that an OverflowError is
3192 # raised.
3193 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3194 # That goes back 1 minute less than a full day.
3195 self.assertRaises(OverflowError, tiny.utctimetuple)
3196
3197 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3198 # That goes forward 1 minute less than a full day.
3199 self.assertRaises(OverflowError, huge.utctimetuple)
3200 # More overflow cases
3201 tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3202 self.assertRaises(OverflowError, tiny.utctimetuple)
3203 huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3204 self.assertRaises(OverflowError, huge.utctimetuple)
3205
3206 def test_tzinfo_isoformat(self):
3207 zero = FixedOffset(0, "+00:00")
3208 plus = FixedOffset(220, "+03:40")
3209 minus = FixedOffset(-231, "-03:51")
3210 unknown = FixedOffset(None, "")
3211
3212 cls = self.theclass
3213 datestr = '0001-02-03'
3214 for ofs in None, zero, plus, minus, unknown:
3215 for us in 0, 987001:
3216 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3217 timestr = '04:05:59' + (us and '.987001' or '')
3218 ofsstr = ofs is not None and d.tzname() or ''
3219 tailstr = timestr + ofsstr
3220 iso = d.isoformat()
3221 self.assertEqual(iso, datestr + 'T' + tailstr)
3222 self.assertEqual(iso, d.isoformat('T'))
3223 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3224 self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3225 self.assertEqual(str(d), datestr + ' ' + tailstr)
3226
3227 def test_replace(self):
3228 cls = self.theclass
3229 z100 = FixedOffset(100, "+100")
3230 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3231 args = [1, 2, 3, 4, 5, 6, 7, z100]
3232 base = cls(*args)
3233 self.assertEqual(base, base.replace())
3234
3235 i = 0
3236 for name, newval in (("year", 2),
3237 ("month", 3),
3238 ("day", 4),
3239 ("hour", 5),
3240 ("minute", 6),
3241 ("second", 7),
3242 ("microsecond", 8),
3243 ("tzinfo", zm200)):
3244 newargs = args[:]
3245 newargs[i] = newval
3246 expected = cls(*newargs)
3247 got = base.replace(**{name: newval})
3248 self.assertEqual(expected, got)
3249 i += 1
3250
3251 # Ensure we can get rid of a tzinfo.
3252 self.assertEqual(base.tzname(), "+100")
3253 base2 = base.replace(tzinfo=None)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003254 self.assertIsNone(base2.tzinfo)
3255 self.assertIsNone(base2.tzname())
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003256
3257 # Ensure we can add one.
3258 base3 = base2.replace(tzinfo=z100)
3259 self.assertEqual(base, base3)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003260 self.assertIs(base.tzinfo, base3.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003261
3262 # Out of bounds.
3263 base = cls(2000, 2, 29)
3264 self.assertRaises(ValueError, base.replace, year=2001)
3265
3266 def test_more_astimezone(self):
3267 # The inherited test_astimezone covered some trivial and error cases.
3268 fnone = FixedOffset(None, "None")
3269 f44m = FixedOffset(44, "44")
3270 fm5h = FixedOffset(-timedelta(hours=5), "m300")
3271
3272 dt = self.theclass.now(tz=f44m)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003273 self.assertIs(dt.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003274 # Replacing with degenerate tzinfo raises an exception.
3275 self.assertRaises(ValueError, dt.astimezone, fnone)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003276 # Replacing with same tzinfo makes no change.
3277 x = dt.astimezone(dt.tzinfo)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003278 self.assertIs(x.tzinfo, f44m)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003279 self.assertEqual(x.date(), dt.date())
3280 self.assertEqual(x.time(), dt.time())
3281
3282 # Replacing with different tzinfo does adjust.
3283 got = dt.astimezone(fm5h)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003284 self.assertIs(got.tzinfo, fm5h)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003285 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3286 expected = dt - dt.utcoffset() # in effect, convert to UTC
3287 expected += fm5h.utcoffset(dt) # and from there to local time
3288 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3289 self.assertEqual(got.date(), expected.date())
3290 self.assertEqual(got.time(), expected.time())
3291 self.assertEqual(got.timetz(), expected.timetz())
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003292 self.assertIs(got.tzinfo, expected.tzinfo)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003293 self.assertEqual(got, expected)
3294
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003295 @support.run_with_tz('UTC')
3296 def test_astimezone_default_utc(self):
3297 dt = self.theclass.now(timezone.utc)
3298 self.assertEqual(dt.astimezone(None), dt)
3299 self.assertEqual(dt.astimezone(), dt)
3300
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003301 # Note that offset in TZ variable has the opposite sign to that
3302 # produced by %z directive.
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003303 @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3304 def test_astimezone_default_eastern(self):
3305 dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3306 local = dt.astimezone()
3307 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003308 self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003309 dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3310 local = dt.astimezone()
3311 self.assertEqual(dt, local)
Alexander Belopolsky93c9cd02012-06-22 16:04:19 -04003312 self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
Alexander Belopolskyfdc860f2012-06-22 12:23:23 -04003313
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003314 def test_aware_subtract(self):
3315 cls = self.theclass
3316
3317 # Ensure that utcoffset() is ignored when the operands have the
3318 # same tzinfo member.
3319 class OperandDependentOffset(tzinfo):
3320 def utcoffset(self, t):
3321 if t.minute < 10:
3322 # d0 and d1 equal after adjustment
3323 return timedelta(minutes=t.minute)
3324 else:
3325 # d2 off in the weeds
3326 return timedelta(minutes=59)
3327
3328 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3329 d0 = base.replace(minute=3)
3330 d1 = base.replace(minute=9)
3331 d2 = base.replace(minute=11)
3332 for x in d0, d1, d2:
3333 for y in d0, d1, d2:
3334 got = x - y
3335 expected = timedelta(minutes=x.minute - y.minute)
3336 self.assertEqual(got, expected)
3337
3338 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3339 # ignored.
3340 base = cls(8, 9, 10, 11, 12, 13, 14)
3341 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3342 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3343 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3344 for x in d0, d1, d2:
3345 for y in d0, d1, d2:
3346 got = x - y
3347 if (x is d0 or x is d1) and (y is d0 or y is d1):
3348 expected = timedelta(0)
3349 elif x is y is d2:
3350 expected = timedelta(0)
3351 elif x is d2:
3352 expected = timedelta(minutes=(11-59)-0)
3353 else:
3354 assert y is d2
3355 expected = timedelta(minutes=0-(11-59))
3356 self.assertEqual(got, expected)
3357
3358 def test_mixed_compare(self):
3359 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3360 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3361 self.assertEqual(t1, t2)
3362 t2 = t2.replace(tzinfo=None)
3363 self.assertEqual(t1, t2)
3364 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3365 self.assertEqual(t1, t2)
3366 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
Alexander Belopolsky08313822012-06-15 20:19:47 -04003367 self.assertNotEqual(t1, t2)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003368
3369 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3370 class Varies(tzinfo):
3371 def __init__(self):
3372 self.offset = timedelta(minutes=22)
3373 def utcoffset(self, t):
3374 self.offset += timedelta(minutes=1)
3375 return self.offset
3376
3377 v = Varies()
3378 t1 = t2.replace(tzinfo=v)
3379 t2 = t2.replace(tzinfo=v)
3380 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3381 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3382 self.assertEqual(t1, t2)
3383
3384 # But if they're not identical, it isn't ignored.
3385 t2 = t2.replace(tzinfo=Varies())
3386 self.assertTrue(t1 < t2) # t1's offset counter still going up
3387
3388 def test_subclass_datetimetz(self):
3389
3390 class C(self.theclass):
3391 theAnswer = 42
3392
3393 def __new__(cls, *args, **kws):
3394 temp = kws.copy()
3395 extra = temp.pop('extra')
3396 result = self.theclass.__new__(cls, *args, **temp)
3397 result.extra = extra
3398 return result
3399
3400 def newmeth(self, start):
3401 return start + self.hour + self.year
3402
3403 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3404
3405 dt1 = self.theclass(*args)
3406 dt2 = C(*args, **{'extra': 7})
3407
3408 self.assertEqual(dt2.__class__, C)
3409 self.assertEqual(dt2.theAnswer, 42)
3410 self.assertEqual(dt2.extra, 7)
3411 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3412 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3413
3414# Pain to set up DST-aware tzinfo classes.
3415
3416def first_sunday_on_or_after(dt):
3417 days_to_go = 6 - dt.weekday()
3418 if days_to_go:
3419 dt += timedelta(days_to_go)
3420 return dt
3421
3422ZERO = timedelta(0)
3423MINUTE = timedelta(minutes=1)
3424HOUR = timedelta(hours=1)
3425DAY = timedelta(days=1)
3426# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3427DSTSTART = datetime(1, 4, 1, 2)
3428# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3429# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3430# being standard time on that day, there is no spelling in local time of
3431# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3432DSTEND = datetime(1, 10, 25, 1)
3433
3434class USTimeZone(tzinfo):
3435
3436 def __init__(self, hours, reprname, stdname, dstname):
3437 self.stdoffset = timedelta(hours=hours)
3438 self.reprname = reprname
3439 self.stdname = stdname
3440 self.dstname = dstname
3441
3442 def __repr__(self):
3443 return self.reprname
3444
3445 def tzname(self, dt):
3446 if self.dst(dt):
3447 return self.dstname
3448 else:
3449 return self.stdname
3450
3451 def utcoffset(self, dt):
3452 return self.stdoffset + self.dst(dt)
3453
3454 def dst(self, dt):
3455 if dt is None or dt.tzinfo is None:
3456 # An exception instead may be sensible here, in one or more of
3457 # the cases.
3458 return ZERO
3459 assert dt.tzinfo is self
3460
3461 # Find first Sunday in April.
3462 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3463 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3464
3465 # Find last Sunday in October.
3466 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3467 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3468
3469 # Can't compare naive to aware objects, so strip the timezone from
3470 # dt first.
3471 if start <= dt.replace(tzinfo=None) < end:
3472 return HOUR
3473 else:
3474 return ZERO
3475
3476Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3477Central = USTimeZone(-6, "Central", "CST", "CDT")
3478Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3479Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
3480utc_real = FixedOffset(0, "UTC", 0)
3481# For better test coverage, we want another flavor of UTC that's west of
3482# the Eastern and Pacific timezones.
3483utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3484
3485class TestTimezoneConversions(unittest.TestCase):
3486 # The DST switch times for 2002, in std time.
3487 dston = datetime(2002, 4, 7, 2)
3488 dstoff = datetime(2002, 10, 27, 1)
3489
3490 theclass = datetime
3491
3492 # Check a time that's inside DST.
3493 def checkinside(self, dt, tz, utc, dston, dstoff):
3494 self.assertEqual(dt.dst(), HOUR)
3495
3496 # Conversion to our own timezone is always an identity.
3497 self.assertEqual(dt.astimezone(tz), dt)
3498
3499 asutc = dt.astimezone(utc)
3500 there_and_back = asutc.astimezone(tz)
3501
3502 # Conversion to UTC and back isn't always an identity here,
3503 # because there are redundant spellings (in local time) of
3504 # UTC time when DST begins: the clock jumps from 1:59:59
3505 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3506 # make sense then. The classes above treat 2:MM:SS as
3507 # daylight time then (it's "after 2am"), really an alias
3508 # for 1:MM:SS standard time. The latter form is what
3509 # conversion back from UTC produces.
3510 if dt.date() == dston.date() and dt.hour == 2:
3511 # We're in the redundant hour, and coming back from
3512 # UTC gives the 1:MM:SS standard-time spelling.
3513 self.assertEqual(there_and_back + HOUR, dt)
3514 # Although during was considered to be in daylight
3515 # time, there_and_back is not.
3516 self.assertEqual(there_and_back.dst(), ZERO)
3517 # They're the same times in UTC.
3518 self.assertEqual(there_and_back.astimezone(utc),
3519 dt.astimezone(utc))
3520 else:
3521 # We're not in the redundant hour.
3522 self.assertEqual(dt, there_and_back)
3523
3524 # Because we have a redundant spelling when DST begins, there is
Ezio Melotti3b3499b2011-03-16 11:35:38 +02003525 # (unfortunately) an hour when DST ends that can't be spelled at all in
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003526 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3527 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3528 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3529 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3530 # expressed in local time. Nevertheless, we want conversion back
3531 # from UTC to mimic the local clock's "repeat an hour" behavior.
3532 nexthour_utc = asutc + HOUR
3533 nexthour_tz = nexthour_utc.astimezone(tz)
3534 if dt.date() == dstoff.date() and dt.hour == 0:
3535 # We're in the hour before the last DST hour. The last DST hour
3536 # is ineffable. We want the conversion back to repeat 1:MM.
3537 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3538 nexthour_utc += HOUR
3539 nexthour_tz = nexthour_utc.astimezone(tz)
3540 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3541 else:
3542 self.assertEqual(nexthour_tz - dt, HOUR)
3543
3544 # Check a time that's outside DST.
3545 def checkoutside(self, dt, tz, utc):
3546 self.assertEqual(dt.dst(), ZERO)
3547
3548 # Conversion to our own timezone is always an identity.
3549 self.assertEqual(dt.astimezone(tz), dt)
3550
3551 # Converting to UTC and back is an identity too.
3552 asutc = dt.astimezone(utc)
3553 there_and_back = asutc.astimezone(tz)
3554 self.assertEqual(dt, there_and_back)
3555
3556 def convert_between_tz_and_utc(self, tz, utc):
3557 dston = self.dston.replace(tzinfo=tz)
3558 # Because 1:MM on the day DST ends is taken as being standard time,
3559 # there is no spelling in tz for the last hour of daylight time.
3560 # For purposes of the test, the last hour of DST is 0:MM, which is
3561 # taken as being daylight time (and 1:MM is taken as being standard
3562 # time).
3563 dstoff = self.dstoff.replace(tzinfo=tz)
3564 for delta in (timedelta(weeks=13),
3565 DAY,
3566 HOUR,
3567 timedelta(minutes=1),
3568 timedelta(microseconds=1)):
3569
3570 self.checkinside(dston, tz, utc, dston, dstoff)
3571 for during in dston + delta, dstoff - delta:
3572 self.checkinside(during, tz, utc, dston, dstoff)
3573
3574 self.checkoutside(dstoff, tz, utc)
3575 for outside in dston - delta, dstoff + delta:
3576 self.checkoutside(outside, tz, utc)
3577
3578 def test_easy(self):
3579 # Despite the name of this test, the endcases are excruciating.
3580 self.convert_between_tz_and_utc(Eastern, utc_real)
3581 self.convert_between_tz_and_utc(Pacific, utc_real)
3582 self.convert_between_tz_and_utc(Eastern, utc_fake)
3583 self.convert_between_tz_and_utc(Pacific, utc_fake)
3584 # The next is really dancing near the edge. It works because
3585 # Pacific and Eastern are far enough apart that their "problem
3586 # hours" don't overlap.
3587 self.convert_between_tz_and_utc(Eastern, Pacific)
3588 self.convert_between_tz_and_utc(Pacific, Eastern)
3589 # OTOH, these fail! Don't enable them. The difficulty is that
3590 # the edge case tests assume that every hour is representable in
3591 # the "utc" class. This is always true for a fixed-offset tzinfo
3592 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3593 # For these adjacent DST-aware time zones, the range of time offsets
3594 # tested ends up creating hours in the one that aren't representable
3595 # in the other. For the same reason, we would see failures in the
3596 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3597 # offset deltas in convert_between_tz_and_utc().
3598 #
3599 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3600 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
3601
3602 def test_tricky(self):
3603 # 22:00 on day before daylight starts.
3604 fourback = self.dston - timedelta(hours=4)
3605 ninewest = FixedOffset(-9*60, "-0900", 0)
3606 fourback = fourback.replace(tzinfo=ninewest)
3607 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3608 # 2", we should get the 3 spelling.
3609 # If we plug 22:00 the day before into Eastern, it "looks like std
3610 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3611 # to 22:00 lands on 2:00, which makes no sense in local time (the
3612 # local clock jumps from 1 to 3). The point here is to make sure we
3613 # get the 3 spelling.
3614 expected = self.dston.replace(hour=3)
3615 got = fourback.astimezone(Eastern).replace(tzinfo=None)
3616 self.assertEqual(expected, got)
3617
3618 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3619 # case we want the 1:00 spelling.
3620 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3621 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3622 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3623 # spelling.
3624 expected = self.dston.replace(hour=1)
3625 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3626 self.assertEqual(expected, got)
3627
3628 # Now on the day DST ends, we want "repeat an hour" behavior.
3629 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3630 # EST 23:MM 0:MM 1:MM 2:MM
3631 # EDT 0:MM 1:MM 2:MM 3:MM
3632 # wall 0:MM 1:MM 1:MM 2:MM against these
3633 for utc in utc_real, utc_fake:
3634 for tz in Eastern, Pacific:
3635 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3636 # Convert that to UTC.
3637 first_std_hour -= tz.utcoffset(None)
3638 # Adjust for possibly fake UTC.
3639 asutc = first_std_hour + utc.utcoffset(None)
3640 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3641 # tz=Eastern.
3642 asutcbase = asutc.replace(tzinfo=utc)
3643 for tzhour in (0, 1, 1, 2):
3644 expectedbase = self.dstoff.replace(hour=tzhour)
3645 for minute in 0, 30, 59:
3646 expected = expectedbase.replace(minute=minute)
3647 asutc = asutcbase.replace(minute=minute)
3648 astz = asutc.astimezone(tz)
3649 self.assertEqual(astz.replace(tzinfo=None), expected)
3650 asutcbase += HOUR
3651
3652
3653 def test_bogus_dst(self):
3654 class ok(tzinfo):
3655 def utcoffset(self, dt): return HOUR
3656 def dst(self, dt): return HOUR
3657
3658 now = self.theclass.now().replace(tzinfo=utc_real)
3659 # Doesn't blow up.
3660 now.astimezone(ok())
3661
3662 # Does blow up.
3663 class notok(ok):
3664 def dst(self, dt): return None
3665 self.assertRaises(ValueError, now.astimezone, notok())
3666
3667 # Sometimes blow up. In the following, tzinfo.dst()
3668 # implementation may return None or not None depending on
3669 # whether DST is assumed to be in effect. In this situation,
3670 # a ValueError should be raised by astimezone().
3671 class tricky_notok(ok):
3672 def dst(self, dt):
3673 if dt.year == 2000:
3674 return None
3675 else:
3676 return 10*HOUR
3677 dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3678 self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3679
3680 def test_fromutc(self):
3681 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3682 now = datetime.utcnow().replace(tzinfo=utc_real)
3683 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3684 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3685 enow = Eastern.fromutc(now) # doesn't blow up
3686 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3687 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3688 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3689
3690 # Always converts UTC to standard time.
3691 class FauxUSTimeZone(USTimeZone):
3692 def fromutc(self, dt):
3693 return dt + self.stdoffset
3694 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3695
3696 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3697 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3698 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3699
3700 # Check around DST start.
3701 start = self.dston.replace(hour=4, tzinfo=Eastern)
3702 fstart = start.replace(tzinfo=FEastern)
3703 for wall in 23, 0, 1, 3, 4, 5:
3704 expected = start.replace(hour=wall)
3705 if wall == 23:
3706 expected -= timedelta(days=1)
3707 got = Eastern.fromutc(start)
3708 self.assertEqual(expected, got)
3709
3710 expected = fstart + FEastern.stdoffset
3711 got = FEastern.fromutc(fstart)
3712 self.assertEqual(expected, got)
3713
3714 # Ensure astimezone() calls fromutc() too.
3715 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3716 self.assertEqual(expected, got)
3717
3718 start += HOUR
3719 fstart += HOUR
3720
3721 # Check around DST end.
3722 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3723 fstart = start.replace(tzinfo=FEastern)
3724 for wall in 0, 1, 1, 2, 3, 4:
3725 expected = start.replace(hour=wall)
3726 got = Eastern.fromutc(start)
3727 self.assertEqual(expected, got)
3728
3729 expected = fstart + FEastern.stdoffset
3730 got = FEastern.fromutc(fstart)
3731 self.assertEqual(expected, got)
3732
3733 # Ensure astimezone() calls fromutc() too.
3734 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3735 self.assertEqual(expected, got)
3736
3737 start += HOUR
3738 fstart += HOUR
3739
3740
3741#############################################################################
3742# oddballs
3743
3744class Oddballs(unittest.TestCase):
3745
3746 def test_bug_1028306(self):
3747 # Trying to compare a date to a datetime should act like a mixed-
3748 # type comparison, despite that datetime is a subclass of date.
3749 as_date = date.today()
3750 as_datetime = datetime.combine(as_date, time())
3751 self.assertTrue(as_date != as_datetime)
3752 self.assertTrue(as_datetime != as_date)
Serhiy Storchaka3df4dcc2013-11-17 12:52:33 +02003753 self.assertFalse(as_date == as_datetime)
3754 self.assertFalse(as_datetime == as_date)
Alexander Belopolskycf86e362010-07-23 19:25:47 +00003755 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3756 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3757 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3758 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3759 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3760 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3761 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3762 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3763
3764 # Neverthelss, comparison should work with the base-class (date)
3765 # projection if use of a date method is forced.
3766 self.assertEqual(as_date.__eq__(as_datetime), True)
3767 different_day = (as_date.day + 1) % 20 + 1
3768 as_different = as_datetime.replace(day= different_day)
3769 self.assertEqual(as_date.__eq__(as_different), False)
3770
3771 # And date should compare with other subclasses of date. If a
3772 # subclass wants to stop this, it's up to the subclass to do so.
3773 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3774 self.assertEqual(as_date, date_sc)
3775 self.assertEqual(date_sc, as_date)
3776
3777 # Ditto for datetimes.
3778 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3779 as_date.day, 0, 0, 0)
3780 self.assertEqual(as_datetime, datetime_sc)
3781 self.assertEqual(datetime_sc, as_datetime)
3782
3783def test_main():
3784 support.run_unittest(__name__)
3785
3786if __name__ == "__main__":
3787 test_main()