blob: cb639b41eaf443fca6be181203bb633033bab903 [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
Guido van Rossumf1200f82007-03-07 15:16:29 +00006import os
Tim Peters2a799bf2002-12-16 20:18:38 +00007import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00008import pickle
9import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +000010import unittest
11
12from test import test_support
13
14from datetime import MINYEAR, MAXYEAR
15from datetime import timedelta
16from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000017from datetime import time
18from datetime import date, datetime
19
Tim Peters35ad6412003-02-05 04:08:07 +000020pickle_choices = [(pickler, unpickler, proto)
21 for pickler in pickle, cPickle
22 for unpickler in pickle, cPickle
23 for proto in range(3)]
24assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000025
Tim Peters68124bb2003-02-08 03:46:31 +000026# An arbitrary collection of objects of non-datetime types, for testing
27# mixed-type comparisons.
28OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000029
Tim Peters2a799bf2002-12-16 20:18:38 +000030
31#############################################################################
32# module tests
33
34class TestModule(unittest.TestCase):
35
36 def test_constants(self):
37 import datetime
38 self.assertEqual(datetime.MINYEAR, 1)
39 self.assertEqual(datetime.MAXYEAR, 9999)
40
41#############################################################################
42# tzinfo tests
43
44class FixedOffset(tzinfo):
45 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000046 if isinstance(offset, int):
47 offset = timedelta(minutes=offset)
48 if isinstance(dstoffset, int):
49 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000050 self.__offset = offset
51 self.__name = name
52 self.__dstoffset = dstoffset
53 def __repr__(self):
54 return self.__name.lower()
55 def utcoffset(self, dt):
56 return self.__offset
57 def tzname(self, dt):
58 return self.__name
59 def dst(self, dt):
60 return self.__dstoffset
61
Tim Petersfb8472c2002-12-21 05:04:42 +000062class PicklableFixedOffset(FixedOffset):
63 def __init__(self, offset=None, name=None, dstoffset=None):
64 FixedOffset.__init__(self, offset, name, dstoffset)
65
Tim Peters2a799bf2002-12-16 20:18:38 +000066class TestTZInfo(unittest.TestCase):
67
68 def test_non_abstractness(self):
69 # In order to allow subclasses to get pickled, the C implementation
70 # wasn't able to get away with having __init__ raise
71 # NotImplementedError.
72 useless = tzinfo()
73 dt = datetime.max
74 self.assertRaises(NotImplementedError, useless.tzname, dt)
75 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
76 self.assertRaises(NotImplementedError, useless.dst, dt)
77
78 def test_subclass_must_override(self):
79 class NotEnough(tzinfo):
80 def __init__(self, offset, name):
81 self.__offset = offset
82 self.__name = name
83 self.failUnless(issubclass(NotEnough, tzinfo))
84 ne = NotEnough(3, "NotByALongShot")
85 self.failUnless(isinstance(ne, tzinfo))
86
87 dt = datetime.now()
88 self.assertRaises(NotImplementedError, ne.tzname, dt)
89 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
90 self.assertRaises(NotImplementedError, ne.dst, dt)
91
92 def test_normal(self):
93 fo = FixedOffset(3, "Three")
94 self.failUnless(isinstance(fo, tzinfo))
95 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000096 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000097 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000098 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000099
100 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000101 # There's no point to pickling tzinfo objects on their own (they
102 # carry no data), but they need to be picklable anyway else
103 # concrete subclasses can't be pickled.
104 orig = tzinfo.__new__(tzinfo)
105 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000106 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000107 green = pickler.dumps(orig, proto)
108 derived = unpickler.loads(green)
109 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000110
111 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000112 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000113 offset = timedelta(minutes=-300)
114 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000115 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000116 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000117 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000118 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000119 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000120 green = pickler.dumps(orig, proto)
121 derived = unpickler.loads(green)
122 self.failUnless(isinstance(derived, tzinfo))
123 self.failUnless(type(derived) is PicklableFixedOffset)
124 self.assertEqual(derived.utcoffset(None), offset)
125 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000126
127#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000128# Base clase for testing a particular aspect of timedelta, time, date and
129# datetime comparisons.
130
Collin Winterc2898c52007-04-25 17:29:52 +0000131class HarmlessMixedComparison:
Tim Peters07534a62003-02-07 22:50:28 +0000132 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
133
134 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
135 # legit constructor.
136
137 def test_harmless_mixed_comparison(self):
138 me = self.theclass(1, 1, 1)
139
140 self.failIf(me == ())
141 self.failUnless(me != ())
142 self.failIf(() == me)
143 self.failUnless(() != me)
144
145 self.failUnless(me in [1, 20L, [], me])
146 self.failIf(me not in [1, 20L, [], me])
147
148 self.failUnless([] in [me, 1, 20L, []])
149 self.failIf([] not in [me, 1, 20L, []])
150
151 def test_harmful_mixed_comparison(self):
152 me = self.theclass(1, 1, 1)
153
154 self.assertRaises(TypeError, lambda: me < ())
155 self.assertRaises(TypeError, lambda: me <= ())
156 self.assertRaises(TypeError, lambda: me > ())
157 self.assertRaises(TypeError, lambda: me >= ())
158
159 self.assertRaises(TypeError, lambda: () < me)
160 self.assertRaises(TypeError, lambda: () <= me)
161 self.assertRaises(TypeError, lambda: () > me)
162 self.assertRaises(TypeError, lambda: () >= me)
163
164 self.assertRaises(TypeError, cmp, (), me)
165 self.assertRaises(TypeError, cmp, me, ())
166
167#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000168# timedelta tests
169
Collin Winterc2898c52007-04-25 17:29:52 +0000170class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
Tim Peters07534a62003-02-07 22:50:28 +0000171
172 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000173
174 def test_constructor(self):
175 eq = self.assertEqual
176 td = timedelta
177
178 # Check keyword args to constructor
179 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
180 milliseconds=0, microseconds=0))
181 eq(td(1), td(days=1))
182 eq(td(0, 1), td(seconds=1))
183 eq(td(0, 0, 1), td(microseconds=1))
184 eq(td(weeks=1), td(days=7))
185 eq(td(days=1), td(hours=24))
186 eq(td(hours=1), td(minutes=60))
187 eq(td(minutes=1), td(seconds=60))
188 eq(td(seconds=1), td(milliseconds=1000))
189 eq(td(milliseconds=1), td(microseconds=1000))
190
191 # Check float args to constructor
192 eq(td(weeks=1.0/7), td(days=1))
193 eq(td(days=1.0/24), td(hours=1))
194 eq(td(hours=1.0/60), td(minutes=1))
195 eq(td(minutes=1.0/60), td(seconds=1))
196 eq(td(seconds=0.001), td(milliseconds=1))
197 eq(td(milliseconds=0.001), td(microseconds=1))
198
199 def test_computations(self):
200 eq = self.assertEqual
201 td = timedelta
202
203 a = td(7) # One week
204 b = td(0, 60) # One minute
205 c = td(0, 0, 1000) # One millisecond
206 eq(a+b+c, td(7, 60, 1000))
207 eq(a-b, td(6, 24*3600 - 60))
208 eq(-a, td(-7))
209 eq(+a, td(7))
210 eq(-b, td(-1, 24*3600 - 60))
211 eq(-c, td(-1, 24*3600 - 1, 999000))
212 eq(abs(a), a)
213 eq(abs(-a), a)
214 eq(td(6, 24*3600), a)
215 eq(td(0, 0, 60*1000000), b)
216 eq(a*10, td(70))
217 eq(a*10, 10*a)
218 eq(a*10L, 10*a)
219 eq(b*10, td(0, 600))
220 eq(10*b, td(0, 600))
221 eq(b*10L, td(0, 600))
222 eq(c*10, td(0, 0, 10000))
223 eq(10*c, td(0, 0, 10000))
224 eq(c*10L, td(0, 0, 10000))
225 eq(a*-1, -a)
226 eq(b*-2, -b-b)
227 eq(c*-2, -c+-c)
228 eq(b*(60*24), (b*60)*24)
229 eq(b*(60*24), (60*b)*24)
230 eq(c*1000, td(0, 1))
231 eq(1000*c, td(0, 1))
232 eq(a//7, td(1))
233 eq(b//10, td(0, 6))
234 eq(c//1000, td(0, 0, 1))
235 eq(a//10, td(0, 7*24*360))
236 eq(a//3600000, td(0, 0, 7*24*1000))
237
238 def test_disallowed_computations(self):
239 a = timedelta(42)
240
241 # Add/sub ints, longs, floats should be illegal
242 for i in 1, 1L, 1.0:
243 self.assertRaises(TypeError, lambda: a+i)
244 self.assertRaises(TypeError, lambda: a-i)
245 self.assertRaises(TypeError, lambda: i+a)
246 self.assertRaises(TypeError, lambda: i-a)
247
248 # Mul/div by float isn't supported.
249 x = 2.3
250 self.assertRaises(TypeError, lambda: a*x)
251 self.assertRaises(TypeError, lambda: x*a)
252 self.assertRaises(TypeError, lambda: a/x)
253 self.assertRaises(TypeError, lambda: x/a)
254 self.assertRaises(TypeError, lambda: a // x)
255 self.assertRaises(TypeError, lambda: x // a)
256
257 # Divison of int by timedelta doesn't make sense.
258 # Division by zero doesn't make sense.
259 for zero in 0, 0L:
260 self.assertRaises(TypeError, lambda: zero // a)
261 self.assertRaises(ZeroDivisionError, lambda: a // zero)
262
263 def test_basic_attributes(self):
264 days, seconds, us = 1, 7, 31
265 td = timedelta(days, seconds, us)
266 self.assertEqual(td.days, days)
267 self.assertEqual(td.seconds, seconds)
268 self.assertEqual(td.microseconds, us)
269
270 def test_carries(self):
271 t1 = timedelta(days=100,
272 weeks=-7,
273 hours=-24*(100-49),
274 minutes=-3,
275 seconds=12,
276 microseconds=(3*60 - 12) * 1e6 + 1)
277 t2 = timedelta(microseconds=1)
278 self.assertEqual(t1, t2)
279
280 def test_hash_equality(self):
281 t1 = timedelta(days=100,
282 weeks=-7,
283 hours=-24*(100-49),
284 minutes=-3,
285 seconds=12,
286 microseconds=(3*60 - 12) * 1000000)
287 t2 = timedelta()
288 self.assertEqual(hash(t1), hash(t2))
289
290 t1 += timedelta(weeks=7)
291 t2 += timedelta(days=7*7)
292 self.assertEqual(t1, t2)
293 self.assertEqual(hash(t1), hash(t2))
294
295 d = {t1: 1}
296 d[t2] = 2
297 self.assertEqual(len(d), 1)
298 self.assertEqual(d[t1], 2)
299
300 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000301 args = 12, 34, 56
302 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000303 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000304 green = pickler.dumps(orig, proto)
305 derived = unpickler.loads(green)
306 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000307
308 def test_compare(self):
309 t1 = timedelta(2, 3, 4)
310 t2 = timedelta(2, 3, 4)
311 self.failUnless(t1 == t2)
312 self.failUnless(t1 <= t2)
313 self.failUnless(t1 >= t2)
314 self.failUnless(not t1 != t2)
315 self.failUnless(not t1 < t2)
316 self.failUnless(not t1 > t2)
317 self.assertEqual(cmp(t1, t2), 0)
318 self.assertEqual(cmp(t2, t1), 0)
319
320 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
321 t2 = timedelta(*args) # this is larger than t1
322 self.failUnless(t1 < t2)
323 self.failUnless(t2 > t1)
324 self.failUnless(t1 <= t2)
325 self.failUnless(t2 >= t1)
326 self.failUnless(t1 != t2)
327 self.failUnless(t2 != t1)
328 self.failUnless(not t1 == t2)
329 self.failUnless(not t2 == t1)
330 self.failUnless(not t1 > t2)
331 self.failUnless(not t2 < t1)
332 self.failUnless(not t1 >= t2)
333 self.failUnless(not t2 <= t1)
334 self.assertEqual(cmp(t1, t2), -1)
335 self.assertEqual(cmp(t2, t1), 1)
336
Tim Peters68124bb2003-02-08 03:46:31 +0000337 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000338 self.assertEqual(t1 == badarg, False)
339 self.assertEqual(t1 != badarg, True)
340 self.assertEqual(badarg == t1, False)
341 self.assertEqual(badarg != t1, True)
342
Tim Peters2a799bf2002-12-16 20:18:38 +0000343 self.assertRaises(TypeError, lambda: t1 <= badarg)
344 self.assertRaises(TypeError, lambda: t1 < badarg)
345 self.assertRaises(TypeError, lambda: t1 > badarg)
346 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000347 self.assertRaises(TypeError, lambda: badarg <= t1)
348 self.assertRaises(TypeError, lambda: badarg < t1)
349 self.assertRaises(TypeError, lambda: badarg > t1)
350 self.assertRaises(TypeError, lambda: badarg >= t1)
351
352 def test_str(self):
353 td = timedelta
354 eq = self.assertEqual
355
356 eq(str(td(1)), "1 day, 0:00:00")
357 eq(str(td(-1)), "-1 day, 0:00:00")
358 eq(str(td(2)), "2 days, 0:00:00")
359 eq(str(td(-2)), "-2 days, 0:00:00")
360
361 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
362 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
363 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
364 "-210 days, 23:12:34")
365
366 eq(str(td(milliseconds=1)), "0:00:00.001000")
367 eq(str(td(microseconds=3)), "0:00:00.000003")
368
369 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
370 microseconds=999999)),
371 "999999999 days, 23:59:59.999999")
372
373 def test_roundtrip(self):
374 for td in (timedelta(days=999999999, hours=23, minutes=59,
375 seconds=59, microseconds=999999),
376 timedelta(days=-999999999),
377 timedelta(days=1, seconds=2, microseconds=3)):
378
379 # Verify td -> string -> td identity.
380 s = repr(td)
381 self.failUnless(s.startswith('datetime.'))
382 s = s[9:]
383 td2 = eval(s)
384 self.assertEqual(td, td2)
385
386 # Verify identity via reconstructing from pieces.
387 td2 = timedelta(td.days, td.seconds, td.microseconds)
388 self.assertEqual(td, td2)
389
390 def test_resolution_info(self):
391 self.assert_(isinstance(timedelta.min, timedelta))
392 self.assert_(isinstance(timedelta.max, timedelta))
393 self.assert_(isinstance(timedelta.resolution, timedelta))
394 self.assert_(timedelta.max > timedelta.min)
395 self.assertEqual(timedelta.min, timedelta(-999999999))
396 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
397 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
398
399 def test_overflow(self):
400 tiny = timedelta.resolution
401
402 td = timedelta.min + tiny
403 td -= tiny # no problem
404 self.assertRaises(OverflowError, td.__sub__, tiny)
405 self.assertRaises(OverflowError, td.__add__, -tiny)
406
407 td = timedelta.max - tiny
408 td += tiny # no problem
409 self.assertRaises(OverflowError, td.__add__, tiny)
410 self.assertRaises(OverflowError, td.__sub__, -tiny)
411
412 self.assertRaises(OverflowError, lambda: -timedelta.max)
413
414 def test_microsecond_rounding(self):
415 td = timedelta
416 eq = self.assertEqual
417
418 # Single-field rounding.
419 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
421 eq(td(milliseconds=0.6/1000), td(microseconds=1))
422 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
423
424 # Rounding due to contributions from more than one field.
425 us_per_hour = 3600e6
426 us_per_day = us_per_hour * 24
427 eq(td(days=.4/us_per_day), td(0))
428 eq(td(hours=.2/us_per_hour), td(0))
429 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
430
431 eq(td(days=-.4/us_per_day), td(0))
432 eq(td(hours=-.2/us_per_hour), td(0))
433 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
434
435 def test_massive_normalization(self):
436 td = timedelta(microseconds=-1)
437 self.assertEqual((td.days, td.seconds, td.microseconds),
438 (-1, 24*3600-1, 999999))
439
440 def test_bool(self):
441 self.failUnless(timedelta(1))
442 self.failUnless(timedelta(0, 1))
443 self.failUnless(timedelta(0, 0, 1))
444 self.failUnless(timedelta(microseconds=1))
445 self.failUnless(not timedelta(0))
446
Tim Petersb0c854d2003-05-17 15:57:00 +0000447 def test_subclass_timedelta(self):
448
449 class T(timedelta):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000450 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000451 def from_td(td):
452 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000453
454 def as_hours(self):
455 sum = (self.days * 24 +
456 self.seconds / 3600.0 +
457 self.microseconds / 3600e6)
458 return round(sum)
459
460 t1 = T(days=1)
461 self.assert_(type(t1) is T)
462 self.assertEqual(t1.as_hours(), 24)
463
464 t2 = T(days=-1, seconds=-3600)
465 self.assert_(type(t2) is T)
466 self.assertEqual(t2.as_hours(), -25)
467
468 t3 = t1 + t2
469 self.assert_(type(t3) is timedelta)
470 t4 = T.from_td(t3)
471 self.assert_(type(t4) is T)
472 self.assertEqual(t3.days, t4.days)
473 self.assertEqual(t3.seconds, t4.seconds)
474 self.assertEqual(t3.microseconds, t4.microseconds)
475 self.assertEqual(str(t3), str(t4))
476 self.assertEqual(t4.as_hours(), -1)
477
Tim Peters2a799bf2002-12-16 20:18:38 +0000478#############################################################################
479# date tests
480
481class TestDateOnly(unittest.TestCase):
482 # Tests here won't pass if also run on datetime objects, so don't
483 # subclass this to test datetimes too.
484
485 def test_delta_non_days_ignored(self):
486 dt = date(2000, 1, 2)
487 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
488 microseconds=5)
489 days = timedelta(delta.days)
490 self.assertEqual(days, timedelta(1))
491
492 dt2 = dt + delta
493 self.assertEqual(dt2, dt + days)
494
495 dt2 = delta + dt
496 self.assertEqual(dt2, dt + days)
497
498 dt2 = dt - delta
499 self.assertEqual(dt2, dt - days)
500
501 delta = -delta
502 days = timedelta(delta.days)
503 self.assertEqual(days, timedelta(-2))
504
505 dt2 = dt + delta
506 self.assertEqual(dt2, dt + days)
507
508 dt2 = delta + dt
509 self.assertEqual(dt2, dt + days)
510
511 dt2 = dt - delta
512 self.assertEqual(dt2, dt - days)
513
Tim Peters604c0132004-06-07 23:04:33 +0000514class SubclassDate(date):
515 sub_var = 1
516
Collin Winterc2898c52007-04-25 17:29:52 +0000517class TestDate(HarmlessMixedComparison, unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +0000518 # Tests here should pass for both dates and datetimes, except for a
519 # few tests that TestDateTime overrides.
520
521 theclass = date
522
523 def test_basic_attributes(self):
524 dt = self.theclass(2002, 3, 1)
525 self.assertEqual(dt.year, 2002)
526 self.assertEqual(dt.month, 3)
527 self.assertEqual(dt.day, 1)
528
529 def test_roundtrip(self):
530 for dt in (self.theclass(1, 2, 3),
531 self.theclass.today()):
532 # Verify dt -> string -> date identity.
533 s = repr(dt)
534 self.failUnless(s.startswith('datetime.'))
535 s = s[9:]
536 dt2 = eval(s)
537 self.assertEqual(dt, dt2)
538
539 # Verify identity via reconstructing from pieces.
540 dt2 = self.theclass(dt.year, dt.month, dt.day)
541 self.assertEqual(dt, dt2)
542
543 def test_ordinal_conversions(self):
544 # Check some fixed values.
545 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
546 (1, 12, 31, 365),
547 (2, 1, 1, 366),
548 # first example from "Calendrical Calculations"
549 (1945, 11, 12, 710347)]:
550 d = self.theclass(y, m, d)
551 self.assertEqual(n, d.toordinal())
552 fromord = self.theclass.fromordinal(n)
553 self.assertEqual(d, fromord)
554 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000555 # if we're checking something fancier than a date, verify
556 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000557 self.assertEqual(fromord.hour, 0)
558 self.assertEqual(fromord.minute, 0)
559 self.assertEqual(fromord.second, 0)
560 self.assertEqual(fromord.microsecond, 0)
561
Tim Peters0bf60bd2003-01-08 20:40:01 +0000562 # Check first and last days of year spottily across the whole
563 # range of years supported.
564 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000565 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
566 d = self.theclass(year, 1, 1)
567 n = d.toordinal()
568 d2 = self.theclass.fromordinal(n)
569 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000570 # Verify that moving back a day gets to the end of year-1.
571 if year > 1:
572 d = self.theclass.fromordinal(n-1)
573 d2 = self.theclass(year-1, 12, 31)
574 self.assertEqual(d, d2)
575 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000576
577 # Test every day in a leap-year and a non-leap year.
578 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
579 for year, isleap in (2000, True), (2002, False):
580 n = self.theclass(year, 1, 1).toordinal()
581 for month, maxday in zip(range(1, 13), dim):
582 if month == 2 and isleap:
583 maxday += 1
584 for day in range(1, maxday+1):
585 d = self.theclass(year, month, day)
586 self.assertEqual(d.toordinal(), n)
587 self.assertEqual(d, self.theclass.fromordinal(n))
588 n += 1
589
590 def test_extreme_ordinals(self):
591 a = self.theclass.min
592 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
593 aord = a.toordinal()
594 b = a.fromordinal(aord)
595 self.assertEqual(a, b)
596
597 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
598
599 b = a + timedelta(days=1)
600 self.assertEqual(b.toordinal(), aord + 1)
601 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
602
603 a = self.theclass.max
604 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
605 aord = a.toordinal()
606 b = a.fromordinal(aord)
607 self.assertEqual(a, b)
608
609 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
610
611 b = a - timedelta(days=1)
612 self.assertEqual(b.toordinal(), aord - 1)
613 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
614
615 def test_bad_constructor_arguments(self):
616 # bad years
617 self.theclass(MINYEAR, 1, 1) # no exception
618 self.theclass(MAXYEAR, 1, 1) # no exception
619 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
620 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
621 # bad months
622 self.theclass(2000, 1, 1) # no exception
623 self.theclass(2000, 12, 1) # no exception
624 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
625 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
626 # bad days
627 self.theclass(2000, 2, 29) # no exception
628 self.theclass(2004, 2, 29) # no exception
629 self.theclass(2400, 2, 29) # no exception
630 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
631 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
635 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
636
637 def test_hash_equality(self):
638 d = self.theclass(2000, 12, 31)
639 # same thing
640 e = self.theclass(2000, 12, 31)
641 self.assertEqual(d, e)
642 self.assertEqual(hash(d), hash(e))
643
644 dic = {d: 1}
645 dic[e] = 2
646 self.assertEqual(len(dic), 1)
647 self.assertEqual(dic[d], 2)
648 self.assertEqual(dic[e], 2)
649
650 d = self.theclass(2001, 1, 1)
651 # same thing
652 e = self.theclass(2001, 1, 1)
653 self.assertEqual(d, e)
654 self.assertEqual(hash(d), hash(e))
655
656 dic = {d: 1}
657 dic[e] = 2
658 self.assertEqual(len(dic), 1)
659 self.assertEqual(dic[d], 2)
660 self.assertEqual(dic[e], 2)
661
662 def test_computations(self):
663 a = self.theclass(2002, 1, 31)
664 b = self.theclass(1956, 1, 31)
665
666 diff = a-b
667 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
668 self.assertEqual(diff.seconds, 0)
669 self.assertEqual(diff.microseconds, 0)
670
671 day = timedelta(1)
672 week = timedelta(7)
673 a = self.theclass(2002, 3, 2)
674 self.assertEqual(a + day, self.theclass(2002, 3, 3))
675 self.assertEqual(day + a, self.theclass(2002, 3, 3))
676 self.assertEqual(a - day, self.theclass(2002, 3, 1))
677 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
678 self.assertEqual(a + week, self.theclass(2002, 3, 9))
679 self.assertEqual(a - week, self.theclass(2002, 2, 23))
680 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
681 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
682 self.assertEqual((a + week) - a, week)
683 self.assertEqual((a + day) - a, day)
684 self.assertEqual((a - week) - a, -week)
685 self.assertEqual((a - day) - a, -day)
686 self.assertEqual(a - (a + week), -week)
687 self.assertEqual(a - (a + day), -day)
688 self.assertEqual(a - (a - week), week)
689 self.assertEqual(a - (a - day), day)
690
691 # Add/sub ints, longs, floats should be illegal
692 for i in 1, 1L, 1.0:
693 self.assertRaises(TypeError, lambda: a+i)
694 self.assertRaises(TypeError, lambda: a-i)
695 self.assertRaises(TypeError, lambda: i+a)
696 self.assertRaises(TypeError, lambda: i-a)
697
698 # delta - date is senseless.
699 self.assertRaises(TypeError, lambda: day - a)
700 # mixing date and (delta or date) via * or // is senseless
701 self.assertRaises(TypeError, lambda: day * a)
702 self.assertRaises(TypeError, lambda: a * day)
703 self.assertRaises(TypeError, lambda: day // a)
704 self.assertRaises(TypeError, lambda: a // day)
705 self.assertRaises(TypeError, lambda: a * a)
706 self.assertRaises(TypeError, lambda: a // a)
707 # date + date is senseless
708 self.assertRaises(TypeError, lambda: a + a)
709
710 def test_overflow(self):
711 tiny = self.theclass.resolution
712
713 dt = self.theclass.min + tiny
714 dt -= tiny # no problem
715 self.assertRaises(OverflowError, dt.__sub__, tiny)
716 self.assertRaises(OverflowError, dt.__add__, -tiny)
717
718 dt = self.theclass.max - tiny
719 dt += tiny # no problem
720 self.assertRaises(OverflowError, dt.__add__, tiny)
721 self.assertRaises(OverflowError, dt.__sub__, -tiny)
722
723 def test_fromtimestamp(self):
724 import time
725
726 # Try an arbitrary fixed value.
727 year, month, day = 1999, 9, 19
728 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
729 d = self.theclass.fromtimestamp(ts)
730 self.assertEqual(d.year, year)
731 self.assertEqual(d.month, month)
732 self.assertEqual(d.day, day)
733
Tim Peters1b6f7a92004-06-20 02:50:16 +0000734 def test_insane_fromtimestamp(self):
735 # It's possible that some platform maps time_t to double,
736 # and that this test will fail there. This test should
737 # exempt such platforms (provided they return reasonable
738 # results!).
739 for insane in -1e200, 1e200:
740 self.assertRaises(ValueError, self.theclass.fromtimestamp,
741 insane)
742
Tim Peters2a799bf2002-12-16 20:18:38 +0000743 def test_today(self):
744 import time
745
746 # We claim that today() is like fromtimestamp(time.time()), so
747 # prove it.
748 for dummy in range(3):
749 today = self.theclass.today()
750 ts = time.time()
751 todayagain = self.theclass.fromtimestamp(ts)
752 if today == todayagain:
753 break
754 # There are several legit reasons that could fail:
755 # 1. It recently became midnight, between the today() and the
756 # time() calls.
757 # 2. The platform time() has such fine resolution that we'll
758 # never get the same value twice.
759 # 3. The platform time() has poor resolution, and we just
760 # happened to call today() right before a resolution quantum
761 # boundary.
762 # 4. The system clock got fiddled between calls.
763 # In any case, wait a little while and try again.
764 time.sleep(0.1)
765
766 # It worked or it didn't. If it didn't, assume it's reason #2, and
767 # let the test pass if they're within half a second of each other.
768 self.failUnless(today == todayagain or
769 abs(todayagain - today) < timedelta(seconds=0.5))
770
771 def test_weekday(self):
772 for i in range(7):
773 # March 4, 2002 is a Monday
774 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
775 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
776 # January 2, 1956 is a Monday
777 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
778 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
779
780 def test_isocalendar(self):
781 # Check examples from
782 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
783 for i in range(7):
784 d = self.theclass(2003, 12, 22+i)
785 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
786 d = self.theclass(2003, 12, 29) + timedelta(i)
787 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
788 d = self.theclass(2004, 1, 5+i)
789 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
790 d = self.theclass(2009, 12, 21+i)
791 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
792 d = self.theclass(2009, 12, 28) + timedelta(i)
793 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
794 d = self.theclass(2010, 1, 4+i)
795 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
796
797 def test_iso_long_years(self):
798 # Calculate long ISO years and compare to table from
799 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
800 ISO_LONG_YEARS_TABLE = """
801 4 32 60 88
802 9 37 65 93
803 15 43 71 99
804 20 48 76
805 26 54 82
806
807 105 133 161 189
808 111 139 167 195
809 116 144 172
810 122 150 178
811 128 156 184
812
813 201 229 257 285
814 207 235 263 291
815 212 240 268 296
816 218 246 274
817 224 252 280
818
819 303 331 359 387
820 308 336 364 392
821 314 342 370 398
822 320 348 376
823 325 353 381
824 """
825 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
826 iso_long_years.sort()
827 L = []
828 for i in range(400):
829 d = self.theclass(2000+i, 12, 31)
830 d1 = self.theclass(1600+i, 12, 31)
831 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
832 if d.isocalendar()[1] == 53:
833 L.append(i)
834 self.assertEqual(L, iso_long_years)
835
836 def test_isoformat(self):
837 t = self.theclass(2, 3, 2)
838 self.assertEqual(t.isoformat(), "0002-03-02")
839
840 def test_ctime(self):
841 t = self.theclass(2002, 3, 2)
842 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
843
844 def test_strftime(self):
845 t = self.theclass(2005, 3, 2)
846 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000847 self.assertEqual(t.strftime(""), "") # SF bug #761337
Georg Brandl4ddfcd32006-09-30 11:17:34 +0000848 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
Tim Peters2a799bf2002-12-16 20:18:38 +0000849
850 self.assertRaises(TypeError, t.strftime) # needs an arg
851 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
852 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
853
854 # A naive object replaces %z and %Z w/ empty strings.
855 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
856
Eric Smitha9f7d622008-02-17 19:46:49 +0000857 def test_format(self):
858 dt = self.theclass(2007, 9, 10)
859 self.assertEqual(dt.__format__(''), str(dt))
860
861 # check that a derived class's __str__() gets called
862 class A(self.theclass):
863 def __str__(self):
864 return 'A'
865 a = A(2007, 9, 10)
866 self.assertEqual(a.__format__(''), 'A')
867
868 # check that a derived class's strftime gets called
869 class B(self.theclass):
870 def strftime(self, format_spec):
871 return 'B'
872 b = B(2007, 9, 10)
873 self.assertEqual(b.__format__(''), str(dt))
874
875 for fmt in ["m:%m d:%d y:%y",
876 "m:%m d:%d y:%y H:%H M:%M S:%S",
877 "%z %Z",
878 ]:
879 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
880 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
881 self.assertEqual(b.__format__(fmt), 'B')
882
Tim Peters2a799bf2002-12-16 20:18:38 +0000883 def test_resolution_info(self):
884 self.assert_(isinstance(self.theclass.min, self.theclass))
885 self.assert_(isinstance(self.theclass.max, self.theclass))
886 self.assert_(isinstance(self.theclass.resolution, timedelta))
887 self.assert_(self.theclass.max > self.theclass.min)
888
889 def test_extreme_timedelta(self):
890 big = self.theclass.max - self.theclass.min
891 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
892 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
893 # n == 315537897599999999 ~= 2**58.13
894 justasbig = timedelta(0, 0, n)
895 self.assertEqual(big, justasbig)
896 self.assertEqual(self.theclass.min + big, self.theclass.max)
897 self.assertEqual(self.theclass.max - big, self.theclass.min)
898
899 def test_timetuple(self):
900 for i in range(7):
901 # January 2, 1956 is a Monday (0)
902 d = self.theclass(1956, 1, 2+i)
903 t = d.timetuple()
904 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
905 # February 1, 1956 is a Wednesday (2)
906 d = self.theclass(1956, 2, 1+i)
907 t = d.timetuple()
908 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
909 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
910 # of the year.
911 d = self.theclass(1956, 3, 1+i)
912 t = d.timetuple()
913 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
914 self.assertEqual(t.tm_year, 1956)
915 self.assertEqual(t.tm_mon, 3)
916 self.assertEqual(t.tm_mday, 1+i)
917 self.assertEqual(t.tm_hour, 0)
918 self.assertEqual(t.tm_min, 0)
919 self.assertEqual(t.tm_sec, 0)
920 self.assertEqual(t.tm_wday, (3+i)%7)
921 self.assertEqual(t.tm_yday, 61+i)
922 self.assertEqual(t.tm_isdst, -1)
923
924 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000925 args = 6, 7, 23
926 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000927 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000928 green = pickler.dumps(orig, proto)
929 derived = unpickler.loads(green)
930 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000931
932 def test_compare(self):
933 t1 = self.theclass(2, 3, 4)
934 t2 = self.theclass(2, 3, 4)
935 self.failUnless(t1 == t2)
936 self.failUnless(t1 <= t2)
937 self.failUnless(t1 >= t2)
938 self.failUnless(not t1 != t2)
939 self.failUnless(not t1 < t2)
940 self.failUnless(not t1 > t2)
941 self.assertEqual(cmp(t1, t2), 0)
942 self.assertEqual(cmp(t2, t1), 0)
943
944 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
945 t2 = self.theclass(*args) # this is larger than t1
946 self.failUnless(t1 < t2)
947 self.failUnless(t2 > t1)
948 self.failUnless(t1 <= t2)
949 self.failUnless(t2 >= t1)
950 self.failUnless(t1 != t2)
951 self.failUnless(t2 != t1)
952 self.failUnless(not t1 == t2)
953 self.failUnless(not t2 == t1)
954 self.failUnless(not t1 > t2)
955 self.failUnless(not t2 < t1)
956 self.failUnless(not t1 >= t2)
957 self.failUnless(not t2 <= t1)
958 self.assertEqual(cmp(t1, t2), -1)
959 self.assertEqual(cmp(t2, t1), 1)
960
Tim Peters68124bb2003-02-08 03:46:31 +0000961 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000962 self.assertEqual(t1 == badarg, False)
963 self.assertEqual(t1 != badarg, True)
964 self.assertEqual(badarg == t1, False)
965 self.assertEqual(badarg != t1, True)
966
Tim Peters2a799bf2002-12-16 20:18:38 +0000967 self.assertRaises(TypeError, lambda: t1 < badarg)
968 self.assertRaises(TypeError, lambda: t1 > badarg)
969 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000970 self.assertRaises(TypeError, lambda: badarg <= t1)
971 self.assertRaises(TypeError, lambda: badarg < t1)
972 self.assertRaises(TypeError, lambda: badarg > t1)
973 self.assertRaises(TypeError, lambda: badarg >= t1)
974
Tim Peters8d81a012003-01-24 22:36:34 +0000975 def test_mixed_compare(self):
976 our = self.theclass(2000, 4, 5)
977 self.assertRaises(TypeError, cmp, our, 1)
978 self.assertRaises(TypeError, cmp, 1, our)
979
980 class AnotherDateTimeClass(object):
981 def __cmp__(self, other):
982 # Return "equal" so calling this can't be confused with
983 # compare-by-address (which never says "equal" for distinct
984 # objects).
985 return 0
986
987 # This still errors, because date and datetime comparison raise
988 # TypeError instead of NotImplemented when they don't know what to
989 # do, in order to stop comparison from falling back to the default
990 # compare-by-address.
991 their = AnotherDateTimeClass()
992 self.assertRaises(TypeError, cmp, our, their)
993 # Oops: The next stab raises TypeError in the C implementation,
994 # but not in the Python implementation of datetime. The difference
995 # is due to that the Python implementation defines __cmp__ but
996 # the C implementation defines tp_richcompare. This is more pain
997 # to fix than it's worth, so commenting out the test.
998 # self.assertEqual(cmp(their, our), 0)
999
1000 # But date and datetime comparison return NotImplemented instead if the
1001 # other object has a timetuple attr. This gives the other object a
1002 # chance to do the comparison.
1003 class Comparable(AnotherDateTimeClass):
1004 def timetuple(self):
1005 return ()
1006
1007 their = Comparable()
1008 self.assertEqual(cmp(our, their), 0)
1009 self.assertEqual(cmp(their, our), 0)
1010 self.failUnless(our == their)
1011 self.failUnless(their == our)
1012
Tim Peters2a799bf2002-12-16 20:18:38 +00001013 def test_bool(self):
1014 # All dates are considered true.
1015 self.failUnless(self.theclass.min)
1016 self.failUnless(self.theclass.max)
1017
Guido van Rossum966bb8c2007-08-24 14:53:14 +00001018 def test_strftime_out_of_range(self):
Tim Petersd6844152002-12-22 20:58:42 +00001019 # For nasty technical reasons, we can't handle years before 1900.
1020 cls = self.theclass
1021 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1022 for y in 1, 49, 51, 99, 100, 1000, 1899:
1023 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +00001024
1025 def test_replace(self):
1026 cls = self.theclass
1027 args = [1, 2, 3]
1028 base = cls(*args)
1029 self.assertEqual(base, base.replace())
1030
1031 i = 0
1032 for name, newval in (("year", 2),
1033 ("month", 3),
1034 ("day", 4)):
1035 newargs = args[:]
1036 newargs[i] = newval
1037 expected = cls(*newargs)
1038 got = base.replace(**{name: newval})
1039 self.assertEqual(expected, got)
1040 i += 1
1041
1042 # Out of bounds.
1043 base = cls(2000, 2, 29)
1044 self.assertRaises(ValueError, base.replace, year=2001)
1045
Tim Petersa98924a2003-05-17 05:55:19 +00001046 def test_subclass_date(self):
1047
1048 class C(self.theclass):
1049 theAnswer = 42
1050
1051 def __new__(cls, *args, **kws):
1052 temp = kws.copy()
1053 extra = temp.pop('extra')
1054 result = self.theclass.__new__(cls, *args, **temp)
1055 result.extra = extra
1056 return result
1057
1058 def newmeth(self, start):
1059 return start + self.year + self.month
1060
1061 args = 2003, 4, 14
1062
1063 dt1 = self.theclass(*args)
1064 dt2 = C(*args, **{'extra': 7})
1065
1066 self.assertEqual(dt2.__class__, C)
1067 self.assertEqual(dt2.theAnswer, 42)
1068 self.assertEqual(dt2.extra, 7)
1069 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1070 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1071
Tim Peters604c0132004-06-07 23:04:33 +00001072 def test_pickling_subclass_date(self):
1073
1074 args = 6, 7, 23
1075 orig = SubclassDate(*args)
1076 for pickler, unpickler, proto in pickle_choices:
1077 green = pickler.dumps(orig, proto)
1078 derived = unpickler.loads(green)
1079 self.assertEqual(orig, derived)
1080
Tim Peters3f606292004-03-21 23:38:41 +00001081 def test_backdoor_resistance(self):
1082 # For fast unpickling, the constructor accepts a pickle string.
1083 # This is a low-overhead backdoor. A user can (by intent or
1084 # mistake) pass a string directly, which (if it's the right length)
1085 # will get treated like a pickle, and bypass the normal sanity
1086 # checks in the constructor. This can create insane objects.
1087 # The constructor doesn't want to burn the time to validate all
1088 # fields, but does check the month field. This stops, e.g.,
1089 # datetime.datetime('1995-03-25') from yielding an insane object.
1090 base = '1995-03-25'
1091 if not issubclass(self.theclass, datetime):
1092 base = base[:4]
1093 for month_byte in '9', chr(0), chr(13), '\xff':
1094 self.assertRaises(TypeError, self.theclass,
1095 base[:2] + month_byte + base[3:])
1096 for ord_byte in range(1, 13):
1097 # This shouldn't blow up because of the month byte alone. If
1098 # the implementation changes to do more-careful checking, it may
1099 # blow up because other fields are insane.
1100 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001101
Tim Peters2a799bf2002-12-16 20:18:38 +00001102#############################################################################
1103# datetime tests
1104
Tim Peters604c0132004-06-07 23:04:33 +00001105class SubclassDatetime(datetime):
1106 sub_var = 1
1107
Tim Peters2a799bf2002-12-16 20:18:38 +00001108class TestDateTime(TestDate):
1109
1110 theclass = datetime
1111
1112 def test_basic_attributes(self):
1113 dt = self.theclass(2002, 3, 1, 12, 0)
1114 self.assertEqual(dt.year, 2002)
1115 self.assertEqual(dt.month, 3)
1116 self.assertEqual(dt.day, 1)
1117 self.assertEqual(dt.hour, 12)
1118 self.assertEqual(dt.minute, 0)
1119 self.assertEqual(dt.second, 0)
1120 self.assertEqual(dt.microsecond, 0)
1121
1122 def test_basic_attributes_nonzero(self):
1123 # Make sure all attributes are non-zero so bugs in
1124 # bit-shifting access show up.
1125 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1126 self.assertEqual(dt.year, 2002)
1127 self.assertEqual(dt.month, 3)
1128 self.assertEqual(dt.day, 1)
1129 self.assertEqual(dt.hour, 12)
1130 self.assertEqual(dt.minute, 59)
1131 self.assertEqual(dt.second, 59)
1132 self.assertEqual(dt.microsecond, 8000)
1133
1134 def test_roundtrip(self):
1135 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1136 self.theclass.now()):
1137 # Verify dt -> string -> datetime identity.
1138 s = repr(dt)
1139 self.failUnless(s.startswith('datetime.'))
1140 s = s[9:]
1141 dt2 = eval(s)
1142 self.assertEqual(dt, dt2)
1143
1144 # Verify identity via reconstructing from pieces.
1145 dt2 = self.theclass(dt.year, dt.month, dt.day,
1146 dt.hour, dt.minute, dt.second,
1147 dt.microsecond)
1148 self.assertEqual(dt, dt2)
1149
1150 def test_isoformat(self):
1151 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1152 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1153 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1154 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1155 # str is ISO format with the separator forced to a blank.
1156 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1157
1158 t = self.theclass(2, 3, 2)
1159 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1160 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1161 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1162 # str is ISO format with the separator forced to a blank.
1163 self.assertEqual(str(t), "0002-03-02 00:00:00")
1164
Eric Smitha9f7d622008-02-17 19:46:49 +00001165 def test_format(self):
1166 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1167 self.assertEqual(dt.__format__(''), str(dt))
1168
1169 # check that a derived class's __str__() gets called
1170 class A(self.theclass):
1171 def __str__(self):
1172 return 'A'
1173 a = A(2007, 9, 10, 4, 5, 1, 123)
1174 self.assertEqual(a.__format__(''), 'A')
1175
1176 # check that a derived class's strftime gets called
1177 class B(self.theclass):
1178 def strftime(self, format_spec):
1179 return 'B'
1180 b = B(2007, 9, 10, 4, 5, 1, 123)
1181 self.assertEqual(b.__format__(''), str(dt))
1182
1183 for fmt in ["m:%m d:%d y:%y",
1184 "m:%m d:%d y:%y H:%H M:%M S:%S",
1185 "%z %Z",
1186 ]:
1187 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1188 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1189 self.assertEqual(b.__format__(fmt), 'B')
1190
Tim Peters2a799bf2002-12-16 20:18:38 +00001191 def test_more_ctime(self):
1192 # Test fields that TestDate doesn't touch.
1193 import time
1194
1195 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1196 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1197 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1198 # out. The difference is that t.ctime() produces " 2" for the day,
1199 # but platform ctime() produces "02" for the day. According to
1200 # C99, t.ctime() is correct here.
1201 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1202
1203 # So test a case where that difference doesn't matter.
1204 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1205 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1206
1207 def test_tz_independent_comparing(self):
1208 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1209 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1210 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1211 self.assertEqual(dt1, dt3)
1212 self.assert_(dt2 > dt3)
1213
1214 # Make sure comparison doesn't forget microseconds, and isn't done
1215 # via comparing a float timestamp (an IEEE double doesn't have enough
1216 # precision to span microsecond resolution across years 1 thru 9999,
1217 # so comparing via timestamp necessarily calls some distinct values
1218 # equal).
1219 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1220 us = timedelta(microseconds=1)
1221 dt2 = dt1 + us
1222 self.assertEqual(dt2 - dt1, us)
1223 self.assert_(dt1 < dt2)
1224
Neal Norwitzd5b0c9b2006-03-20 01:58:39 +00001225 def test_strftime_with_bad_tzname_replace(self):
1226 # verify ok if tzinfo.tzname().replace() returns a non-string
1227 class MyTzInfo(FixedOffset):
1228 def tzname(self, dt):
1229 class MyStr(str):
1230 def replace(self, *args):
1231 return None
1232 return MyStr('name')
1233 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1234 self.assertRaises(TypeError, t.strftime, '%Z')
1235
Tim Peters2a799bf2002-12-16 20:18:38 +00001236 def test_bad_constructor_arguments(self):
1237 # bad years
1238 self.theclass(MINYEAR, 1, 1) # no exception
1239 self.theclass(MAXYEAR, 1, 1) # no exception
1240 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1241 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1242 # bad months
1243 self.theclass(2000, 1, 1) # no exception
1244 self.theclass(2000, 12, 1) # no exception
1245 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1246 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1247 # bad days
1248 self.theclass(2000, 2, 29) # no exception
1249 self.theclass(2004, 2, 29) # no exception
1250 self.theclass(2400, 2, 29) # no exception
1251 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1252 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1253 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1254 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1255 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1256 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1257 # bad hours
1258 self.theclass(2000, 1, 31, 0) # no exception
1259 self.theclass(2000, 1, 31, 23) # no exception
1260 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1261 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1262 # bad minutes
1263 self.theclass(2000, 1, 31, 23, 0) # no exception
1264 self.theclass(2000, 1, 31, 23, 59) # no exception
1265 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1266 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1267 # bad seconds
1268 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1269 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1270 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1271 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1272 # bad microseconds
1273 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1274 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1275 self.assertRaises(ValueError, self.theclass,
1276 2000, 1, 31, 23, 59, 59, -1)
1277 self.assertRaises(ValueError, self.theclass,
1278 2000, 1, 31, 23, 59, 59,
1279 1000000)
1280
1281 def test_hash_equality(self):
1282 d = self.theclass(2000, 12, 31, 23, 30, 17)
1283 e = self.theclass(2000, 12, 31, 23, 30, 17)
1284 self.assertEqual(d, e)
1285 self.assertEqual(hash(d), hash(e))
1286
1287 dic = {d: 1}
1288 dic[e] = 2
1289 self.assertEqual(len(dic), 1)
1290 self.assertEqual(dic[d], 2)
1291 self.assertEqual(dic[e], 2)
1292
1293 d = self.theclass(2001, 1, 1, 0, 5, 17)
1294 e = self.theclass(2001, 1, 1, 0, 5, 17)
1295 self.assertEqual(d, e)
1296 self.assertEqual(hash(d), hash(e))
1297
1298 dic = {d: 1}
1299 dic[e] = 2
1300 self.assertEqual(len(dic), 1)
1301 self.assertEqual(dic[d], 2)
1302 self.assertEqual(dic[e], 2)
1303
1304 def test_computations(self):
1305 a = self.theclass(2002, 1, 31)
1306 b = self.theclass(1956, 1, 31)
1307 diff = a-b
1308 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1309 self.assertEqual(diff.seconds, 0)
1310 self.assertEqual(diff.microseconds, 0)
1311 a = self.theclass(2002, 3, 2, 17, 6)
1312 millisec = timedelta(0, 0, 1000)
1313 hour = timedelta(0, 3600)
1314 day = timedelta(1)
1315 week = timedelta(7)
1316 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1317 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1318 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1319 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1320 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1321 self.assertEqual(a - hour, a + -hour)
1322 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1323 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1324 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1325 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1326 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1327 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1328 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1329 self.assertEqual((a + week) - a, week)
1330 self.assertEqual((a + day) - a, day)
1331 self.assertEqual((a + hour) - a, hour)
1332 self.assertEqual((a + millisec) - a, millisec)
1333 self.assertEqual((a - week) - a, -week)
1334 self.assertEqual((a - day) - a, -day)
1335 self.assertEqual((a - hour) - a, -hour)
1336 self.assertEqual((a - millisec) - a, -millisec)
1337 self.assertEqual(a - (a + week), -week)
1338 self.assertEqual(a - (a + day), -day)
1339 self.assertEqual(a - (a + hour), -hour)
1340 self.assertEqual(a - (a + millisec), -millisec)
1341 self.assertEqual(a - (a - week), week)
1342 self.assertEqual(a - (a - day), day)
1343 self.assertEqual(a - (a - hour), hour)
1344 self.assertEqual(a - (a - millisec), millisec)
1345 self.assertEqual(a + (week + day + hour + millisec),
1346 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1347 self.assertEqual(a + (week + day + hour + millisec),
1348 (((a + week) + day) + hour) + millisec)
1349 self.assertEqual(a - (week + day + hour + millisec),
1350 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1351 self.assertEqual(a - (week + day + hour + millisec),
1352 (((a - week) - day) - hour) - millisec)
1353 # Add/sub ints, longs, floats should be illegal
1354 for i in 1, 1L, 1.0:
1355 self.assertRaises(TypeError, lambda: a+i)
1356 self.assertRaises(TypeError, lambda: a-i)
1357 self.assertRaises(TypeError, lambda: i+a)
1358 self.assertRaises(TypeError, lambda: i-a)
1359
1360 # delta - datetime is senseless.
1361 self.assertRaises(TypeError, lambda: day - a)
1362 # mixing datetime and (delta or datetime) via * or // is senseless
1363 self.assertRaises(TypeError, lambda: day * a)
1364 self.assertRaises(TypeError, lambda: a * day)
1365 self.assertRaises(TypeError, lambda: day // a)
1366 self.assertRaises(TypeError, lambda: a // day)
1367 self.assertRaises(TypeError, lambda: a * a)
1368 self.assertRaises(TypeError, lambda: a // a)
1369 # datetime + datetime is senseless
1370 self.assertRaises(TypeError, lambda: a + a)
1371
1372 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001373 args = 6, 7, 23, 20, 59, 1, 64**2
1374 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001375 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001376 green = pickler.dumps(orig, proto)
1377 derived = unpickler.loads(green)
1378 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001379
Guido van Rossum275666f2003-02-07 21:49:01 +00001380 def test_more_pickling(self):
1381 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1382 s = pickle.dumps(a)
1383 b = pickle.loads(s)
1384 self.assertEqual(b.year, 2003)
1385 self.assertEqual(b.month, 2)
1386 self.assertEqual(b.day, 7)
1387
Tim Peters604c0132004-06-07 23:04:33 +00001388 def test_pickling_subclass_datetime(self):
1389 args = 6, 7, 23, 20, 59, 1, 64**2
1390 orig = SubclassDatetime(*args)
1391 for pickler, unpickler, proto in pickle_choices:
1392 green = pickler.dumps(orig, proto)
1393 derived = unpickler.loads(green)
1394 self.assertEqual(orig, derived)
1395
Tim Peters2a799bf2002-12-16 20:18:38 +00001396 def test_more_compare(self):
1397 # The test_compare() inherited from TestDate covers the error cases.
1398 # We just want to test lexicographic ordering on the members datetime
1399 # has that date lacks.
1400 args = [2000, 11, 29, 20, 58, 16, 999998]
1401 t1 = self.theclass(*args)
1402 t2 = self.theclass(*args)
1403 self.failUnless(t1 == t2)
1404 self.failUnless(t1 <= t2)
1405 self.failUnless(t1 >= t2)
1406 self.failUnless(not t1 != t2)
1407 self.failUnless(not t1 < t2)
1408 self.failUnless(not t1 > t2)
1409 self.assertEqual(cmp(t1, t2), 0)
1410 self.assertEqual(cmp(t2, t1), 0)
1411
1412 for i in range(len(args)):
1413 newargs = args[:]
1414 newargs[i] = args[i] + 1
1415 t2 = self.theclass(*newargs) # this is larger than t1
1416 self.failUnless(t1 < t2)
1417 self.failUnless(t2 > t1)
1418 self.failUnless(t1 <= t2)
1419 self.failUnless(t2 >= t1)
1420 self.failUnless(t1 != t2)
1421 self.failUnless(t2 != t1)
1422 self.failUnless(not t1 == t2)
1423 self.failUnless(not t2 == t1)
1424 self.failUnless(not t1 > t2)
1425 self.failUnless(not t2 < t1)
1426 self.failUnless(not t1 >= t2)
1427 self.failUnless(not t2 <= t1)
1428 self.assertEqual(cmp(t1, t2), -1)
1429 self.assertEqual(cmp(t2, t1), 1)
1430
1431
1432 # A helper for timestamp constructor tests.
1433 def verify_field_equality(self, expected, got):
1434 self.assertEqual(expected.tm_year, got.year)
1435 self.assertEqual(expected.tm_mon, got.month)
1436 self.assertEqual(expected.tm_mday, got.day)
1437 self.assertEqual(expected.tm_hour, got.hour)
1438 self.assertEqual(expected.tm_min, got.minute)
1439 self.assertEqual(expected.tm_sec, got.second)
1440
1441 def test_fromtimestamp(self):
1442 import time
1443
1444 ts = time.time()
1445 expected = time.localtime(ts)
1446 got = self.theclass.fromtimestamp(ts)
1447 self.verify_field_equality(expected, got)
1448
1449 def test_utcfromtimestamp(self):
1450 import time
1451
1452 ts = time.time()
1453 expected = time.gmtime(ts)
1454 got = self.theclass.utcfromtimestamp(ts)
1455 self.verify_field_equality(expected, got)
1456
Georg Brandl6d78a582006-04-28 19:09:24 +00001457 def test_microsecond_rounding(self):
1458 # Test whether fromtimestamp "rounds up" floats that are less
1459 # than one microsecond smaller than an integer.
1460 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1461 self.theclass.fromtimestamp(1))
1462
Tim Peters1b6f7a92004-06-20 02:50:16 +00001463 def test_insane_fromtimestamp(self):
1464 # It's possible that some platform maps time_t to double,
1465 # and that this test will fail there. This test should
1466 # exempt such platforms (provided they return reasonable
1467 # results!).
1468 for insane in -1e200, 1e200:
1469 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1470 insane)
1471
1472 def test_insane_utcfromtimestamp(self):
1473 # It's possible that some platform maps time_t to double,
1474 # and that this test will fail there. This test should
1475 # exempt such platforms (provided they return reasonable
1476 # results!).
1477 for insane in -1e200, 1e200:
1478 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1479 insane)
1480
Guido van Rossum2054ee92007-03-06 15:50:01 +00001481 def test_negative_float_fromtimestamp(self):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001482 # Windows doesn't accept negative timestamps
1483 if os.name == "nt":
1484 return
Guido van Rossum2054ee92007-03-06 15:50:01 +00001485 # The result is tz-dependent; at least test that this doesn't
1486 # fail (like it did before bug 1646728 was fixed).
1487 self.theclass.fromtimestamp(-1.05)
1488
1489 def test_negative_float_utcfromtimestamp(self):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001490 # Windows doesn't accept negative timestamps
1491 if os.name == "nt":
1492 return
Guido van Rossum2054ee92007-03-06 15:50:01 +00001493 d = self.theclass.utcfromtimestamp(-1.05)
1494 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1495
Tim Peters2a799bf2002-12-16 20:18:38 +00001496 def test_utcnow(self):
1497 import time
1498
1499 # Call it a success if utcnow() and utcfromtimestamp() are within
1500 # a second of each other.
1501 tolerance = timedelta(seconds=1)
1502 for dummy in range(3):
1503 from_now = self.theclass.utcnow()
1504 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1505 if abs(from_timestamp - from_now) <= tolerance:
1506 break
1507 # Else try again a few times.
1508 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1509
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001510 def test_strptime(self):
1511 import time
1512
1513 string = '2004-12-01 13:02:47'
1514 format = '%Y-%m-%d %H:%M:%S'
1515 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1516 got = self.theclass.strptime(string, format)
1517 self.assertEqual(expected, got)
1518
Tim Peters2a799bf2002-12-16 20:18:38 +00001519 def test_more_timetuple(self):
1520 # This tests fields beyond those tested by the TestDate.test_timetuple.
1521 t = self.theclass(2004, 12, 31, 6, 22, 33)
1522 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1523 self.assertEqual(t.timetuple(),
1524 (t.year, t.month, t.day,
1525 t.hour, t.minute, t.second,
1526 t.weekday(),
1527 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1528 -1))
1529 tt = t.timetuple()
1530 self.assertEqual(tt.tm_year, t.year)
1531 self.assertEqual(tt.tm_mon, t.month)
1532 self.assertEqual(tt.tm_mday, t.day)
1533 self.assertEqual(tt.tm_hour, t.hour)
1534 self.assertEqual(tt.tm_min, t.minute)
1535 self.assertEqual(tt.tm_sec, t.second)
1536 self.assertEqual(tt.tm_wday, t.weekday())
1537 self.assertEqual(tt.tm_yday, t.toordinal() -
1538 date(t.year, 1, 1).toordinal() + 1)
1539 self.assertEqual(tt.tm_isdst, -1)
1540
1541 def test_more_strftime(self):
1542 # This tests fields beyond those tested by the TestDate.test_strftime.
1543 t = self.theclass(2004, 12, 31, 6, 22, 33)
1544 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1545 "12 31 04 33 22 06 366")
1546
1547 def test_extract(self):
1548 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1549 self.assertEqual(dt.date(), date(2002, 3, 4))
1550 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1551
1552 def test_combine(self):
1553 d = date(2002, 3, 4)
1554 t = time(18, 45, 3, 1234)
1555 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1556 combine = self.theclass.combine
1557 dt = combine(d, t)
1558 self.assertEqual(dt, expected)
1559
1560 dt = combine(time=t, date=d)
1561 self.assertEqual(dt, expected)
1562
1563 self.assertEqual(d, dt.date())
1564 self.assertEqual(t, dt.time())
1565 self.assertEqual(dt, combine(dt.date(), dt.time()))
1566
1567 self.assertRaises(TypeError, combine) # need an arg
1568 self.assertRaises(TypeError, combine, d) # need two args
1569 self.assertRaises(TypeError, combine, t, d) # args reversed
1570 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1571 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1572
Tim Peters12bf3392002-12-24 05:41:27 +00001573 def test_replace(self):
1574 cls = self.theclass
1575 args = [1, 2, 3, 4, 5, 6, 7]
1576 base = cls(*args)
1577 self.assertEqual(base, base.replace())
1578
1579 i = 0
1580 for name, newval in (("year", 2),
1581 ("month", 3),
1582 ("day", 4),
1583 ("hour", 5),
1584 ("minute", 6),
1585 ("second", 7),
1586 ("microsecond", 8)):
1587 newargs = args[:]
1588 newargs[i] = newval
1589 expected = cls(*newargs)
1590 got = base.replace(**{name: newval})
1591 self.assertEqual(expected, got)
1592 i += 1
1593
1594 # Out of bounds.
1595 base = cls(2000, 2, 29)
1596 self.assertRaises(ValueError, base.replace, year=2001)
1597
Tim Peters80475bb2002-12-25 07:40:55 +00001598 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001599 # Pretty boring! The TZ test is more interesting here. astimezone()
1600 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001601 dt = self.theclass.now()
1602 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001603 self.assertRaises(TypeError, dt.astimezone) # not enough args
1604 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1605 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001606 self.assertRaises(ValueError, dt.astimezone, f) # naive
1607 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001608
Tim Peters52dcce22003-01-23 16:36:11 +00001609 class Bogus(tzinfo):
1610 def utcoffset(self, dt): return None
1611 def dst(self, dt): return timedelta(0)
1612 bog = Bogus()
1613 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1614
1615 class AlsoBogus(tzinfo):
1616 def utcoffset(self, dt): return timedelta(0)
1617 def dst(self, dt): return None
1618 alsobog = AlsoBogus()
1619 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001620
Tim Petersa98924a2003-05-17 05:55:19 +00001621 def test_subclass_datetime(self):
1622
1623 class C(self.theclass):
1624 theAnswer = 42
1625
1626 def __new__(cls, *args, **kws):
1627 temp = kws.copy()
1628 extra = temp.pop('extra')
1629 result = self.theclass.__new__(cls, *args, **temp)
1630 result.extra = extra
1631 return result
1632
1633 def newmeth(self, start):
1634 return start + self.year + self.month + self.second
1635
1636 args = 2003, 4, 14, 12, 13, 41
1637
1638 dt1 = self.theclass(*args)
1639 dt2 = C(*args, **{'extra': 7})
1640
1641 self.assertEqual(dt2.__class__, C)
1642 self.assertEqual(dt2.theAnswer, 42)
1643 self.assertEqual(dt2.extra, 7)
1644 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1645 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1646 dt1.second - 7)
1647
Tim Peters604c0132004-06-07 23:04:33 +00001648class SubclassTime(time):
1649 sub_var = 1
1650
Collin Winterc2898c52007-04-25 17:29:52 +00001651class TestTime(HarmlessMixedComparison, unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001652
1653 theclass = time
1654
1655 def test_basic_attributes(self):
1656 t = self.theclass(12, 0)
1657 self.assertEqual(t.hour, 12)
1658 self.assertEqual(t.minute, 0)
1659 self.assertEqual(t.second, 0)
1660 self.assertEqual(t.microsecond, 0)
1661
1662 def test_basic_attributes_nonzero(self):
1663 # Make sure all attributes are non-zero so bugs in
1664 # bit-shifting access show up.
1665 t = self.theclass(12, 59, 59, 8000)
1666 self.assertEqual(t.hour, 12)
1667 self.assertEqual(t.minute, 59)
1668 self.assertEqual(t.second, 59)
1669 self.assertEqual(t.microsecond, 8000)
1670
1671 def test_roundtrip(self):
1672 t = self.theclass(1, 2, 3, 4)
1673
1674 # Verify t -> string -> time identity.
1675 s = repr(t)
1676 self.failUnless(s.startswith('datetime.'))
1677 s = s[9:]
1678 t2 = eval(s)
1679 self.assertEqual(t, t2)
1680
1681 # Verify identity via reconstructing from pieces.
1682 t2 = self.theclass(t.hour, t.minute, t.second,
1683 t.microsecond)
1684 self.assertEqual(t, t2)
1685
1686 def test_comparing(self):
1687 args = [1, 2, 3, 4]
1688 t1 = self.theclass(*args)
1689 t2 = self.theclass(*args)
1690 self.failUnless(t1 == t2)
1691 self.failUnless(t1 <= t2)
1692 self.failUnless(t1 >= t2)
1693 self.failUnless(not t1 != t2)
1694 self.failUnless(not t1 < t2)
1695 self.failUnless(not t1 > t2)
1696 self.assertEqual(cmp(t1, t2), 0)
1697 self.assertEqual(cmp(t2, t1), 0)
1698
1699 for i in range(len(args)):
1700 newargs = args[:]
1701 newargs[i] = args[i] + 1
1702 t2 = self.theclass(*newargs) # this is larger than t1
1703 self.failUnless(t1 < t2)
1704 self.failUnless(t2 > t1)
1705 self.failUnless(t1 <= t2)
1706 self.failUnless(t2 >= t1)
1707 self.failUnless(t1 != t2)
1708 self.failUnless(t2 != t1)
1709 self.failUnless(not t1 == t2)
1710 self.failUnless(not t2 == t1)
1711 self.failUnless(not t1 > t2)
1712 self.failUnless(not t2 < t1)
1713 self.failUnless(not t1 >= t2)
1714 self.failUnless(not t2 <= t1)
1715 self.assertEqual(cmp(t1, t2), -1)
1716 self.assertEqual(cmp(t2, t1), 1)
1717
Tim Peters68124bb2003-02-08 03:46:31 +00001718 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001719 self.assertEqual(t1 == badarg, False)
1720 self.assertEqual(t1 != badarg, True)
1721 self.assertEqual(badarg == t1, False)
1722 self.assertEqual(badarg != t1, True)
1723
Tim Peters2a799bf2002-12-16 20:18:38 +00001724 self.assertRaises(TypeError, lambda: t1 <= badarg)
1725 self.assertRaises(TypeError, lambda: t1 < badarg)
1726 self.assertRaises(TypeError, lambda: t1 > badarg)
1727 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001728 self.assertRaises(TypeError, lambda: badarg <= t1)
1729 self.assertRaises(TypeError, lambda: badarg < t1)
1730 self.assertRaises(TypeError, lambda: badarg > t1)
1731 self.assertRaises(TypeError, lambda: badarg >= t1)
1732
1733 def test_bad_constructor_arguments(self):
1734 # bad hours
1735 self.theclass(0, 0) # no exception
1736 self.theclass(23, 0) # no exception
1737 self.assertRaises(ValueError, self.theclass, -1, 0)
1738 self.assertRaises(ValueError, self.theclass, 24, 0)
1739 # bad minutes
1740 self.theclass(23, 0) # no exception
1741 self.theclass(23, 59) # no exception
1742 self.assertRaises(ValueError, self.theclass, 23, -1)
1743 self.assertRaises(ValueError, self.theclass, 23, 60)
1744 # bad seconds
1745 self.theclass(23, 59, 0) # no exception
1746 self.theclass(23, 59, 59) # no exception
1747 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1748 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1749 # bad microseconds
1750 self.theclass(23, 59, 59, 0) # no exception
1751 self.theclass(23, 59, 59, 999999) # no exception
1752 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1753 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1754
1755 def test_hash_equality(self):
1756 d = self.theclass(23, 30, 17)
1757 e = self.theclass(23, 30, 17)
1758 self.assertEqual(d, e)
1759 self.assertEqual(hash(d), hash(e))
1760
1761 dic = {d: 1}
1762 dic[e] = 2
1763 self.assertEqual(len(dic), 1)
1764 self.assertEqual(dic[d], 2)
1765 self.assertEqual(dic[e], 2)
1766
1767 d = self.theclass(0, 5, 17)
1768 e = self.theclass(0, 5, 17)
1769 self.assertEqual(d, e)
1770 self.assertEqual(hash(d), hash(e))
1771
1772 dic = {d: 1}
1773 dic[e] = 2
1774 self.assertEqual(len(dic), 1)
1775 self.assertEqual(dic[d], 2)
1776 self.assertEqual(dic[e], 2)
1777
1778 def test_isoformat(self):
1779 t = self.theclass(4, 5, 1, 123)
1780 self.assertEqual(t.isoformat(), "04:05:01.000123")
1781 self.assertEqual(t.isoformat(), str(t))
1782
1783 t = self.theclass()
1784 self.assertEqual(t.isoformat(), "00:00:00")
1785 self.assertEqual(t.isoformat(), str(t))
1786
1787 t = self.theclass(microsecond=1)
1788 self.assertEqual(t.isoformat(), "00:00:00.000001")
1789 self.assertEqual(t.isoformat(), str(t))
1790
1791 t = self.theclass(microsecond=10)
1792 self.assertEqual(t.isoformat(), "00:00:00.000010")
1793 self.assertEqual(t.isoformat(), str(t))
1794
1795 t = self.theclass(microsecond=100)
1796 self.assertEqual(t.isoformat(), "00:00:00.000100")
1797 self.assertEqual(t.isoformat(), str(t))
1798
1799 t = self.theclass(microsecond=1000)
1800 self.assertEqual(t.isoformat(), "00:00:00.001000")
1801 self.assertEqual(t.isoformat(), str(t))
1802
1803 t = self.theclass(microsecond=10000)
1804 self.assertEqual(t.isoformat(), "00:00:00.010000")
1805 self.assertEqual(t.isoformat(), str(t))
1806
1807 t = self.theclass(microsecond=100000)
1808 self.assertEqual(t.isoformat(), "00:00:00.100000")
1809 self.assertEqual(t.isoformat(), str(t))
1810
Martin v. Löwis4c11a922007-02-08 09:13:36 +00001811 def test_1653736(self):
1812 # verify it doesn't accept extra keyword arguments
1813 t = self.theclass(second=1)
1814 self.assertRaises(TypeError, t.isoformat, foo=3)
1815
Tim Peters2a799bf2002-12-16 20:18:38 +00001816 def test_strftime(self):
1817 t = self.theclass(1, 2, 3, 4)
1818 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1819 # A naive object replaces %z and %Z with empty strings.
1820 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1821
Eric Smitha9f7d622008-02-17 19:46:49 +00001822 def test_format(self):
1823 t = self.theclass(1, 2, 3, 4)
1824 self.assertEqual(t.__format__(''), str(t))
1825
1826 # check that a derived class's __str__() gets called
1827 class A(self.theclass):
1828 def __str__(self):
1829 return 'A'
1830 a = A(1, 2, 3, 4)
1831 self.assertEqual(a.__format__(''), 'A')
1832
1833 # check that a derived class's strftime gets called
1834 class B(self.theclass):
1835 def strftime(self, format_spec):
1836 return 'B'
1837 b = B(1, 2, 3, 4)
1838 self.assertEqual(b.__format__(''), str(t))
1839
1840 for fmt in ['%H %M %S',
1841 ]:
1842 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
1843 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
1844 self.assertEqual(b.__format__(fmt), 'B')
1845
Tim Peters2a799bf2002-12-16 20:18:38 +00001846 def test_str(self):
1847 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1848 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1849 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1850 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1851 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1852
1853 def test_repr(self):
1854 name = 'datetime.' + self.theclass.__name__
1855 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1856 "%s(1, 2, 3, 4)" % name)
1857 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1858 "%s(10, 2, 3, 4000)" % name)
1859 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1860 "%s(0, 2, 3, 400000)" % name)
1861 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1862 "%s(12, 2, 3)" % name)
1863 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1864 "%s(23, 15)" % name)
1865
1866 def test_resolution_info(self):
1867 self.assert_(isinstance(self.theclass.min, self.theclass))
1868 self.assert_(isinstance(self.theclass.max, self.theclass))
1869 self.assert_(isinstance(self.theclass.resolution, timedelta))
1870 self.assert_(self.theclass.max > self.theclass.min)
1871
1872 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001873 args = 20, 59, 16, 64**2
1874 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001875 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001876 green = pickler.dumps(orig, proto)
1877 derived = unpickler.loads(green)
1878 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001879
Tim Peters604c0132004-06-07 23:04:33 +00001880 def test_pickling_subclass_time(self):
1881 args = 20, 59, 16, 64**2
1882 orig = SubclassTime(*args)
1883 for pickler, unpickler, proto in pickle_choices:
1884 green = pickler.dumps(orig, proto)
1885 derived = unpickler.loads(green)
1886 self.assertEqual(orig, derived)
1887
Tim Peters2a799bf2002-12-16 20:18:38 +00001888 def test_bool(self):
1889 cls = self.theclass
1890 self.failUnless(cls(1))
1891 self.failUnless(cls(0, 1))
1892 self.failUnless(cls(0, 0, 1))
1893 self.failUnless(cls(0, 0, 0, 1))
1894 self.failUnless(not cls(0))
1895 self.failUnless(not cls())
1896
Tim Peters12bf3392002-12-24 05:41:27 +00001897 def test_replace(self):
1898 cls = self.theclass
1899 args = [1, 2, 3, 4]
1900 base = cls(*args)
1901 self.assertEqual(base, base.replace())
1902
1903 i = 0
1904 for name, newval in (("hour", 5),
1905 ("minute", 6),
1906 ("second", 7),
1907 ("microsecond", 8)):
1908 newargs = args[:]
1909 newargs[i] = newval
1910 expected = cls(*newargs)
1911 got = base.replace(**{name: newval})
1912 self.assertEqual(expected, got)
1913 i += 1
1914
1915 # Out of bounds.
1916 base = cls(1)
1917 self.assertRaises(ValueError, base.replace, hour=24)
1918 self.assertRaises(ValueError, base.replace, minute=-1)
1919 self.assertRaises(ValueError, base.replace, second=100)
1920 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1921
Tim Petersa98924a2003-05-17 05:55:19 +00001922 def test_subclass_time(self):
1923
1924 class C(self.theclass):
1925 theAnswer = 42
1926
1927 def __new__(cls, *args, **kws):
1928 temp = kws.copy()
1929 extra = temp.pop('extra')
1930 result = self.theclass.__new__(cls, *args, **temp)
1931 result.extra = extra
1932 return result
1933
1934 def newmeth(self, start):
1935 return start + self.hour + self.second
1936
1937 args = 4, 5, 6
1938
1939 dt1 = self.theclass(*args)
1940 dt2 = C(*args, **{'extra': 7})
1941
1942 self.assertEqual(dt2.__class__, C)
1943 self.assertEqual(dt2.theAnswer, 42)
1944 self.assertEqual(dt2.extra, 7)
1945 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1946 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1947
Armin Rigof4afb212005-11-07 07:15:48 +00001948 def test_backdoor_resistance(self):
1949 # see TestDate.test_backdoor_resistance().
1950 base = '2:59.0'
1951 for hour_byte in ' ', '9', chr(24), '\xff':
1952 self.assertRaises(TypeError, self.theclass,
1953 hour_byte + base[1:])
1954
Tim Peters855fe882002-12-22 03:43:39 +00001955# A mixin for classes with a tzinfo= argument. Subclasses must define
1956# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001957# must be legit (which is true for time and datetime).
Collin Winterc2898c52007-04-25 17:29:52 +00001958class TZInfoBase:
Tim Peters2a799bf2002-12-16 20:18:38 +00001959
Tim Petersbad8ff02002-12-30 20:52:32 +00001960 def test_argument_passing(self):
1961 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001962 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001963 class introspective(tzinfo):
1964 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001965 def utcoffset(self, dt):
1966 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001967 dst = utcoffset
1968
1969 obj = cls(1, 2, 3, tzinfo=introspective())
1970
Tim Peters0bf60bd2003-01-08 20:40:01 +00001971 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001972 self.assertEqual(obj.tzname(), expected)
1973
Tim Peters0bf60bd2003-01-08 20:40:01 +00001974 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001975 self.assertEqual(obj.utcoffset(), expected)
1976 self.assertEqual(obj.dst(), expected)
1977
Tim Peters855fe882002-12-22 03:43:39 +00001978 def test_bad_tzinfo_classes(self):
1979 cls = self.theclass
1980 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001981
Tim Peters855fe882002-12-22 03:43:39 +00001982 class NiceTry(object):
1983 def __init__(self): pass
1984 def utcoffset(self, dt): pass
1985 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1986
1987 class BetterTry(tzinfo):
1988 def __init__(self): pass
1989 def utcoffset(self, dt): pass
1990 b = BetterTry()
1991 t = cls(1, 1, 1, tzinfo=b)
1992 self.failUnless(t.tzinfo is b)
1993
1994 def test_utc_offset_out_of_bounds(self):
1995 class Edgy(tzinfo):
1996 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001997 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001998 def utcoffset(self, dt):
1999 return self.offset
2000
2001 cls = self.theclass
2002 for offset, legit in ((-1440, False),
2003 (-1439, True),
2004 (1439, True),
2005 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002006 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00002007 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002008 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00002009 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002010 else:
2011 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00002012 if legit:
2013 aofs = abs(offset)
2014 h, m = divmod(aofs, 60)
2015 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002016 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00002017 t = t.timetz()
2018 self.assertEqual(str(t), "01:02:03" + tag)
2019 else:
2020 self.assertRaises(ValueError, str, t)
2021
2022 def test_tzinfo_classes(self):
2023 cls = self.theclass
2024 class C1(tzinfo):
2025 def utcoffset(self, dt): return None
2026 def dst(self, dt): return None
2027 def tzname(self, dt): return None
2028 for t in (cls(1, 1, 1),
2029 cls(1, 1, 1, tzinfo=None),
2030 cls(1, 1, 1, tzinfo=C1())):
2031 self.failUnless(t.utcoffset() is None)
2032 self.failUnless(t.dst() is None)
2033 self.failUnless(t.tzname() is None)
2034
Tim Peters855fe882002-12-22 03:43:39 +00002035 class C3(tzinfo):
2036 def utcoffset(self, dt): return timedelta(minutes=-1439)
2037 def dst(self, dt): return timedelta(minutes=1439)
2038 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00002039 t = cls(1, 1, 1, tzinfo=C3())
2040 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2041 self.assertEqual(t.dst(), timedelta(minutes=1439))
2042 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00002043
2044 # Wrong types.
2045 class C4(tzinfo):
2046 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00002047 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00002048 def tzname(self, dt): return 0
2049 t = cls(1, 1, 1, tzinfo=C4())
2050 self.assertRaises(TypeError, t.utcoffset)
2051 self.assertRaises(TypeError, t.dst)
2052 self.assertRaises(TypeError, t.tzname)
2053
2054 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00002055 class C6(tzinfo):
2056 def utcoffset(self, dt): return timedelta(hours=-24)
2057 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00002058 t = cls(1, 1, 1, tzinfo=C6())
2059 self.assertRaises(ValueError, t.utcoffset)
2060 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00002061
2062 # Not a whole number of minutes.
2063 class C7(tzinfo):
2064 def utcoffset(self, dt): return timedelta(seconds=61)
2065 def dst(self, dt): return timedelta(microseconds=-81)
2066 t = cls(1, 1, 1, tzinfo=C7())
2067 self.assertRaises(ValueError, t.utcoffset)
2068 self.assertRaises(ValueError, t.dst)
2069
Tim Peters4c0db782002-12-26 05:01:19 +00002070 def test_aware_compare(self):
2071 cls = self.theclass
2072
Tim Peters60c76e42002-12-27 00:41:11 +00002073 # Ensure that utcoffset() gets ignored if the comparands have
2074 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002075 class OperandDependentOffset(tzinfo):
2076 def utcoffset(self, t):
2077 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002078 # d0 and d1 equal after adjustment
2079 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002080 else:
Tim Peters397301e2003-01-02 21:28:08 +00002081 # d2 off in the weeds
2082 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002083
2084 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2085 d0 = base.replace(minute=3)
2086 d1 = base.replace(minute=9)
2087 d2 = base.replace(minute=11)
2088 for x in d0, d1, d2:
2089 for y in d0, d1, d2:
2090 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002091 expected = cmp(x.minute, y.minute)
2092 self.assertEqual(got, expected)
2093
2094 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002095 # Note that a time can't actually have an operand-depedent offset,
2096 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2097 # so skip this test for time.
2098 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002099 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2100 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2101 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2102 for x in d0, d1, d2:
2103 for y in d0, d1, d2:
2104 got = cmp(x, y)
2105 if (x is d0 or x is d1) and (y is d0 or y is d1):
2106 expected = 0
2107 elif x is y is d2:
2108 expected = 0
2109 elif x is d2:
2110 expected = -1
2111 else:
2112 assert y is d2
2113 expected = 1
2114 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002115
Tim Peters855fe882002-12-22 03:43:39 +00002116
Tim Peters0bf60bd2003-01-08 20:40:01 +00002117# Testing time objects with a non-None tzinfo.
Collin Winterc2898c52007-04-25 17:29:52 +00002118class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002119 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002120
2121 def test_empty(self):
2122 t = self.theclass()
2123 self.assertEqual(t.hour, 0)
2124 self.assertEqual(t.minute, 0)
2125 self.assertEqual(t.second, 0)
2126 self.assertEqual(t.microsecond, 0)
2127 self.failUnless(t.tzinfo is None)
2128
Tim Peters2a799bf2002-12-16 20:18:38 +00002129 def test_zones(self):
2130 est = FixedOffset(-300, "EST", 1)
2131 utc = FixedOffset(0, "UTC", -2)
2132 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002133 t1 = time( 7, 47, tzinfo=est)
2134 t2 = time(12, 47, tzinfo=utc)
2135 t3 = time(13, 47, tzinfo=met)
2136 t4 = time(microsecond=40)
2137 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002138
2139 self.assertEqual(t1.tzinfo, est)
2140 self.assertEqual(t2.tzinfo, utc)
2141 self.assertEqual(t3.tzinfo, met)
2142 self.failUnless(t4.tzinfo is None)
2143 self.assertEqual(t5.tzinfo, utc)
2144
Tim Peters855fe882002-12-22 03:43:39 +00002145 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2146 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2147 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002148 self.failUnless(t4.utcoffset() is None)
2149 self.assertRaises(TypeError, t1.utcoffset, "no args")
2150
2151 self.assertEqual(t1.tzname(), "EST")
2152 self.assertEqual(t2.tzname(), "UTC")
2153 self.assertEqual(t3.tzname(), "MET")
2154 self.failUnless(t4.tzname() is None)
2155 self.assertRaises(TypeError, t1.tzname, "no args")
2156
Tim Peters855fe882002-12-22 03:43:39 +00002157 self.assertEqual(t1.dst(), timedelta(minutes=1))
2158 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2159 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002160 self.failUnless(t4.dst() is None)
2161 self.assertRaises(TypeError, t1.dst, "no args")
2162
2163 self.assertEqual(hash(t1), hash(t2))
2164 self.assertEqual(hash(t1), hash(t3))
2165 self.assertEqual(hash(t2), hash(t3))
2166
2167 self.assertEqual(t1, t2)
2168 self.assertEqual(t1, t3)
2169 self.assertEqual(t2, t3)
2170 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2171 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2172 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2173
2174 self.assertEqual(str(t1), "07:47:00-05:00")
2175 self.assertEqual(str(t2), "12:47:00+00:00")
2176 self.assertEqual(str(t3), "13:47:00+01:00")
2177 self.assertEqual(str(t4), "00:00:00.000040")
2178 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2179
2180 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2181 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2182 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2183 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2184 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2185
Tim Peters0bf60bd2003-01-08 20:40:01 +00002186 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002187 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2188 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2189 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2190 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2191 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2192
2193 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2194 "07:47:00 %Z=EST %z=-0500")
2195 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2196 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2197
2198 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002199 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002200 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2201 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2202
Tim Petersb92bb712002-12-21 17:44:07 +00002203 # Check that an invalid tzname result raises an exception.
2204 class Badtzname(tzinfo):
2205 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002206 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002207 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2208 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002209
2210 def test_hash_edge_cases(self):
2211 # Offsets that overflow a basic time.
2212 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2213 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2214 self.assertEqual(hash(t1), hash(t2))
2215
2216 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2217 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2218 self.assertEqual(hash(t1), hash(t2))
2219
Tim Peters2a799bf2002-12-16 20:18:38 +00002220 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002221 # Try one without a tzinfo.
2222 args = 20, 59, 16, 64**2
2223 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002224 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002225 green = pickler.dumps(orig, proto)
2226 derived = unpickler.loads(green)
2227 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002228
2229 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002230 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002231 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002232 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002233 green = pickler.dumps(orig, proto)
2234 derived = unpickler.loads(green)
2235 self.assertEqual(orig, derived)
2236 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2237 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2238 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002239
2240 def test_more_bool(self):
2241 # Test cases with non-None tzinfo.
2242 cls = self.theclass
2243
2244 t = cls(0, tzinfo=FixedOffset(-300, ""))
2245 self.failUnless(t)
2246
2247 t = cls(5, tzinfo=FixedOffset(-300, ""))
2248 self.failUnless(t)
2249
2250 t = cls(5, tzinfo=FixedOffset(300, ""))
2251 self.failUnless(not t)
2252
2253 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2254 self.failUnless(not t)
2255
2256 # Mostly ensuring this doesn't overflow internally.
2257 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2258 self.failUnless(t)
2259
2260 # But this should yield a value error -- the utcoffset is bogus.
2261 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2262 self.assertRaises(ValueError, lambda: bool(t))
2263
2264 # Likewise.
2265 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2266 self.assertRaises(ValueError, lambda: bool(t))
2267
Tim Peters12bf3392002-12-24 05:41:27 +00002268 def test_replace(self):
2269 cls = self.theclass
2270 z100 = FixedOffset(100, "+100")
2271 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2272 args = [1, 2, 3, 4, z100]
2273 base = cls(*args)
2274 self.assertEqual(base, base.replace())
2275
2276 i = 0
2277 for name, newval in (("hour", 5),
2278 ("minute", 6),
2279 ("second", 7),
2280 ("microsecond", 8),
2281 ("tzinfo", zm200)):
2282 newargs = args[:]
2283 newargs[i] = newval
2284 expected = cls(*newargs)
2285 got = base.replace(**{name: newval})
2286 self.assertEqual(expected, got)
2287 i += 1
2288
2289 # Ensure we can get rid of a tzinfo.
2290 self.assertEqual(base.tzname(), "+100")
2291 base2 = base.replace(tzinfo=None)
2292 self.failUnless(base2.tzinfo is None)
2293 self.failUnless(base2.tzname() is None)
2294
2295 # Ensure we can add one.
2296 base3 = base2.replace(tzinfo=z100)
2297 self.assertEqual(base, base3)
2298 self.failUnless(base.tzinfo is base3.tzinfo)
2299
2300 # Out of bounds.
2301 base = cls(1)
2302 self.assertRaises(ValueError, base.replace, hour=24)
2303 self.assertRaises(ValueError, base.replace, minute=-1)
2304 self.assertRaises(ValueError, base.replace, second=100)
2305 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2306
Tim Peters60c76e42002-12-27 00:41:11 +00002307 def test_mixed_compare(self):
2308 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002309 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002310 self.assertEqual(t1, t2)
2311 t2 = t2.replace(tzinfo=None)
2312 self.assertEqual(t1, t2)
2313 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2314 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002315 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2316 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002317
Tim Peters0bf60bd2003-01-08 20:40:01 +00002318 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002319 class Varies(tzinfo):
2320 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002321 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002322 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002323 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002324 return self.offset
2325
2326 v = Varies()
2327 t1 = t2.replace(tzinfo=v)
2328 t2 = t2.replace(tzinfo=v)
2329 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2330 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2331 self.assertEqual(t1, t2)
2332
2333 # But if they're not identical, it isn't ignored.
2334 t2 = t2.replace(tzinfo=Varies())
2335 self.failUnless(t1 < t2) # t1's offset counter still going up
2336
Tim Petersa98924a2003-05-17 05:55:19 +00002337 def test_subclass_timetz(self):
2338
2339 class C(self.theclass):
2340 theAnswer = 42
2341
2342 def __new__(cls, *args, **kws):
2343 temp = kws.copy()
2344 extra = temp.pop('extra')
2345 result = self.theclass.__new__(cls, *args, **temp)
2346 result.extra = extra
2347 return result
2348
2349 def newmeth(self, start):
2350 return start + self.hour + self.second
2351
2352 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2353
2354 dt1 = self.theclass(*args)
2355 dt2 = C(*args, **{'extra': 7})
2356
2357 self.assertEqual(dt2.__class__, C)
2358 self.assertEqual(dt2.theAnswer, 42)
2359 self.assertEqual(dt2.extra, 7)
2360 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2361 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2362
Tim Peters4c0db782002-12-26 05:01:19 +00002363
Tim Peters0bf60bd2003-01-08 20:40:01 +00002364# Testing datetime objects with a non-None tzinfo.
2365
Collin Winterc2898c52007-04-25 17:29:52 +00002366class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002367 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002368
2369 def test_trivial(self):
2370 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2371 self.assertEqual(dt.year, 1)
2372 self.assertEqual(dt.month, 2)
2373 self.assertEqual(dt.day, 3)
2374 self.assertEqual(dt.hour, 4)
2375 self.assertEqual(dt.minute, 5)
2376 self.assertEqual(dt.second, 6)
2377 self.assertEqual(dt.microsecond, 7)
2378 self.assertEqual(dt.tzinfo, None)
2379
2380 def test_even_more_compare(self):
2381 # The test_compare() and test_more_compare() inherited from TestDate
2382 # and TestDateTime covered non-tzinfo cases.
2383
2384 # Smallest possible after UTC adjustment.
2385 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2386 # Largest possible after UTC adjustment.
2387 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2388 tzinfo=FixedOffset(-1439, ""))
2389
2390 # Make sure those compare correctly, and w/o overflow.
2391 self.failUnless(t1 < t2)
2392 self.failUnless(t1 != t2)
2393 self.failUnless(t2 > t1)
2394
2395 self.failUnless(t1 == t1)
2396 self.failUnless(t2 == t2)
2397
2398 # Equal afer adjustment.
2399 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2400 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2401 self.assertEqual(t1, t2)
2402
2403 # Change t1 not to subtract a minute, and t1 should be larger.
2404 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2405 self.failUnless(t1 > t2)
2406
2407 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2408 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2409 self.failUnless(t1 < t2)
2410
2411 # Back to the original t1, but make seconds resolve it.
2412 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2413 second=1)
2414 self.failUnless(t1 > t2)
2415
2416 # Likewise, but make microseconds resolve it.
2417 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2418 microsecond=1)
2419 self.failUnless(t1 > t2)
2420
2421 # Make t2 naive and it should fail.
2422 t2 = self.theclass.min
2423 self.assertRaises(TypeError, lambda: t1 == t2)
2424 self.assertEqual(t2, t2)
2425
2426 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2427 class Naive(tzinfo):
2428 def utcoffset(self, dt): return None
2429 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2430 self.assertRaises(TypeError, lambda: t1 == t2)
2431 self.assertEqual(t2, t2)
2432
2433 # OTOH, it's OK to compare two of these mixing the two ways of being
2434 # naive.
2435 t1 = self.theclass(5, 6, 7)
2436 self.assertEqual(t1, t2)
2437
2438 # Try a bogus uctoffset.
2439 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002440 def utcoffset(self, dt):
2441 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002442 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2443 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002444 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002445
Tim Peters2a799bf2002-12-16 20:18:38 +00002446 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002447 # Try one without a tzinfo.
2448 args = 6, 7, 23, 20, 59, 1, 64**2
2449 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002450 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002451 green = pickler.dumps(orig, proto)
2452 derived = unpickler.loads(green)
2453 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002454
2455 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002456 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002457 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002458 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002459 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002460 green = pickler.dumps(orig, proto)
2461 derived = unpickler.loads(green)
2462 self.assertEqual(orig, derived)
2463 self.failUnless(isinstance(derived.tzinfo,
2464 PicklableFixedOffset))
2465 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2466 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002467
2468 def test_extreme_hashes(self):
2469 # If an attempt is made to hash these via subtracting the offset
2470 # then hashing a datetime object, OverflowError results. The
2471 # Python implementation used to blow up here.
2472 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2473 hash(t)
2474 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2475 tzinfo=FixedOffset(-1439, ""))
2476 hash(t)
2477
2478 # OTOH, an OOB offset should blow up.
2479 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2480 self.assertRaises(ValueError, hash, t)
2481
2482 def test_zones(self):
2483 est = FixedOffset(-300, "EST")
2484 utc = FixedOffset(0, "UTC")
2485 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002486 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2487 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2488 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002489 self.assertEqual(t1.tzinfo, est)
2490 self.assertEqual(t2.tzinfo, utc)
2491 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002492 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2493 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2494 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002495 self.assertEqual(t1.tzname(), "EST")
2496 self.assertEqual(t2.tzname(), "UTC")
2497 self.assertEqual(t3.tzname(), "MET")
2498 self.assertEqual(hash(t1), hash(t2))
2499 self.assertEqual(hash(t1), hash(t3))
2500 self.assertEqual(hash(t2), hash(t3))
2501 self.assertEqual(t1, t2)
2502 self.assertEqual(t1, t3)
2503 self.assertEqual(t2, t3)
2504 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2505 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2506 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002507 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002508 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2509 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2510 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2511
2512 def test_combine(self):
2513 met = FixedOffset(60, "MET")
2514 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002515 tz = time(18, 45, 3, 1234, tzinfo=met)
2516 dt = datetime.combine(d, tz)
2517 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002518 tzinfo=met))
2519
2520 def test_extract(self):
2521 met = FixedOffset(60, "MET")
2522 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2523 self.assertEqual(dt.date(), date(2002, 3, 4))
2524 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002525 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002526
2527 def test_tz_aware_arithmetic(self):
2528 import random
2529
2530 now = self.theclass.now()
2531 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002532 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002533 nowaware = self.theclass.combine(now.date(), timeaware)
2534 self.failUnless(nowaware.tzinfo is tz55)
2535 self.assertEqual(nowaware.timetz(), timeaware)
2536
2537 # Can't mix aware and non-aware.
2538 self.assertRaises(TypeError, lambda: now - nowaware)
2539 self.assertRaises(TypeError, lambda: nowaware - now)
2540
Tim Peters0bf60bd2003-01-08 20:40:01 +00002541 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002542 self.assertRaises(TypeError, lambda: now + nowaware)
2543 self.assertRaises(TypeError, lambda: nowaware + now)
2544 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2545
2546 # Subtracting should yield 0.
2547 self.assertEqual(now - now, timedelta(0))
2548 self.assertEqual(nowaware - nowaware, timedelta(0))
2549
2550 # Adding a delta should preserve tzinfo.
2551 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2552 nowawareplus = nowaware + delta
2553 self.failUnless(nowaware.tzinfo is tz55)
2554 nowawareplus2 = delta + nowaware
2555 self.failUnless(nowawareplus2.tzinfo is tz55)
2556 self.assertEqual(nowawareplus, nowawareplus2)
2557
2558 # that - delta should be what we started with, and that - what we
2559 # started with should be delta.
2560 diff = nowawareplus - delta
2561 self.failUnless(diff.tzinfo is tz55)
2562 self.assertEqual(nowaware, diff)
2563 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2564 self.assertEqual(nowawareplus - nowaware, delta)
2565
2566 # Make up a random timezone.
2567 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002568 # Attach it to nowawareplus.
2569 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002570 self.failUnless(nowawareplus.tzinfo is tzr)
2571 # Make sure the difference takes the timezone adjustments into account.
2572 got = nowaware - nowawareplus
2573 # Expected: (nowaware base - nowaware offset) -
2574 # (nowawareplus base - nowawareplus offset) =
2575 # (nowaware base - nowawareplus base) +
2576 # (nowawareplus offset - nowaware offset) =
2577 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002578 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002579 self.assertEqual(got, expected)
2580
2581 # Try max possible difference.
2582 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2583 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2584 tzinfo=FixedOffset(-1439, "max"))
2585 maxdiff = max - min
2586 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2587 timedelta(minutes=2*1439))
2588
2589 def test_tzinfo_now(self):
2590 meth = self.theclass.now
2591 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2592 base = meth()
2593 # Try with and without naming the keyword.
2594 off42 = FixedOffset(42, "42")
2595 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002596 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002597 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002598 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002599 # Bad argument with and w/o naming the keyword.
2600 self.assertRaises(TypeError, meth, 16)
2601 self.assertRaises(TypeError, meth, tzinfo=16)
2602 # Bad keyword name.
2603 self.assertRaises(TypeError, meth, tinfo=off42)
2604 # Too many args.
2605 self.assertRaises(TypeError, meth, off42, off42)
2606
Tim Peters10cadce2003-01-23 19:58:02 +00002607 # We don't know which time zone we're in, and don't have a tzinfo
2608 # class to represent it, so seeing whether a tz argument actually
2609 # does a conversion is tricky.
2610 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2611 utc = FixedOffset(0, "utc", 0)
2612 for dummy in range(3):
2613 now = datetime.now(weirdtz)
2614 self.failUnless(now.tzinfo is weirdtz)
2615 utcnow = datetime.utcnow().replace(tzinfo=utc)
2616 now2 = utcnow.astimezone(weirdtz)
2617 if abs(now - now2) < timedelta(seconds=30):
2618 break
2619 # Else the code is broken, or more than 30 seconds passed between
2620 # calls; assuming the latter, just try again.
2621 else:
2622 # Three strikes and we're out.
2623 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2624
Tim Peters2a799bf2002-12-16 20:18:38 +00002625 def test_tzinfo_fromtimestamp(self):
2626 import time
2627 meth = self.theclass.fromtimestamp
2628 ts = time.time()
2629 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2630 base = meth(ts)
2631 # Try with and without naming the keyword.
2632 off42 = FixedOffset(42, "42")
2633 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002634 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002635 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002636 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002637 # Bad argument with and w/o naming the keyword.
2638 self.assertRaises(TypeError, meth, ts, 16)
2639 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2640 # Bad keyword name.
2641 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2642 # Too many args.
2643 self.assertRaises(TypeError, meth, ts, off42, off42)
2644 # Too few args.
2645 self.assertRaises(TypeError, meth)
2646
Tim Peters2a44a8d2003-01-23 20:53:10 +00002647 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002648 timestamp = 1000000000
2649 utcdatetime = datetime.utcfromtimestamp(timestamp)
2650 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2651 # But on some flavor of Mac, it's nowhere near that. So we can't have
2652 # any idea here what time that actually is, we can only test that
2653 # relative changes match.
2654 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2655 tz = FixedOffset(utcoffset, "tz", 0)
2656 expected = utcdatetime + utcoffset
2657 got = datetime.fromtimestamp(timestamp, tz)
2658 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002659
Tim Peters2a799bf2002-12-16 20:18:38 +00002660 def test_tzinfo_utcnow(self):
2661 meth = self.theclass.utcnow
2662 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2663 base = meth()
2664 # Try with and without naming the keyword; for whatever reason,
2665 # utcnow() doesn't accept a tzinfo argument.
2666 off42 = FixedOffset(42, "42")
2667 self.assertRaises(TypeError, meth, off42)
2668 self.assertRaises(TypeError, meth, tzinfo=off42)
2669
2670 def test_tzinfo_utcfromtimestamp(self):
2671 import time
2672 meth = self.theclass.utcfromtimestamp
2673 ts = time.time()
2674 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2675 base = meth(ts)
2676 # Try with and without naming the keyword; for whatever reason,
2677 # utcfromtimestamp() doesn't accept a tzinfo argument.
2678 off42 = FixedOffset(42, "42")
2679 self.assertRaises(TypeError, meth, ts, off42)
2680 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2681
2682 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002683 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002684 # DST flag.
2685 class DST(tzinfo):
2686 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002687 if isinstance(dstvalue, int):
2688 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002689 self.dstvalue = dstvalue
2690 def dst(self, dt):
2691 return self.dstvalue
2692
2693 cls = self.theclass
2694 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2695 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2696 t = d.timetuple()
2697 self.assertEqual(1, t.tm_year)
2698 self.assertEqual(1, t.tm_mon)
2699 self.assertEqual(1, t.tm_mday)
2700 self.assertEqual(10, t.tm_hour)
2701 self.assertEqual(20, t.tm_min)
2702 self.assertEqual(30, t.tm_sec)
2703 self.assertEqual(0, t.tm_wday)
2704 self.assertEqual(1, t.tm_yday)
2705 self.assertEqual(flag, t.tm_isdst)
2706
2707 # dst() returns wrong type.
2708 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2709
2710 # dst() at the edge.
2711 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2712 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2713
2714 # dst() out of range.
2715 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2716 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2717
2718 def test_utctimetuple(self):
2719 class DST(tzinfo):
2720 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002721 if isinstance(dstvalue, int):
2722 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002723 self.dstvalue = dstvalue
2724 def dst(self, dt):
2725 return self.dstvalue
2726
2727 cls = self.theclass
2728 # This can't work: DST didn't implement utcoffset.
2729 self.assertRaises(NotImplementedError,
2730 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2731
2732 class UOFS(DST):
2733 def __init__(self, uofs, dofs=None):
2734 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002735 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002736 def utcoffset(self, dt):
2737 return self.uofs
2738
2739 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2740 # in effect for a UTC time.
2741 for dstvalue in -33, 33, 0, None:
2742 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2743 t = d.utctimetuple()
2744 self.assertEqual(d.year, t.tm_year)
2745 self.assertEqual(d.month, t.tm_mon)
2746 self.assertEqual(d.day, t.tm_mday)
2747 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2748 self.assertEqual(13, t.tm_min)
2749 self.assertEqual(d.second, t.tm_sec)
2750 self.assertEqual(d.weekday(), t.tm_wday)
2751 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2752 t.tm_yday)
2753 self.assertEqual(0, t.tm_isdst)
2754
2755 # At the edges, UTC adjustment can normalize into years out-of-range
2756 # for a datetime object. Ensure that a correct timetuple is
2757 # created anyway.
2758 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2759 # That goes back 1 minute less than a full day.
2760 t = tiny.utctimetuple()
2761 self.assertEqual(t.tm_year, MINYEAR-1)
2762 self.assertEqual(t.tm_mon, 12)
2763 self.assertEqual(t.tm_mday, 31)
2764 self.assertEqual(t.tm_hour, 0)
2765 self.assertEqual(t.tm_min, 1)
2766 self.assertEqual(t.tm_sec, 37)
2767 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2768 self.assertEqual(t.tm_isdst, 0)
2769
2770 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2771 # That goes forward 1 minute less than a full day.
2772 t = huge.utctimetuple()
2773 self.assertEqual(t.tm_year, MAXYEAR+1)
2774 self.assertEqual(t.tm_mon, 1)
2775 self.assertEqual(t.tm_mday, 1)
2776 self.assertEqual(t.tm_hour, 23)
2777 self.assertEqual(t.tm_min, 58)
2778 self.assertEqual(t.tm_sec, 37)
2779 self.assertEqual(t.tm_yday, 1)
2780 self.assertEqual(t.tm_isdst, 0)
2781
2782 def test_tzinfo_isoformat(self):
2783 zero = FixedOffset(0, "+00:00")
2784 plus = FixedOffset(220, "+03:40")
2785 minus = FixedOffset(-231, "-03:51")
2786 unknown = FixedOffset(None, "")
2787
2788 cls = self.theclass
2789 datestr = '0001-02-03'
2790 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002791 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002792 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2793 timestr = '04:05:59' + (us and '.987001' or '')
2794 ofsstr = ofs is not None and d.tzname() or ''
2795 tailstr = timestr + ofsstr
2796 iso = d.isoformat()
2797 self.assertEqual(iso, datestr + 'T' + tailstr)
2798 self.assertEqual(iso, d.isoformat('T'))
2799 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2800 self.assertEqual(str(d), datestr + ' ' + tailstr)
2801
Tim Peters12bf3392002-12-24 05:41:27 +00002802 def test_replace(self):
2803 cls = self.theclass
2804 z100 = FixedOffset(100, "+100")
2805 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2806 args = [1, 2, 3, 4, 5, 6, 7, z100]
2807 base = cls(*args)
2808 self.assertEqual(base, base.replace())
2809
2810 i = 0
2811 for name, newval in (("year", 2),
2812 ("month", 3),
2813 ("day", 4),
2814 ("hour", 5),
2815 ("minute", 6),
2816 ("second", 7),
2817 ("microsecond", 8),
2818 ("tzinfo", zm200)):
2819 newargs = args[:]
2820 newargs[i] = newval
2821 expected = cls(*newargs)
2822 got = base.replace(**{name: newval})
2823 self.assertEqual(expected, got)
2824 i += 1
2825
2826 # Ensure we can get rid of a tzinfo.
2827 self.assertEqual(base.tzname(), "+100")
2828 base2 = base.replace(tzinfo=None)
2829 self.failUnless(base2.tzinfo is None)
2830 self.failUnless(base2.tzname() is None)
2831
2832 # Ensure we can add one.
2833 base3 = base2.replace(tzinfo=z100)
2834 self.assertEqual(base, base3)
2835 self.failUnless(base.tzinfo is base3.tzinfo)
2836
2837 # Out of bounds.
2838 base = cls(2000, 2, 29)
2839 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002840
Tim Peters80475bb2002-12-25 07:40:55 +00002841 def test_more_astimezone(self):
2842 # The inherited test_astimezone covered some trivial and error cases.
2843 fnone = FixedOffset(None, "None")
2844 f44m = FixedOffset(44, "44")
2845 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2846
Tim Peters10cadce2003-01-23 19:58:02 +00002847 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002848 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002849 # Replacing with degenerate tzinfo raises an exception.
2850 self.assertRaises(ValueError, dt.astimezone, fnone)
2851 # Ditto with None tz.
2852 self.assertRaises(TypeError, dt.astimezone, None)
2853 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002854 x = dt.astimezone(dt.tzinfo)
2855 self.failUnless(x.tzinfo is f44m)
2856 self.assertEqual(x.date(), dt.date())
2857 self.assertEqual(x.time(), dt.time())
2858
2859 # Replacing with different tzinfo does adjust.
2860 got = dt.astimezone(fm5h)
2861 self.failUnless(got.tzinfo is fm5h)
2862 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2863 expected = dt - dt.utcoffset() # in effect, convert to UTC
2864 expected += fm5h.utcoffset(dt) # and from there to local time
2865 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2866 self.assertEqual(got.date(), expected.date())
2867 self.assertEqual(got.time(), expected.time())
2868 self.assertEqual(got.timetz(), expected.timetz())
2869 self.failUnless(got.tzinfo is expected.tzinfo)
2870 self.assertEqual(got, expected)
2871
Tim Peters4c0db782002-12-26 05:01:19 +00002872 def test_aware_subtract(self):
2873 cls = self.theclass
2874
Tim Peters60c76e42002-12-27 00:41:11 +00002875 # Ensure that utcoffset() is ignored when the operands have the
2876 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002877 class OperandDependentOffset(tzinfo):
2878 def utcoffset(self, t):
2879 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002880 # d0 and d1 equal after adjustment
2881 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002882 else:
Tim Peters397301e2003-01-02 21:28:08 +00002883 # d2 off in the weeds
2884 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002885
2886 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2887 d0 = base.replace(minute=3)
2888 d1 = base.replace(minute=9)
2889 d2 = base.replace(minute=11)
2890 for x in d0, d1, d2:
2891 for y in d0, d1, d2:
2892 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002893 expected = timedelta(minutes=x.minute - y.minute)
2894 self.assertEqual(got, expected)
2895
2896 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2897 # ignored.
2898 base = cls(8, 9, 10, 11, 12, 13, 14)
2899 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2900 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2901 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2902 for x in d0, d1, d2:
2903 for y in d0, d1, d2:
2904 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002905 if (x is d0 or x is d1) and (y is d0 or y is d1):
2906 expected = timedelta(0)
2907 elif x is y is d2:
2908 expected = timedelta(0)
2909 elif x is d2:
2910 expected = timedelta(minutes=(11-59)-0)
2911 else:
2912 assert y is d2
2913 expected = timedelta(minutes=0-(11-59))
2914 self.assertEqual(got, expected)
2915
Tim Peters60c76e42002-12-27 00:41:11 +00002916 def test_mixed_compare(self):
2917 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002918 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002919 self.assertEqual(t1, t2)
2920 t2 = t2.replace(tzinfo=None)
2921 self.assertEqual(t1, t2)
2922 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2923 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002924 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2925 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002926
Tim Peters0bf60bd2003-01-08 20:40:01 +00002927 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002928 class Varies(tzinfo):
2929 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002930 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002931 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002932 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002933 return self.offset
2934
2935 v = Varies()
2936 t1 = t2.replace(tzinfo=v)
2937 t2 = t2.replace(tzinfo=v)
2938 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2939 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2940 self.assertEqual(t1, t2)
2941
2942 # But if they're not identical, it isn't ignored.
2943 t2 = t2.replace(tzinfo=Varies())
2944 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002945
Tim Petersa98924a2003-05-17 05:55:19 +00002946 def test_subclass_datetimetz(self):
2947
2948 class C(self.theclass):
2949 theAnswer = 42
2950
2951 def __new__(cls, *args, **kws):
2952 temp = kws.copy()
2953 extra = temp.pop('extra')
2954 result = self.theclass.__new__(cls, *args, **temp)
2955 result.extra = extra
2956 return result
2957
2958 def newmeth(self, start):
2959 return start + self.hour + self.year
2960
2961 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2962
2963 dt1 = self.theclass(*args)
2964 dt2 = C(*args, **{'extra': 7})
2965
2966 self.assertEqual(dt2.__class__, C)
2967 self.assertEqual(dt2.theAnswer, 42)
2968 self.assertEqual(dt2.extra, 7)
2969 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2970 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2971
Tim Peters621818b2002-12-29 23:44:49 +00002972# Pain to set up DST-aware tzinfo classes.
2973
2974def first_sunday_on_or_after(dt):
2975 days_to_go = 6 - dt.weekday()
2976 if days_to_go:
2977 dt += timedelta(days_to_go)
2978 return dt
2979
2980ZERO = timedelta(0)
2981HOUR = timedelta(hours=1)
2982DAY = timedelta(days=1)
2983# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2984DSTSTART = datetime(1, 4, 1, 2)
2985# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002986# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2987# being standard time on that day, there is no spelling in local time of
2988# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2989DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002990
2991class USTimeZone(tzinfo):
2992
2993 def __init__(self, hours, reprname, stdname, dstname):
2994 self.stdoffset = timedelta(hours=hours)
2995 self.reprname = reprname
2996 self.stdname = stdname
2997 self.dstname = dstname
2998
2999 def __repr__(self):
3000 return self.reprname
3001
3002 def tzname(self, dt):
3003 if self.dst(dt):
3004 return self.dstname
3005 else:
3006 return self.stdname
3007
3008 def utcoffset(self, dt):
3009 return self.stdoffset + self.dst(dt)
3010
3011 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00003012 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00003013 # An exception instead may be sensible here, in one or more of
3014 # the cases.
3015 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00003016 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00003017
3018 # Find first Sunday in April.
3019 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3020 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3021
3022 # Find last Sunday in October.
3023 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3024 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3025
Tim Peters621818b2002-12-29 23:44:49 +00003026 # Can't compare naive to aware objects, so strip the timezone from
3027 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00003028 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00003029 return HOUR
3030 else:
3031 return ZERO
3032
Tim Peters521fc152002-12-31 17:36:56 +00003033Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3034Central = USTimeZone(-6, "Central", "CST", "CDT")
3035Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3036Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00003037utc_real = FixedOffset(0, "UTC", 0)
3038# For better test coverage, we want another flavor of UTC that's west of
3039# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00003040utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00003041
3042class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00003043 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00003044 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00003045 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00003046
Tim Peters0bf60bd2003-01-08 20:40:01 +00003047 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00003048
Tim Peters521fc152002-12-31 17:36:56 +00003049 # Check a time that's inside DST.
3050 def checkinside(self, dt, tz, utc, dston, dstoff):
3051 self.assertEqual(dt.dst(), HOUR)
3052
3053 # Conversion to our own timezone is always an identity.
3054 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00003055
3056 asutc = dt.astimezone(utc)
3057 there_and_back = asutc.astimezone(tz)
3058
3059 # Conversion to UTC and back isn't always an identity here,
3060 # because there are redundant spellings (in local time) of
3061 # UTC time when DST begins: the clock jumps from 1:59:59
3062 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3063 # make sense then. The classes above treat 2:MM:SS as
3064 # daylight time then (it's "after 2am"), really an alias
3065 # for 1:MM:SS standard time. The latter form is what
3066 # conversion back from UTC produces.
3067 if dt.date() == dston.date() and dt.hour == 2:
3068 # We're in the redundant hour, and coming back from
3069 # UTC gives the 1:MM:SS standard-time spelling.
3070 self.assertEqual(there_and_back + HOUR, dt)
3071 # Although during was considered to be in daylight
3072 # time, there_and_back is not.
3073 self.assertEqual(there_and_back.dst(), ZERO)
3074 # They're the same times in UTC.
3075 self.assertEqual(there_and_back.astimezone(utc),
3076 dt.astimezone(utc))
3077 else:
3078 # We're not in the redundant hour.
3079 self.assertEqual(dt, there_and_back)
3080
Tim Peters327098a2003-01-20 22:54:38 +00003081 # Because we have a redundant spelling when DST begins, there is
3082 # (unforunately) an hour when DST ends that can't be spelled at all in
3083 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3084 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3085 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3086 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3087 # expressed in local time. Nevertheless, we want conversion back
3088 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003089 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003090 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003091 if dt.date() == dstoff.date() and dt.hour == 0:
3092 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003093 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003094 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3095 nexthour_utc += HOUR
3096 nexthour_tz = nexthour_utc.astimezone(tz)
3097 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003098 else:
Tim Peters327098a2003-01-20 22:54:38 +00003099 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003100
3101 # Check a time that's outside DST.
3102 def checkoutside(self, dt, tz, utc):
3103 self.assertEqual(dt.dst(), ZERO)
3104
3105 # Conversion to our own timezone is always an identity.
3106 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003107
3108 # Converting to UTC and back is an identity too.
3109 asutc = dt.astimezone(utc)
3110 there_and_back = asutc.astimezone(tz)
3111 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003112
Tim Peters1024bf82002-12-30 17:09:40 +00003113 def convert_between_tz_and_utc(self, tz, utc):
3114 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003115 # Because 1:MM on the day DST ends is taken as being standard time,
3116 # there is no spelling in tz for the last hour of daylight time.
3117 # For purposes of the test, the last hour of DST is 0:MM, which is
3118 # taken as being daylight time (and 1:MM is taken as being standard
3119 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003120 dstoff = self.dstoff.replace(tzinfo=tz)
3121 for delta in (timedelta(weeks=13),
3122 DAY,
3123 HOUR,
3124 timedelta(minutes=1),
3125 timedelta(microseconds=1)):
3126
Tim Peters521fc152002-12-31 17:36:56 +00003127 self.checkinside(dston, tz, utc, dston, dstoff)
3128 for during in dston + delta, dstoff - delta:
3129 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003130
Tim Peters521fc152002-12-31 17:36:56 +00003131 self.checkoutside(dstoff, tz, utc)
3132 for outside in dston - delta, dstoff + delta:
3133 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003134
Tim Peters621818b2002-12-29 23:44:49 +00003135 def test_easy(self):
3136 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003137 self.convert_between_tz_and_utc(Eastern, utc_real)
3138 self.convert_between_tz_and_utc(Pacific, utc_real)
3139 self.convert_between_tz_and_utc(Eastern, utc_fake)
3140 self.convert_between_tz_and_utc(Pacific, utc_fake)
3141 # The next is really dancing near the edge. It works because
3142 # Pacific and Eastern are far enough apart that their "problem
3143 # hours" don't overlap.
3144 self.convert_between_tz_and_utc(Eastern, Pacific)
3145 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003146 # OTOH, these fail! Don't enable them. The difficulty is that
3147 # the edge case tests assume that every hour is representable in
3148 # the "utc" class. This is always true for a fixed-offset tzinfo
3149 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3150 # For these adjacent DST-aware time zones, the range of time offsets
3151 # tested ends up creating hours in the one that aren't representable
3152 # in the other. For the same reason, we would see failures in the
3153 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3154 # offset deltas in convert_between_tz_and_utc().
3155 #
3156 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3157 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003158
Tim Petersf3615152003-01-01 21:51:37 +00003159 def test_tricky(self):
3160 # 22:00 on day before daylight starts.
3161 fourback = self.dston - timedelta(hours=4)
3162 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003163 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003164 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3165 # 2", we should get the 3 spelling.
3166 # If we plug 22:00 the day before into Eastern, it "looks like std
3167 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3168 # to 22:00 lands on 2:00, which makes no sense in local time (the
3169 # local clock jumps from 1 to 3). The point here is to make sure we
3170 # get the 3 spelling.
3171 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003172 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003173 self.assertEqual(expected, got)
3174
3175 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3176 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003177 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003178 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3179 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3180 # spelling.
3181 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003182 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003183 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003184
Tim Petersadf64202003-01-04 06:03:15 +00003185 # Now on the day DST ends, we want "repeat an hour" behavior.
3186 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3187 # EST 23:MM 0:MM 1:MM 2:MM
3188 # EDT 0:MM 1:MM 2:MM 3:MM
3189 # wall 0:MM 1:MM 1:MM 2:MM against these
3190 for utc in utc_real, utc_fake:
3191 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003192 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003193 # Convert that to UTC.
3194 first_std_hour -= tz.utcoffset(None)
3195 # Adjust for possibly fake UTC.
3196 asutc = first_std_hour + utc.utcoffset(None)
3197 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3198 # tz=Eastern.
3199 asutcbase = asutc.replace(tzinfo=utc)
3200 for tzhour in (0, 1, 1, 2):
3201 expectedbase = self.dstoff.replace(hour=tzhour)
3202 for minute in 0, 30, 59:
3203 expected = expectedbase.replace(minute=minute)
3204 asutc = asutcbase.replace(minute=minute)
3205 astz = asutc.astimezone(tz)
3206 self.assertEqual(astz.replace(tzinfo=None), expected)
3207 asutcbase += HOUR
3208
3209
Tim Peters710fb152003-01-02 19:35:54 +00003210 def test_bogus_dst(self):
3211 class ok(tzinfo):
3212 def utcoffset(self, dt): return HOUR
3213 def dst(self, dt): return HOUR
3214
3215 now = self.theclass.now().replace(tzinfo=utc_real)
3216 # Doesn't blow up.
3217 now.astimezone(ok())
3218
3219 # Does blow up.
3220 class notok(ok):
3221 def dst(self, dt): return None
3222 self.assertRaises(ValueError, now.astimezone, notok())
3223
Tim Peters52dcce22003-01-23 16:36:11 +00003224 def test_fromutc(self):
3225 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3226 now = datetime.utcnow().replace(tzinfo=utc_real)
3227 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3228 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3229 enow = Eastern.fromutc(now) # doesn't blow up
3230 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3231 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3232 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3233
3234 # Always converts UTC to standard time.
3235 class FauxUSTimeZone(USTimeZone):
3236 def fromutc(self, dt):
3237 return dt + self.stdoffset
3238 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3239
3240 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3241 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3242 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3243
3244 # Check around DST start.
3245 start = self.dston.replace(hour=4, tzinfo=Eastern)
3246 fstart = start.replace(tzinfo=FEastern)
3247 for wall in 23, 0, 1, 3, 4, 5:
3248 expected = start.replace(hour=wall)
3249 if wall == 23:
3250 expected -= timedelta(days=1)
3251 got = Eastern.fromutc(start)
3252 self.assertEqual(expected, got)
3253
3254 expected = fstart + FEastern.stdoffset
3255 got = FEastern.fromutc(fstart)
3256 self.assertEqual(expected, got)
3257
3258 # Ensure astimezone() calls fromutc() too.
3259 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3260 self.assertEqual(expected, got)
3261
3262 start += HOUR
3263 fstart += HOUR
3264
3265 # Check around DST end.
3266 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3267 fstart = start.replace(tzinfo=FEastern)
3268 for wall in 0, 1, 1, 2, 3, 4:
3269 expected = start.replace(hour=wall)
3270 got = Eastern.fromutc(start)
3271 self.assertEqual(expected, got)
3272
3273 expected = fstart + FEastern.stdoffset
3274 got = FEastern.fromutc(fstart)
3275 self.assertEqual(expected, got)
3276
3277 # Ensure astimezone() calls fromutc() too.
3278 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3279 self.assertEqual(expected, got)
3280
3281 start += HOUR
3282 fstart += HOUR
3283
Tim Peters710fb152003-01-02 19:35:54 +00003284
Tim Peters528ca532004-09-16 01:30:50 +00003285#############################################################################
3286# oddballs
3287
3288class Oddballs(unittest.TestCase):
3289
3290 def test_bug_1028306(self):
3291 # Trying to compare a date to a datetime should act like a mixed-
3292 # type comparison, despite that datetime is a subclass of date.
3293 as_date = date.today()
3294 as_datetime = datetime.combine(as_date, time())
3295 self.assert_(as_date != as_datetime)
3296 self.assert_(as_datetime != as_date)
3297 self.assert_(not as_date == as_datetime)
3298 self.assert_(not as_datetime == as_date)
3299 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3300 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3301 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3302 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3303 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3304 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3305 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3306 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3307
3308 # Neverthelss, comparison should work with the base-class (date)
3309 # projection if use of a date method is forced.
3310 self.assert_(as_date.__eq__(as_datetime))
3311 different_day = (as_date.day + 1) % 20 + 1
3312 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3313 different_day)))
3314
3315 # And date should compare with other subclasses of date. If a
3316 # subclass wants to stop this, it's up to the subclass to do so.
3317 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3318 self.assertEqual(as_date, date_sc)
3319 self.assertEqual(date_sc, as_date)
3320
3321 # Ditto for datetimes.
3322 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3323 as_date.day, 0, 0, 0)
3324 self.assertEqual(as_datetime, datetime_sc)
3325 self.assertEqual(datetime_sc, as_datetime)
3326
Tim Peters2a799bf2002-12-16 20:18:38 +00003327def test_main():
Collin Winterbec754c2007-04-25 17:37:35 +00003328 test_support.run_unittest(__name__)
Tim Peters2a799bf2002-12-16 20:18:38 +00003329
3330if __name__ == "__main__":
3331 test_main()