blob: a46e3cacad5a1c57530bacbb0b95e9501c9f2ece [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
6import sys
7import unittest
8
9from test import test_support
10
11from datetime import MINYEAR, MAXYEAR
12from datetime import timedelta
13from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000014from datetime import time
15from datetime import date, datetime
16
17
18# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are
19# XXX instances of new-style classes (like date and time) that both
20# XXX define __cmp__, and x is compared to y, and one of the __cmp__
21# XXX implementations raises an exception, the exception can get dropped
22# XXX on the floor when it occurs, and pop up again at some "random" time
23# XXX later (it depends on when the next opcode gets executed that
24# XXX bothers to check). There isn't a workaround for this, so instead
25# XXX we disable the parts of the tests that trigger it unless
26# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid
27# XXX provoking it here.
28# XXX Guido checked into a fix that will go into 2.2.3. The bug was
29# XXX already fixed in 2.3 CVS via a different means.
30CMP_BUG_FIXED = sys.version_info >= (2, 2, 3)
31
Tim Peters2a799bf2002-12-16 20:18:38 +000032
33#############################################################################
34# module tests
35
36class TestModule(unittest.TestCase):
37
38 def test_constants(self):
39 import datetime
40 self.assertEqual(datetime.MINYEAR, 1)
41 self.assertEqual(datetime.MAXYEAR, 9999)
42
43#############################################################################
44# tzinfo tests
45
46class FixedOffset(tzinfo):
47 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000048 if isinstance(offset, int):
49 offset = timedelta(minutes=offset)
50 if isinstance(dstoffset, int):
51 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000052 self.__offset = offset
53 self.__name = name
54 self.__dstoffset = dstoffset
55 def __repr__(self):
56 return self.__name.lower()
57 def utcoffset(self, dt):
58 return self.__offset
59 def tzname(self, dt):
60 return self.__name
61 def dst(self, dt):
62 return self.__dstoffset
63
Tim Petersfb8472c2002-12-21 05:04:42 +000064class PicklableFixedOffset(FixedOffset):
65 def __init__(self, offset=None, name=None, dstoffset=None):
66 FixedOffset.__init__(self, offset, name, dstoffset)
67
Tim Peters2a799bf2002-12-16 20:18:38 +000068class TestTZInfo(unittest.TestCase):
69
70 def test_non_abstractness(self):
71 # In order to allow subclasses to get pickled, the C implementation
72 # wasn't able to get away with having __init__ raise
73 # NotImplementedError.
74 useless = tzinfo()
75 dt = datetime.max
76 self.assertRaises(NotImplementedError, useless.tzname, dt)
77 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
78 self.assertRaises(NotImplementedError, useless.dst, dt)
79
80 def test_subclass_must_override(self):
81 class NotEnough(tzinfo):
82 def __init__(self, offset, name):
83 self.__offset = offset
84 self.__name = name
85 self.failUnless(issubclass(NotEnough, tzinfo))
86 ne = NotEnough(3, "NotByALongShot")
87 self.failUnless(isinstance(ne, tzinfo))
88
89 dt = datetime.now()
90 self.assertRaises(NotImplementedError, ne.tzname, dt)
91 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
92 self.assertRaises(NotImplementedError, ne.dst, dt)
93
94 def test_normal(self):
95 fo = FixedOffset(3, "Three")
96 self.failUnless(isinstance(fo, tzinfo))
97 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000098 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000099 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000100 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000101
102 def test_pickling_base(self):
103 import pickle, cPickle
104
105 # There's no point to pickling tzinfo objects on their own (they
106 # carry no data), but they need to be picklable anyway else
107 # concrete subclasses can't be pickled.
108 orig = tzinfo.__new__(tzinfo)
109 self.failUnless(type(orig) is tzinfo)
110 for pickler in pickle, cPickle:
111 for binary in 0, 1:
112 green = pickler.dumps(orig, binary)
113 derived = pickler.loads(green)
114 self.failUnless(type(derived) is tzinfo)
115
116 def test_pickling_subclass(self):
117 import pickle, cPickle
118
119 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000120 offset = timedelta(minutes=-300)
121 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000122 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000123 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000124 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000125 self.assertEqual(orig.tzname(None), 'cookie')
126 for pickler in pickle, cPickle:
127 for binary in 0, 1:
128 green = pickler.dumps(orig, binary)
129 derived = pickler.loads(green)
130 self.failUnless(isinstance(derived, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000131 self.failUnless(type(derived) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000132 self.assertEqual(derived.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000133 self.assertEqual(derived.tzname(None), 'cookie')
134
135#############################################################################
136# timedelta tests
137
138class TestTimeDelta(unittest.TestCase):
139
140 def test_constructor(self):
141 eq = self.assertEqual
142 td = timedelta
143
144 # Check keyword args to constructor
145 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
146 milliseconds=0, microseconds=0))
147 eq(td(1), td(days=1))
148 eq(td(0, 1), td(seconds=1))
149 eq(td(0, 0, 1), td(microseconds=1))
150 eq(td(weeks=1), td(days=7))
151 eq(td(days=1), td(hours=24))
152 eq(td(hours=1), td(minutes=60))
153 eq(td(minutes=1), td(seconds=60))
154 eq(td(seconds=1), td(milliseconds=1000))
155 eq(td(milliseconds=1), td(microseconds=1000))
156
157 # Check float args to constructor
158 eq(td(weeks=1.0/7), td(days=1))
159 eq(td(days=1.0/24), td(hours=1))
160 eq(td(hours=1.0/60), td(minutes=1))
161 eq(td(minutes=1.0/60), td(seconds=1))
162 eq(td(seconds=0.001), td(milliseconds=1))
163 eq(td(milliseconds=0.001), td(microseconds=1))
164
165 def test_computations(self):
166 eq = self.assertEqual
167 td = timedelta
168
169 a = td(7) # One week
170 b = td(0, 60) # One minute
171 c = td(0, 0, 1000) # One millisecond
172 eq(a+b+c, td(7, 60, 1000))
173 eq(a-b, td(6, 24*3600 - 60))
174 eq(-a, td(-7))
175 eq(+a, td(7))
176 eq(-b, td(-1, 24*3600 - 60))
177 eq(-c, td(-1, 24*3600 - 1, 999000))
178 eq(abs(a), a)
179 eq(abs(-a), a)
180 eq(td(6, 24*3600), a)
181 eq(td(0, 0, 60*1000000), b)
182 eq(a*10, td(70))
183 eq(a*10, 10*a)
184 eq(a*10L, 10*a)
185 eq(b*10, td(0, 600))
186 eq(10*b, td(0, 600))
187 eq(b*10L, td(0, 600))
188 eq(c*10, td(0, 0, 10000))
189 eq(10*c, td(0, 0, 10000))
190 eq(c*10L, td(0, 0, 10000))
191 eq(a*-1, -a)
192 eq(b*-2, -b-b)
193 eq(c*-2, -c+-c)
194 eq(b*(60*24), (b*60)*24)
195 eq(b*(60*24), (60*b)*24)
196 eq(c*1000, td(0, 1))
197 eq(1000*c, td(0, 1))
198 eq(a//7, td(1))
199 eq(b//10, td(0, 6))
200 eq(c//1000, td(0, 0, 1))
201 eq(a//10, td(0, 7*24*360))
202 eq(a//3600000, td(0, 0, 7*24*1000))
203
204 def test_disallowed_computations(self):
205 a = timedelta(42)
206
207 # Add/sub ints, longs, floats should be illegal
208 for i in 1, 1L, 1.0:
209 self.assertRaises(TypeError, lambda: a+i)
210 self.assertRaises(TypeError, lambda: a-i)
211 self.assertRaises(TypeError, lambda: i+a)
212 self.assertRaises(TypeError, lambda: i-a)
213
214 # Mul/div by float isn't supported.
215 x = 2.3
216 self.assertRaises(TypeError, lambda: a*x)
217 self.assertRaises(TypeError, lambda: x*a)
218 self.assertRaises(TypeError, lambda: a/x)
219 self.assertRaises(TypeError, lambda: x/a)
220 self.assertRaises(TypeError, lambda: a // x)
221 self.assertRaises(TypeError, lambda: x // a)
222
223 # Divison of int by timedelta doesn't make sense.
224 # Division by zero doesn't make sense.
225 for zero in 0, 0L:
226 self.assertRaises(TypeError, lambda: zero // a)
227 self.assertRaises(ZeroDivisionError, lambda: a // zero)
228
229 def test_basic_attributes(self):
230 days, seconds, us = 1, 7, 31
231 td = timedelta(days, seconds, us)
232 self.assertEqual(td.days, days)
233 self.assertEqual(td.seconds, seconds)
234 self.assertEqual(td.microseconds, us)
235
236 def test_carries(self):
237 t1 = timedelta(days=100,
238 weeks=-7,
239 hours=-24*(100-49),
240 minutes=-3,
241 seconds=12,
242 microseconds=(3*60 - 12) * 1e6 + 1)
243 t2 = timedelta(microseconds=1)
244 self.assertEqual(t1, t2)
245
246 def test_hash_equality(self):
247 t1 = timedelta(days=100,
248 weeks=-7,
249 hours=-24*(100-49),
250 minutes=-3,
251 seconds=12,
252 microseconds=(3*60 - 12) * 1000000)
253 t2 = timedelta()
254 self.assertEqual(hash(t1), hash(t2))
255
256 t1 += timedelta(weeks=7)
257 t2 += timedelta(days=7*7)
258 self.assertEqual(t1, t2)
259 self.assertEqual(hash(t1), hash(t2))
260
261 d = {t1: 1}
262 d[t2] = 2
263 self.assertEqual(len(d), 1)
264 self.assertEqual(d[t1], 2)
265
266 def test_pickling(self):
267 import pickle, cPickle
268 args = 12, 34, 56
269 orig = timedelta(*args)
270 state = orig.__getstate__()
271 self.assertEqual(args, state)
272 derived = timedelta()
273 derived.__setstate__(state)
274 self.assertEqual(orig, derived)
275 for pickler in pickle, cPickle:
276 for binary in 0, 1:
277 green = pickler.dumps(orig, binary)
278 derived = pickler.loads(green)
279 self.assertEqual(orig, derived)
280
281 def test_compare(self):
282 t1 = timedelta(2, 3, 4)
283 t2 = timedelta(2, 3, 4)
284 self.failUnless(t1 == t2)
285 self.failUnless(t1 <= t2)
286 self.failUnless(t1 >= t2)
287 self.failUnless(not t1 != t2)
288 self.failUnless(not t1 < t2)
289 self.failUnless(not t1 > t2)
290 self.assertEqual(cmp(t1, t2), 0)
291 self.assertEqual(cmp(t2, t1), 0)
292
293 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
294 t2 = timedelta(*args) # this is larger than t1
295 self.failUnless(t1 < t2)
296 self.failUnless(t2 > t1)
297 self.failUnless(t1 <= t2)
298 self.failUnless(t2 >= t1)
299 self.failUnless(t1 != t2)
300 self.failUnless(t2 != t1)
301 self.failUnless(not t1 == t2)
302 self.failUnless(not t2 == t1)
303 self.failUnless(not t1 > t2)
304 self.failUnless(not t2 < t1)
305 self.failUnless(not t1 >= t2)
306 self.failUnless(not t2 <= t1)
307 self.assertEqual(cmp(t1, t2), -1)
308 self.assertEqual(cmp(t2, t1), 1)
309
310 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
311 self.assertRaises(TypeError, lambda: t1 == badarg)
312 self.assertRaises(TypeError, lambda: t1 != badarg)
313 self.assertRaises(TypeError, lambda: t1 <= badarg)
314 self.assertRaises(TypeError, lambda: t1 < badarg)
315 self.assertRaises(TypeError, lambda: t1 > badarg)
316 self.assertRaises(TypeError, lambda: t1 >= badarg)
317 self.assertRaises(TypeError, lambda: badarg == t1)
318 self.assertRaises(TypeError, lambda: badarg != t1)
319 self.assertRaises(TypeError, lambda: badarg <= t1)
320 self.assertRaises(TypeError, lambda: badarg < t1)
321 self.assertRaises(TypeError, lambda: badarg > t1)
322 self.assertRaises(TypeError, lambda: badarg >= t1)
323
324 def test_str(self):
325 td = timedelta
326 eq = self.assertEqual
327
328 eq(str(td(1)), "1 day, 0:00:00")
329 eq(str(td(-1)), "-1 day, 0:00:00")
330 eq(str(td(2)), "2 days, 0:00:00")
331 eq(str(td(-2)), "-2 days, 0:00:00")
332
333 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
334 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
335 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
336 "-210 days, 23:12:34")
337
338 eq(str(td(milliseconds=1)), "0:00:00.001000")
339 eq(str(td(microseconds=3)), "0:00:00.000003")
340
341 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
342 microseconds=999999)),
343 "999999999 days, 23:59:59.999999")
344
345 def test_roundtrip(self):
346 for td in (timedelta(days=999999999, hours=23, minutes=59,
347 seconds=59, microseconds=999999),
348 timedelta(days=-999999999),
349 timedelta(days=1, seconds=2, microseconds=3)):
350
351 # Verify td -> string -> td identity.
352 s = repr(td)
353 self.failUnless(s.startswith('datetime.'))
354 s = s[9:]
355 td2 = eval(s)
356 self.assertEqual(td, td2)
357
358 # Verify identity via reconstructing from pieces.
359 td2 = timedelta(td.days, td.seconds, td.microseconds)
360 self.assertEqual(td, td2)
361
362 def test_resolution_info(self):
363 self.assert_(isinstance(timedelta.min, timedelta))
364 self.assert_(isinstance(timedelta.max, timedelta))
365 self.assert_(isinstance(timedelta.resolution, timedelta))
366 self.assert_(timedelta.max > timedelta.min)
367 self.assertEqual(timedelta.min, timedelta(-999999999))
368 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
369 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
370
371 def test_overflow(self):
372 tiny = timedelta.resolution
373
374 td = timedelta.min + tiny
375 td -= tiny # no problem
376 self.assertRaises(OverflowError, td.__sub__, tiny)
377 self.assertRaises(OverflowError, td.__add__, -tiny)
378
379 td = timedelta.max - tiny
380 td += tiny # no problem
381 self.assertRaises(OverflowError, td.__add__, tiny)
382 self.assertRaises(OverflowError, td.__sub__, -tiny)
383
384 self.assertRaises(OverflowError, lambda: -timedelta.max)
385
386 def test_microsecond_rounding(self):
387 td = timedelta
388 eq = self.assertEqual
389
390 # Single-field rounding.
391 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
392 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
393 eq(td(milliseconds=0.6/1000), td(microseconds=1))
394 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
395
396 # Rounding due to contributions from more than one field.
397 us_per_hour = 3600e6
398 us_per_day = us_per_hour * 24
399 eq(td(days=.4/us_per_day), td(0))
400 eq(td(hours=.2/us_per_hour), td(0))
401 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
402
403 eq(td(days=-.4/us_per_day), td(0))
404 eq(td(hours=-.2/us_per_hour), td(0))
405 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
406
407 def test_massive_normalization(self):
408 td = timedelta(microseconds=-1)
409 self.assertEqual((td.days, td.seconds, td.microseconds),
410 (-1, 24*3600-1, 999999))
411
412 def test_bool(self):
413 self.failUnless(timedelta(1))
414 self.failUnless(timedelta(0, 1))
415 self.failUnless(timedelta(0, 0, 1))
416 self.failUnless(timedelta(microseconds=1))
417 self.failUnless(not timedelta(0))
418
419#############################################################################
420# date tests
421
422class TestDateOnly(unittest.TestCase):
423 # Tests here won't pass if also run on datetime objects, so don't
424 # subclass this to test datetimes too.
425
426 def test_delta_non_days_ignored(self):
427 dt = date(2000, 1, 2)
428 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
429 microseconds=5)
430 days = timedelta(delta.days)
431 self.assertEqual(days, timedelta(1))
432
433 dt2 = dt + delta
434 self.assertEqual(dt2, dt + days)
435
436 dt2 = delta + dt
437 self.assertEqual(dt2, dt + days)
438
439 dt2 = dt - delta
440 self.assertEqual(dt2, dt - days)
441
442 delta = -delta
443 days = timedelta(delta.days)
444 self.assertEqual(days, timedelta(-2))
445
446 dt2 = dt + delta
447 self.assertEqual(dt2, dt + days)
448
449 dt2 = delta + dt
450 self.assertEqual(dt2, dt + days)
451
452 dt2 = dt - delta
453 self.assertEqual(dt2, dt - days)
454
455class TestDate(unittest.TestCase):
456 # Tests here should pass for both dates and datetimes, except for a
457 # few tests that TestDateTime overrides.
458
459 theclass = date
460
461 def test_basic_attributes(self):
462 dt = self.theclass(2002, 3, 1)
463 self.assertEqual(dt.year, 2002)
464 self.assertEqual(dt.month, 3)
465 self.assertEqual(dt.day, 1)
466
467 def test_roundtrip(self):
468 for dt in (self.theclass(1, 2, 3),
469 self.theclass.today()):
470 # Verify dt -> string -> date identity.
471 s = repr(dt)
472 self.failUnless(s.startswith('datetime.'))
473 s = s[9:]
474 dt2 = eval(s)
475 self.assertEqual(dt, dt2)
476
477 # Verify identity via reconstructing from pieces.
478 dt2 = self.theclass(dt.year, dt.month, dt.day)
479 self.assertEqual(dt, dt2)
480
481 def test_ordinal_conversions(self):
482 # Check some fixed values.
483 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
484 (1, 12, 31, 365),
485 (2, 1, 1, 366),
486 # first example from "Calendrical Calculations"
487 (1945, 11, 12, 710347)]:
488 d = self.theclass(y, m, d)
489 self.assertEqual(n, d.toordinal())
490 fromord = self.theclass.fromordinal(n)
491 self.assertEqual(d, fromord)
492 if hasattr(fromord, "hour"):
493 # if we're checking something fancier than a date, verify
494 # the extra fields have been zeroed out
495 self.assertEqual(fromord.hour, 0)
496 self.assertEqual(fromord.minute, 0)
497 self.assertEqual(fromord.second, 0)
498 self.assertEqual(fromord.microsecond, 0)
499
Tim Peters0bf60bd2003-01-08 20:40:01 +0000500 # Check first and last days of year spottily across the whole
501 # range of years supported.
502 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000503 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
504 d = self.theclass(year, 1, 1)
505 n = d.toordinal()
506 d2 = self.theclass.fromordinal(n)
507 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000508 # Verify that moving back a day gets to the end of year-1.
509 if year > 1:
510 d = self.theclass.fromordinal(n-1)
511 d2 = self.theclass(year-1, 12, 31)
512 self.assertEqual(d, d2)
513 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000514
515 # Test every day in a leap-year and a non-leap year.
516 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
517 for year, isleap in (2000, True), (2002, False):
518 n = self.theclass(year, 1, 1).toordinal()
519 for month, maxday in zip(range(1, 13), dim):
520 if month == 2 and isleap:
521 maxday += 1
522 for day in range(1, maxday+1):
523 d = self.theclass(year, month, day)
524 self.assertEqual(d.toordinal(), n)
525 self.assertEqual(d, self.theclass.fromordinal(n))
526 n += 1
527
528 def test_extreme_ordinals(self):
529 a = self.theclass.min
530 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
531 aord = a.toordinal()
532 b = a.fromordinal(aord)
533 self.assertEqual(a, b)
534
535 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
536
537 b = a + timedelta(days=1)
538 self.assertEqual(b.toordinal(), aord + 1)
539 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
540
541 a = self.theclass.max
542 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
543 aord = a.toordinal()
544 b = a.fromordinal(aord)
545 self.assertEqual(a, b)
546
547 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
548
549 b = a - timedelta(days=1)
550 self.assertEqual(b.toordinal(), aord - 1)
551 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
552
553 def test_bad_constructor_arguments(self):
554 # bad years
555 self.theclass(MINYEAR, 1, 1) # no exception
556 self.theclass(MAXYEAR, 1, 1) # no exception
557 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
558 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
559 # bad months
560 self.theclass(2000, 1, 1) # no exception
561 self.theclass(2000, 12, 1) # no exception
562 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
563 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
564 # bad days
565 self.theclass(2000, 2, 29) # no exception
566 self.theclass(2004, 2, 29) # no exception
567 self.theclass(2400, 2, 29) # no exception
568 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
569 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
570 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
571 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
572 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
573 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
574
575 def test_hash_equality(self):
576 d = self.theclass(2000, 12, 31)
577 # same thing
578 e = self.theclass(2000, 12, 31)
579 self.assertEqual(d, e)
580 self.assertEqual(hash(d), hash(e))
581
582 dic = {d: 1}
583 dic[e] = 2
584 self.assertEqual(len(dic), 1)
585 self.assertEqual(dic[d], 2)
586 self.assertEqual(dic[e], 2)
587
588 d = self.theclass(2001, 1, 1)
589 # same thing
590 e = self.theclass(2001, 1, 1)
591 self.assertEqual(d, e)
592 self.assertEqual(hash(d), hash(e))
593
594 dic = {d: 1}
595 dic[e] = 2
596 self.assertEqual(len(dic), 1)
597 self.assertEqual(dic[d], 2)
598 self.assertEqual(dic[e], 2)
599
600 def test_computations(self):
601 a = self.theclass(2002, 1, 31)
602 b = self.theclass(1956, 1, 31)
603
604 diff = a-b
605 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
606 self.assertEqual(diff.seconds, 0)
607 self.assertEqual(diff.microseconds, 0)
608
609 day = timedelta(1)
610 week = timedelta(7)
611 a = self.theclass(2002, 3, 2)
612 self.assertEqual(a + day, self.theclass(2002, 3, 3))
613 self.assertEqual(day + a, self.theclass(2002, 3, 3))
614 self.assertEqual(a - day, self.theclass(2002, 3, 1))
615 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
616 self.assertEqual(a + week, self.theclass(2002, 3, 9))
617 self.assertEqual(a - week, self.theclass(2002, 2, 23))
618 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
619 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
620 self.assertEqual((a + week) - a, week)
621 self.assertEqual((a + day) - a, day)
622 self.assertEqual((a - week) - a, -week)
623 self.assertEqual((a - day) - a, -day)
624 self.assertEqual(a - (a + week), -week)
625 self.assertEqual(a - (a + day), -day)
626 self.assertEqual(a - (a - week), week)
627 self.assertEqual(a - (a - day), day)
628
629 # Add/sub ints, longs, floats should be illegal
630 for i in 1, 1L, 1.0:
631 self.assertRaises(TypeError, lambda: a+i)
632 self.assertRaises(TypeError, lambda: a-i)
633 self.assertRaises(TypeError, lambda: i+a)
634 self.assertRaises(TypeError, lambda: i-a)
635
636 # delta - date is senseless.
637 self.assertRaises(TypeError, lambda: day - a)
638 # mixing date and (delta or date) via * or // is senseless
639 self.assertRaises(TypeError, lambda: day * a)
640 self.assertRaises(TypeError, lambda: a * day)
641 self.assertRaises(TypeError, lambda: day // a)
642 self.assertRaises(TypeError, lambda: a // day)
643 self.assertRaises(TypeError, lambda: a * a)
644 self.assertRaises(TypeError, lambda: a // a)
645 # date + date is senseless
646 self.assertRaises(TypeError, lambda: a + a)
647
648 def test_overflow(self):
649 tiny = self.theclass.resolution
650
651 dt = self.theclass.min + tiny
652 dt -= tiny # no problem
653 self.assertRaises(OverflowError, dt.__sub__, tiny)
654 self.assertRaises(OverflowError, dt.__add__, -tiny)
655
656 dt = self.theclass.max - tiny
657 dt += tiny # no problem
658 self.assertRaises(OverflowError, dt.__add__, tiny)
659 self.assertRaises(OverflowError, dt.__sub__, -tiny)
660
661 def test_fromtimestamp(self):
662 import time
663
664 # Try an arbitrary fixed value.
665 year, month, day = 1999, 9, 19
666 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
667 d = self.theclass.fromtimestamp(ts)
668 self.assertEqual(d.year, year)
669 self.assertEqual(d.month, month)
670 self.assertEqual(d.day, day)
671
672 def test_today(self):
673 import time
674
675 # We claim that today() is like fromtimestamp(time.time()), so
676 # prove it.
677 for dummy in range(3):
678 today = self.theclass.today()
679 ts = time.time()
680 todayagain = self.theclass.fromtimestamp(ts)
681 if today == todayagain:
682 break
683 # There are several legit reasons that could fail:
684 # 1. It recently became midnight, between the today() and the
685 # time() calls.
686 # 2. The platform time() has such fine resolution that we'll
687 # never get the same value twice.
688 # 3. The platform time() has poor resolution, and we just
689 # happened to call today() right before a resolution quantum
690 # boundary.
691 # 4. The system clock got fiddled between calls.
692 # In any case, wait a little while and try again.
693 time.sleep(0.1)
694
695 # It worked or it didn't. If it didn't, assume it's reason #2, and
696 # let the test pass if they're within half a second of each other.
697 self.failUnless(today == todayagain or
698 abs(todayagain - today) < timedelta(seconds=0.5))
699
700 def test_weekday(self):
701 for i in range(7):
702 # March 4, 2002 is a Monday
703 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
704 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
705 # January 2, 1956 is a Monday
706 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
707 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
708
709 def test_isocalendar(self):
710 # Check examples from
711 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
712 for i in range(7):
713 d = self.theclass(2003, 12, 22+i)
714 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
715 d = self.theclass(2003, 12, 29) + timedelta(i)
716 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
717 d = self.theclass(2004, 1, 5+i)
718 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
719 d = self.theclass(2009, 12, 21+i)
720 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
721 d = self.theclass(2009, 12, 28) + timedelta(i)
722 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
723 d = self.theclass(2010, 1, 4+i)
724 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
725
726 def test_iso_long_years(self):
727 # Calculate long ISO years and compare to table from
728 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
729 ISO_LONG_YEARS_TABLE = """
730 4 32 60 88
731 9 37 65 93
732 15 43 71 99
733 20 48 76
734 26 54 82
735
736 105 133 161 189
737 111 139 167 195
738 116 144 172
739 122 150 178
740 128 156 184
741
742 201 229 257 285
743 207 235 263 291
744 212 240 268 296
745 218 246 274
746 224 252 280
747
748 303 331 359 387
749 308 336 364 392
750 314 342 370 398
751 320 348 376
752 325 353 381
753 """
754 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
755 iso_long_years.sort()
756 L = []
757 for i in range(400):
758 d = self.theclass(2000+i, 12, 31)
759 d1 = self.theclass(1600+i, 12, 31)
760 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
761 if d.isocalendar()[1] == 53:
762 L.append(i)
763 self.assertEqual(L, iso_long_years)
764
765 def test_isoformat(self):
766 t = self.theclass(2, 3, 2)
767 self.assertEqual(t.isoformat(), "0002-03-02")
768
769 def test_ctime(self):
770 t = self.theclass(2002, 3, 2)
771 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
772
773 def test_strftime(self):
774 t = self.theclass(2005, 3, 2)
775 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
776
777 self.assertRaises(TypeError, t.strftime) # needs an arg
778 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
779 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
780
781 # A naive object replaces %z and %Z w/ empty strings.
782 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
783
784 def test_resolution_info(self):
785 self.assert_(isinstance(self.theclass.min, self.theclass))
786 self.assert_(isinstance(self.theclass.max, self.theclass))
787 self.assert_(isinstance(self.theclass.resolution, timedelta))
788 self.assert_(self.theclass.max > self.theclass.min)
789
790 def test_extreme_timedelta(self):
791 big = self.theclass.max - self.theclass.min
792 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
793 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
794 # n == 315537897599999999 ~= 2**58.13
795 justasbig = timedelta(0, 0, n)
796 self.assertEqual(big, justasbig)
797 self.assertEqual(self.theclass.min + big, self.theclass.max)
798 self.assertEqual(self.theclass.max - big, self.theclass.min)
799
800 def test_timetuple(self):
801 for i in range(7):
802 # January 2, 1956 is a Monday (0)
803 d = self.theclass(1956, 1, 2+i)
804 t = d.timetuple()
805 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
806 # February 1, 1956 is a Wednesday (2)
807 d = self.theclass(1956, 2, 1+i)
808 t = d.timetuple()
809 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
810 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
811 # of the year.
812 d = self.theclass(1956, 3, 1+i)
813 t = d.timetuple()
814 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
815 self.assertEqual(t.tm_year, 1956)
816 self.assertEqual(t.tm_mon, 3)
817 self.assertEqual(t.tm_mday, 1+i)
818 self.assertEqual(t.tm_hour, 0)
819 self.assertEqual(t.tm_min, 0)
820 self.assertEqual(t.tm_sec, 0)
821 self.assertEqual(t.tm_wday, (3+i)%7)
822 self.assertEqual(t.tm_yday, 61+i)
823 self.assertEqual(t.tm_isdst, -1)
824
825 def test_pickling(self):
826 import pickle, cPickle
827 args = 6, 7, 23
828 orig = self.theclass(*args)
829 state = orig.__getstate__()
830 self.assertEqual(state, '\x00\x06\x07\x17')
831 derived = self.theclass(1, 1, 1)
832 derived.__setstate__(state)
833 self.assertEqual(orig, derived)
834 for pickler in pickle, cPickle:
835 for binary in 0, 1:
836 green = pickler.dumps(orig, binary)
837 derived = pickler.loads(green)
838 self.assertEqual(orig, derived)
839
840 def test_compare(self):
841 t1 = self.theclass(2, 3, 4)
842 t2 = self.theclass(2, 3, 4)
843 self.failUnless(t1 == t2)
844 self.failUnless(t1 <= t2)
845 self.failUnless(t1 >= t2)
846 self.failUnless(not t1 != t2)
847 self.failUnless(not t1 < t2)
848 self.failUnless(not t1 > t2)
849 self.assertEqual(cmp(t1, t2), 0)
850 self.assertEqual(cmp(t2, t1), 0)
851
852 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
853 t2 = self.theclass(*args) # this is larger than t1
854 self.failUnless(t1 < t2)
855 self.failUnless(t2 > t1)
856 self.failUnless(t1 <= t2)
857 self.failUnless(t2 >= t1)
858 self.failUnless(t1 != t2)
859 self.failUnless(t2 != t1)
860 self.failUnless(not t1 == t2)
861 self.failUnless(not t2 == t1)
862 self.failUnless(not t1 > t2)
863 self.failUnless(not t2 < t1)
864 self.failUnless(not t1 >= t2)
865 self.failUnless(not t2 <= t1)
866 self.assertEqual(cmp(t1, t2), -1)
867 self.assertEqual(cmp(t2, t1), 1)
868
869 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
870 self.assertRaises(TypeError, lambda: t1 == badarg)
871 self.assertRaises(TypeError, lambda: t1 != badarg)
872 self.assertRaises(TypeError, lambda: t1 <= badarg)
873 self.assertRaises(TypeError, lambda: t1 < badarg)
874 self.assertRaises(TypeError, lambda: t1 > badarg)
875 self.assertRaises(TypeError, lambda: t1 >= badarg)
876 self.assertRaises(TypeError, lambda: badarg == t1)
877 self.assertRaises(TypeError, lambda: badarg != t1)
878 self.assertRaises(TypeError, lambda: badarg <= t1)
879 self.assertRaises(TypeError, lambda: badarg < t1)
880 self.assertRaises(TypeError, lambda: badarg > t1)
881 self.assertRaises(TypeError, lambda: badarg >= t1)
882
883 def test_bool(self):
884 # All dates are considered true.
885 self.failUnless(self.theclass.min)
886 self.failUnless(self.theclass.max)
887
Tim Petersd6844152002-12-22 20:58:42 +0000888 def test_srftime_out_of_range(self):
889 # For nasty technical reasons, we can't handle years before 1900.
890 cls = self.theclass
891 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
892 for y in 1, 49, 51, 99, 100, 1000, 1899:
893 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000894
895 def test_replace(self):
896 cls = self.theclass
897 args = [1, 2, 3]
898 base = cls(*args)
899 self.assertEqual(base, base.replace())
900
901 i = 0
902 for name, newval in (("year", 2),
903 ("month", 3),
904 ("day", 4)):
905 newargs = args[:]
906 newargs[i] = newval
907 expected = cls(*newargs)
908 got = base.replace(**{name: newval})
909 self.assertEqual(expected, got)
910 i += 1
911
912 # Out of bounds.
913 base = cls(2000, 2, 29)
914 self.assertRaises(ValueError, base.replace, year=2001)
915
Tim Peters2a799bf2002-12-16 20:18:38 +0000916#############################################################################
917# datetime tests
918
919class TestDateTime(TestDate):
920
921 theclass = datetime
922
923 def test_basic_attributes(self):
924 dt = self.theclass(2002, 3, 1, 12, 0)
925 self.assertEqual(dt.year, 2002)
926 self.assertEqual(dt.month, 3)
927 self.assertEqual(dt.day, 1)
928 self.assertEqual(dt.hour, 12)
929 self.assertEqual(dt.minute, 0)
930 self.assertEqual(dt.second, 0)
931 self.assertEqual(dt.microsecond, 0)
932
933 def test_basic_attributes_nonzero(self):
934 # Make sure all attributes are non-zero so bugs in
935 # bit-shifting access show up.
936 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
937 self.assertEqual(dt.year, 2002)
938 self.assertEqual(dt.month, 3)
939 self.assertEqual(dt.day, 1)
940 self.assertEqual(dt.hour, 12)
941 self.assertEqual(dt.minute, 59)
942 self.assertEqual(dt.second, 59)
943 self.assertEqual(dt.microsecond, 8000)
944
945 def test_roundtrip(self):
946 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
947 self.theclass.now()):
948 # Verify dt -> string -> datetime identity.
949 s = repr(dt)
950 self.failUnless(s.startswith('datetime.'))
951 s = s[9:]
952 dt2 = eval(s)
953 self.assertEqual(dt, dt2)
954
955 # Verify identity via reconstructing from pieces.
956 dt2 = self.theclass(dt.year, dt.month, dt.day,
957 dt.hour, dt.minute, dt.second,
958 dt.microsecond)
959 self.assertEqual(dt, dt2)
960
961 def test_isoformat(self):
962 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
963 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
964 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
965 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
966 # str is ISO format with the separator forced to a blank.
967 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
968
969 t = self.theclass(2, 3, 2)
970 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
971 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
972 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
973 # str is ISO format with the separator forced to a blank.
974 self.assertEqual(str(t), "0002-03-02 00:00:00")
975
976 def test_more_ctime(self):
977 # Test fields that TestDate doesn't touch.
978 import time
979
980 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
981 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
982 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
983 # out. The difference is that t.ctime() produces " 2" for the day,
984 # but platform ctime() produces "02" for the day. According to
985 # C99, t.ctime() is correct here.
986 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
987
988 # So test a case where that difference doesn't matter.
989 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
990 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
991
992 def test_tz_independent_comparing(self):
993 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
994 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
995 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
996 self.assertEqual(dt1, dt3)
997 self.assert_(dt2 > dt3)
998
999 # Make sure comparison doesn't forget microseconds, and isn't done
1000 # via comparing a float timestamp (an IEEE double doesn't have enough
1001 # precision to span microsecond resolution across years 1 thru 9999,
1002 # so comparing via timestamp necessarily calls some distinct values
1003 # equal).
1004 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1005 us = timedelta(microseconds=1)
1006 dt2 = dt1 + us
1007 self.assertEqual(dt2 - dt1, us)
1008 self.assert_(dt1 < dt2)
1009
1010 def test_bad_constructor_arguments(self):
1011 # bad years
1012 self.theclass(MINYEAR, 1, 1) # no exception
1013 self.theclass(MAXYEAR, 1, 1) # no exception
1014 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1015 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1016 # bad months
1017 self.theclass(2000, 1, 1) # no exception
1018 self.theclass(2000, 12, 1) # no exception
1019 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1020 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1021 # bad days
1022 self.theclass(2000, 2, 29) # no exception
1023 self.theclass(2004, 2, 29) # no exception
1024 self.theclass(2400, 2, 29) # no exception
1025 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1026 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1027 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1028 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1029 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1030 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1031 # bad hours
1032 self.theclass(2000, 1, 31, 0) # no exception
1033 self.theclass(2000, 1, 31, 23) # no exception
1034 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1035 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1036 # bad minutes
1037 self.theclass(2000, 1, 31, 23, 0) # no exception
1038 self.theclass(2000, 1, 31, 23, 59) # no exception
1039 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1040 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1041 # bad seconds
1042 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1043 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1044 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1045 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1046 # bad microseconds
1047 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1048 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1049 self.assertRaises(ValueError, self.theclass,
1050 2000, 1, 31, 23, 59, 59, -1)
1051 self.assertRaises(ValueError, self.theclass,
1052 2000, 1, 31, 23, 59, 59,
1053 1000000)
1054
1055 def test_hash_equality(self):
1056 d = self.theclass(2000, 12, 31, 23, 30, 17)
1057 e = self.theclass(2000, 12, 31, 23, 30, 17)
1058 self.assertEqual(d, e)
1059 self.assertEqual(hash(d), hash(e))
1060
1061 dic = {d: 1}
1062 dic[e] = 2
1063 self.assertEqual(len(dic), 1)
1064 self.assertEqual(dic[d], 2)
1065 self.assertEqual(dic[e], 2)
1066
1067 d = self.theclass(2001, 1, 1, 0, 5, 17)
1068 e = self.theclass(2001, 1, 1, 0, 5, 17)
1069 self.assertEqual(d, e)
1070 self.assertEqual(hash(d), hash(e))
1071
1072 dic = {d: 1}
1073 dic[e] = 2
1074 self.assertEqual(len(dic), 1)
1075 self.assertEqual(dic[d], 2)
1076 self.assertEqual(dic[e], 2)
1077
1078 def test_computations(self):
1079 a = self.theclass(2002, 1, 31)
1080 b = self.theclass(1956, 1, 31)
1081 diff = a-b
1082 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1083 self.assertEqual(diff.seconds, 0)
1084 self.assertEqual(diff.microseconds, 0)
1085 a = self.theclass(2002, 3, 2, 17, 6)
1086 millisec = timedelta(0, 0, 1000)
1087 hour = timedelta(0, 3600)
1088 day = timedelta(1)
1089 week = timedelta(7)
1090 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1091 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1092 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1093 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1094 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1095 self.assertEqual(a - hour, a + -hour)
1096 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1097 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1098 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1099 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1100 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1101 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1102 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1103 self.assertEqual((a + week) - a, week)
1104 self.assertEqual((a + day) - a, day)
1105 self.assertEqual((a + hour) - a, hour)
1106 self.assertEqual((a + millisec) - a, millisec)
1107 self.assertEqual((a - week) - a, -week)
1108 self.assertEqual((a - day) - a, -day)
1109 self.assertEqual((a - hour) - a, -hour)
1110 self.assertEqual((a - millisec) - a, -millisec)
1111 self.assertEqual(a - (a + week), -week)
1112 self.assertEqual(a - (a + day), -day)
1113 self.assertEqual(a - (a + hour), -hour)
1114 self.assertEqual(a - (a + millisec), -millisec)
1115 self.assertEqual(a - (a - week), week)
1116 self.assertEqual(a - (a - day), day)
1117 self.assertEqual(a - (a - hour), hour)
1118 self.assertEqual(a - (a - millisec), millisec)
1119 self.assertEqual(a + (week + day + hour + millisec),
1120 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1121 self.assertEqual(a + (week + day + hour + millisec),
1122 (((a + week) + day) + hour) + millisec)
1123 self.assertEqual(a - (week + day + hour + millisec),
1124 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1125 self.assertEqual(a - (week + day + hour + millisec),
1126 (((a - week) - day) - hour) - millisec)
1127 # Add/sub ints, longs, floats should be illegal
1128 for i in 1, 1L, 1.0:
1129 self.assertRaises(TypeError, lambda: a+i)
1130 self.assertRaises(TypeError, lambda: a-i)
1131 self.assertRaises(TypeError, lambda: i+a)
1132 self.assertRaises(TypeError, lambda: i-a)
1133
1134 # delta - datetime is senseless.
1135 self.assertRaises(TypeError, lambda: day - a)
1136 # mixing datetime and (delta or datetime) via * or // is senseless
1137 self.assertRaises(TypeError, lambda: day * a)
1138 self.assertRaises(TypeError, lambda: a * day)
1139 self.assertRaises(TypeError, lambda: day // a)
1140 self.assertRaises(TypeError, lambda: a // day)
1141 self.assertRaises(TypeError, lambda: a * a)
1142 self.assertRaises(TypeError, lambda: a // a)
1143 # datetime + datetime is senseless
1144 self.assertRaises(TypeError, lambda: a + a)
1145
1146 def test_pickling(self):
1147 import pickle, cPickle
1148 args = 6, 7, 23, 20, 59, 1, 64**2
1149 orig = self.theclass(*args)
1150 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001151 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001152 derived = self.theclass(1, 1, 1)
1153 derived.__setstate__(state)
1154 self.assertEqual(orig, derived)
1155 for pickler in pickle, cPickle:
1156 for binary in 0, 1:
1157 green = pickler.dumps(orig, binary)
1158 derived = pickler.loads(green)
1159 self.assertEqual(orig, derived)
1160
1161 def test_more_compare(self):
1162 # The test_compare() inherited from TestDate covers the error cases.
1163 # We just want to test lexicographic ordering on the members datetime
1164 # has that date lacks.
1165 args = [2000, 11, 29, 20, 58, 16, 999998]
1166 t1 = self.theclass(*args)
1167 t2 = self.theclass(*args)
1168 self.failUnless(t1 == t2)
1169 self.failUnless(t1 <= t2)
1170 self.failUnless(t1 >= t2)
1171 self.failUnless(not t1 != t2)
1172 self.failUnless(not t1 < t2)
1173 self.failUnless(not t1 > t2)
1174 self.assertEqual(cmp(t1, t2), 0)
1175 self.assertEqual(cmp(t2, t1), 0)
1176
1177 for i in range(len(args)):
1178 newargs = args[:]
1179 newargs[i] = args[i] + 1
1180 t2 = self.theclass(*newargs) # this is larger than t1
1181 self.failUnless(t1 < t2)
1182 self.failUnless(t2 > t1)
1183 self.failUnless(t1 <= t2)
1184 self.failUnless(t2 >= t1)
1185 self.failUnless(t1 != t2)
1186 self.failUnless(t2 != t1)
1187 self.failUnless(not t1 == t2)
1188 self.failUnless(not t2 == t1)
1189 self.failUnless(not t1 > t2)
1190 self.failUnless(not t2 < t1)
1191 self.failUnless(not t1 >= t2)
1192 self.failUnless(not t2 <= t1)
1193 self.assertEqual(cmp(t1, t2), -1)
1194 self.assertEqual(cmp(t2, t1), 1)
1195
1196
1197 # A helper for timestamp constructor tests.
1198 def verify_field_equality(self, expected, got):
1199 self.assertEqual(expected.tm_year, got.year)
1200 self.assertEqual(expected.tm_mon, got.month)
1201 self.assertEqual(expected.tm_mday, got.day)
1202 self.assertEqual(expected.tm_hour, got.hour)
1203 self.assertEqual(expected.tm_min, got.minute)
1204 self.assertEqual(expected.tm_sec, got.second)
1205
1206 def test_fromtimestamp(self):
1207 import time
1208
1209 ts = time.time()
1210 expected = time.localtime(ts)
1211 got = self.theclass.fromtimestamp(ts)
1212 self.verify_field_equality(expected, got)
1213
1214 def test_utcfromtimestamp(self):
1215 import time
1216
1217 ts = time.time()
1218 expected = time.gmtime(ts)
1219 got = self.theclass.utcfromtimestamp(ts)
1220 self.verify_field_equality(expected, got)
1221
1222 def test_utcnow(self):
1223 import time
1224
1225 # Call it a success if utcnow() and utcfromtimestamp() are within
1226 # a second of each other.
1227 tolerance = timedelta(seconds=1)
1228 for dummy in range(3):
1229 from_now = self.theclass.utcnow()
1230 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1231 if abs(from_timestamp - from_now) <= tolerance:
1232 break
1233 # Else try again a few times.
1234 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1235
1236 def test_more_timetuple(self):
1237 # This tests fields beyond those tested by the TestDate.test_timetuple.
1238 t = self.theclass(2004, 12, 31, 6, 22, 33)
1239 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1240 self.assertEqual(t.timetuple(),
1241 (t.year, t.month, t.day,
1242 t.hour, t.minute, t.second,
1243 t.weekday(),
1244 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1245 -1))
1246 tt = t.timetuple()
1247 self.assertEqual(tt.tm_year, t.year)
1248 self.assertEqual(tt.tm_mon, t.month)
1249 self.assertEqual(tt.tm_mday, t.day)
1250 self.assertEqual(tt.tm_hour, t.hour)
1251 self.assertEqual(tt.tm_min, t.minute)
1252 self.assertEqual(tt.tm_sec, t.second)
1253 self.assertEqual(tt.tm_wday, t.weekday())
1254 self.assertEqual(tt.tm_yday, t.toordinal() -
1255 date(t.year, 1, 1).toordinal() + 1)
1256 self.assertEqual(tt.tm_isdst, -1)
1257
1258 def test_more_strftime(self):
1259 # This tests fields beyond those tested by the TestDate.test_strftime.
1260 t = self.theclass(2004, 12, 31, 6, 22, 33)
1261 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1262 "12 31 04 33 22 06 366")
1263
1264 def test_extract(self):
1265 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1266 self.assertEqual(dt.date(), date(2002, 3, 4))
1267 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1268
1269 def test_combine(self):
1270 d = date(2002, 3, 4)
1271 t = time(18, 45, 3, 1234)
1272 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1273 combine = self.theclass.combine
1274 dt = combine(d, t)
1275 self.assertEqual(dt, expected)
1276
1277 dt = combine(time=t, date=d)
1278 self.assertEqual(dt, expected)
1279
1280 self.assertEqual(d, dt.date())
1281 self.assertEqual(t, dt.time())
1282 self.assertEqual(dt, combine(dt.date(), dt.time()))
1283
1284 self.assertRaises(TypeError, combine) # need an arg
1285 self.assertRaises(TypeError, combine, d) # need two args
1286 self.assertRaises(TypeError, combine, t, d) # args reversed
1287 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1288 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1289
Tim Peters12bf3392002-12-24 05:41:27 +00001290 def test_replace(self):
1291 cls = self.theclass
1292 args = [1, 2, 3, 4, 5, 6, 7]
1293 base = cls(*args)
1294 self.assertEqual(base, base.replace())
1295
1296 i = 0
1297 for name, newval in (("year", 2),
1298 ("month", 3),
1299 ("day", 4),
1300 ("hour", 5),
1301 ("minute", 6),
1302 ("second", 7),
1303 ("microsecond", 8)):
1304 newargs = args[:]
1305 newargs[i] = newval
1306 expected = cls(*newargs)
1307 got = base.replace(**{name: newval})
1308 self.assertEqual(expected, got)
1309 i += 1
1310
1311 # Out of bounds.
1312 base = cls(2000, 2, 29)
1313 self.assertRaises(ValueError, base.replace, year=2001)
1314
Tim Peters80475bb2002-12-25 07:40:55 +00001315 def test_astimezone(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001316 # Pretty boring! The TZ test is more interesting here.
Tim Peters80475bb2002-12-25 07:40:55 +00001317 dt = self.theclass.now()
1318 f = FixedOffset(44, "")
1319 for dtz in dt.astimezone(f), dt.astimezone(tz=f):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001320 self.failUnless(isinstance(dtz, datetime))
Tim Peters80475bb2002-12-25 07:40:55 +00001321 self.assertEqual(dt.date(), dtz.date())
1322 self.assertEqual(dt.time(), dtz.time())
1323 self.failUnless(dtz.tzinfo is f)
1324 self.assertEqual(dtz.utcoffset(), timedelta(minutes=44))
1325
1326 self.assertRaises(TypeError, dt.astimezone) # not enough args
1327 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1328 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1329
Tim Peters12bf3392002-12-24 05:41:27 +00001330
Tim Peters2a799bf2002-12-16 20:18:38 +00001331class TestTime(unittest.TestCase):
1332
1333 theclass = time
1334
1335 def test_basic_attributes(self):
1336 t = self.theclass(12, 0)
1337 self.assertEqual(t.hour, 12)
1338 self.assertEqual(t.minute, 0)
1339 self.assertEqual(t.second, 0)
1340 self.assertEqual(t.microsecond, 0)
1341
1342 def test_basic_attributes_nonzero(self):
1343 # Make sure all attributes are non-zero so bugs in
1344 # bit-shifting access show up.
1345 t = self.theclass(12, 59, 59, 8000)
1346 self.assertEqual(t.hour, 12)
1347 self.assertEqual(t.minute, 59)
1348 self.assertEqual(t.second, 59)
1349 self.assertEqual(t.microsecond, 8000)
1350
1351 def test_roundtrip(self):
1352 t = self.theclass(1, 2, 3, 4)
1353
1354 # Verify t -> string -> time identity.
1355 s = repr(t)
1356 self.failUnless(s.startswith('datetime.'))
1357 s = s[9:]
1358 t2 = eval(s)
1359 self.assertEqual(t, t2)
1360
1361 # Verify identity via reconstructing from pieces.
1362 t2 = self.theclass(t.hour, t.minute, t.second,
1363 t.microsecond)
1364 self.assertEqual(t, t2)
1365
1366 def test_comparing(self):
1367 args = [1, 2, 3, 4]
1368 t1 = self.theclass(*args)
1369 t2 = self.theclass(*args)
1370 self.failUnless(t1 == t2)
1371 self.failUnless(t1 <= t2)
1372 self.failUnless(t1 >= t2)
1373 self.failUnless(not t1 != t2)
1374 self.failUnless(not t1 < t2)
1375 self.failUnless(not t1 > t2)
1376 self.assertEqual(cmp(t1, t2), 0)
1377 self.assertEqual(cmp(t2, t1), 0)
1378
1379 for i in range(len(args)):
1380 newargs = args[:]
1381 newargs[i] = args[i] + 1
1382 t2 = self.theclass(*newargs) # this is larger than t1
1383 self.failUnless(t1 < t2)
1384 self.failUnless(t2 > t1)
1385 self.failUnless(t1 <= t2)
1386 self.failUnless(t2 >= t1)
1387 self.failUnless(t1 != t2)
1388 self.failUnless(t2 != t1)
1389 self.failUnless(not t1 == t2)
1390 self.failUnless(not t2 == t1)
1391 self.failUnless(not t1 > t2)
1392 self.failUnless(not t2 < t1)
1393 self.failUnless(not t1 >= t2)
1394 self.failUnless(not t2 <= t1)
1395 self.assertEqual(cmp(t1, t2), -1)
1396 self.assertEqual(cmp(t2, t1), 1)
1397
Tim Peters0bf60bd2003-01-08 20:40:01 +00001398 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1399 if CMP_BUG_FIXED:
1400 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1401 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001402 self.assertRaises(TypeError, lambda: t1 == badarg)
1403 self.assertRaises(TypeError, lambda: t1 != badarg)
1404 self.assertRaises(TypeError, lambda: t1 <= badarg)
1405 self.assertRaises(TypeError, lambda: t1 < badarg)
1406 self.assertRaises(TypeError, lambda: t1 > badarg)
1407 self.assertRaises(TypeError, lambda: t1 >= badarg)
1408 self.assertRaises(TypeError, lambda: badarg == t1)
1409 self.assertRaises(TypeError, lambda: badarg != t1)
1410 self.assertRaises(TypeError, lambda: badarg <= t1)
1411 self.assertRaises(TypeError, lambda: badarg < t1)
1412 self.assertRaises(TypeError, lambda: badarg > t1)
1413 self.assertRaises(TypeError, lambda: badarg >= t1)
1414
1415 def test_bad_constructor_arguments(self):
1416 # bad hours
1417 self.theclass(0, 0) # no exception
1418 self.theclass(23, 0) # no exception
1419 self.assertRaises(ValueError, self.theclass, -1, 0)
1420 self.assertRaises(ValueError, self.theclass, 24, 0)
1421 # bad minutes
1422 self.theclass(23, 0) # no exception
1423 self.theclass(23, 59) # no exception
1424 self.assertRaises(ValueError, self.theclass, 23, -1)
1425 self.assertRaises(ValueError, self.theclass, 23, 60)
1426 # bad seconds
1427 self.theclass(23, 59, 0) # no exception
1428 self.theclass(23, 59, 59) # no exception
1429 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1430 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1431 # bad microseconds
1432 self.theclass(23, 59, 59, 0) # no exception
1433 self.theclass(23, 59, 59, 999999) # no exception
1434 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1435 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1436
1437 def test_hash_equality(self):
1438 d = self.theclass(23, 30, 17)
1439 e = self.theclass(23, 30, 17)
1440 self.assertEqual(d, e)
1441 self.assertEqual(hash(d), hash(e))
1442
1443 dic = {d: 1}
1444 dic[e] = 2
1445 self.assertEqual(len(dic), 1)
1446 self.assertEqual(dic[d], 2)
1447 self.assertEqual(dic[e], 2)
1448
1449 d = self.theclass(0, 5, 17)
1450 e = self.theclass(0, 5, 17)
1451 self.assertEqual(d, e)
1452 self.assertEqual(hash(d), hash(e))
1453
1454 dic = {d: 1}
1455 dic[e] = 2
1456 self.assertEqual(len(dic), 1)
1457 self.assertEqual(dic[d], 2)
1458 self.assertEqual(dic[e], 2)
1459
1460 def test_isoformat(self):
1461 t = self.theclass(4, 5, 1, 123)
1462 self.assertEqual(t.isoformat(), "04:05:01.000123")
1463 self.assertEqual(t.isoformat(), str(t))
1464
1465 t = self.theclass()
1466 self.assertEqual(t.isoformat(), "00:00:00")
1467 self.assertEqual(t.isoformat(), str(t))
1468
1469 t = self.theclass(microsecond=1)
1470 self.assertEqual(t.isoformat(), "00:00:00.000001")
1471 self.assertEqual(t.isoformat(), str(t))
1472
1473 t = self.theclass(microsecond=10)
1474 self.assertEqual(t.isoformat(), "00:00:00.000010")
1475 self.assertEqual(t.isoformat(), str(t))
1476
1477 t = self.theclass(microsecond=100)
1478 self.assertEqual(t.isoformat(), "00:00:00.000100")
1479 self.assertEqual(t.isoformat(), str(t))
1480
1481 t = self.theclass(microsecond=1000)
1482 self.assertEqual(t.isoformat(), "00:00:00.001000")
1483 self.assertEqual(t.isoformat(), str(t))
1484
1485 t = self.theclass(microsecond=10000)
1486 self.assertEqual(t.isoformat(), "00:00:00.010000")
1487 self.assertEqual(t.isoformat(), str(t))
1488
1489 t = self.theclass(microsecond=100000)
1490 self.assertEqual(t.isoformat(), "00:00:00.100000")
1491 self.assertEqual(t.isoformat(), str(t))
1492
1493 def test_strftime(self):
1494 t = self.theclass(1, 2, 3, 4)
1495 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1496 # A naive object replaces %z and %Z with empty strings.
1497 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1498
1499 def test_str(self):
1500 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1501 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1502 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1503 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1504 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1505
1506 def test_repr(self):
1507 name = 'datetime.' + self.theclass.__name__
1508 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1509 "%s(1, 2, 3, 4)" % name)
1510 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1511 "%s(10, 2, 3, 4000)" % name)
1512 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1513 "%s(0, 2, 3, 400000)" % name)
1514 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1515 "%s(12, 2, 3)" % name)
1516 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1517 "%s(23, 15)" % name)
1518
1519 def test_resolution_info(self):
1520 self.assert_(isinstance(self.theclass.min, self.theclass))
1521 self.assert_(isinstance(self.theclass.max, self.theclass))
1522 self.assert_(isinstance(self.theclass.resolution, timedelta))
1523 self.assert_(self.theclass.max > self.theclass.min)
1524
1525 def test_pickling(self):
1526 import pickle, cPickle
1527 args = 20, 59, 16, 64**2
1528 orig = self.theclass(*args)
1529 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001530 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001531 derived = self.theclass()
1532 derived.__setstate__(state)
1533 self.assertEqual(orig, derived)
1534 for pickler in pickle, cPickle:
1535 for binary in 0, 1:
1536 green = pickler.dumps(orig, binary)
1537 derived = pickler.loads(green)
1538 self.assertEqual(orig, derived)
1539
1540 def test_bool(self):
1541 cls = self.theclass
1542 self.failUnless(cls(1))
1543 self.failUnless(cls(0, 1))
1544 self.failUnless(cls(0, 0, 1))
1545 self.failUnless(cls(0, 0, 0, 1))
1546 self.failUnless(not cls(0))
1547 self.failUnless(not cls())
1548
Tim Peters12bf3392002-12-24 05:41:27 +00001549 def test_replace(self):
1550 cls = self.theclass
1551 args = [1, 2, 3, 4]
1552 base = cls(*args)
1553 self.assertEqual(base, base.replace())
1554
1555 i = 0
1556 for name, newval in (("hour", 5),
1557 ("minute", 6),
1558 ("second", 7),
1559 ("microsecond", 8)):
1560 newargs = args[:]
1561 newargs[i] = newval
1562 expected = cls(*newargs)
1563 got = base.replace(**{name: newval})
1564 self.assertEqual(expected, got)
1565 i += 1
1566
1567 # Out of bounds.
1568 base = cls(1)
1569 self.assertRaises(ValueError, base.replace, hour=24)
1570 self.assertRaises(ValueError, base.replace, minute=-1)
1571 self.assertRaises(ValueError, base.replace, second=100)
1572 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1573
Tim Peters855fe882002-12-22 03:43:39 +00001574# A mixin for classes with a tzinfo= argument. Subclasses must define
1575# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001576# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001577class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001578
Tim Petersbad8ff02002-12-30 20:52:32 +00001579 def test_argument_passing(self):
1580 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001581 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001582 class introspective(tzinfo):
1583 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001584 def utcoffset(self, dt):
1585 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001586 dst = utcoffset
1587
1588 obj = cls(1, 2, 3, tzinfo=introspective())
1589
Tim Peters0bf60bd2003-01-08 20:40:01 +00001590 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001591 self.assertEqual(obj.tzname(), expected)
1592
Tim Peters0bf60bd2003-01-08 20:40:01 +00001593 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001594 self.assertEqual(obj.utcoffset(), expected)
1595 self.assertEqual(obj.dst(), expected)
1596
Tim Peters855fe882002-12-22 03:43:39 +00001597 def test_bad_tzinfo_classes(self):
1598 cls = self.theclass
1599 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001600
Tim Peters855fe882002-12-22 03:43:39 +00001601 class NiceTry(object):
1602 def __init__(self): pass
1603 def utcoffset(self, dt): pass
1604 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1605
1606 class BetterTry(tzinfo):
1607 def __init__(self): pass
1608 def utcoffset(self, dt): pass
1609 b = BetterTry()
1610 t = cls(1, 1, 1, tzinfo=b)
1611 self.failUnless(t.tzinfo is b)
1612
1613 def test_utc_offset_out_of_bounds(self):
1614 class Edgy(tzinfo):
1615 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001616 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001617 def utcoffset(self, dt):
1618 return self.offset
1619
1620 cls = self.theclass
1621 for offset, legit in ((-1440, False),
1622 (-1439, True),
1623 (1439, True),
1624 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001625 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001626 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001627 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001628 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001629 else:
1630 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001631 if legit:
1632 aofs = abs(offset)
1633 h, m = divmod(aofs, 60)
1634 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001635 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001636 t = t.timetz()
1637 self.assertEqual(str(t), "01:02:03" + tag)
1638 else:
1639 self.assertRaises(ValueError, str, t)
1640
1641 def test_tzinfo_classes(self):
1642 cls = self.theclass
1643 class C1(tzinfo):
1644 def utcoffset(self, dt): return None
1645 def dst(self, dt): return None
1646 def tzname(self, dt): return None
1647 for t in (cls(1, 1, 1),
1648 cls(1, 1, 1, tzinfo=None),
1649 cls(1, 1, 1, tzinfo=C1())):
1650 self.failUnless(t.utcoffset() is None)
1651 self.failUnless(t.dst() is None)
1652 self.failUnless(t.tzname() is None)
1653
Tim Peters855fe882002-12-22 03:43:39 +00001654 class C3(tzinfo):
1655 def utcoffset(self, dt): return timedelta(minutes=-1439)
1656 def dst(self, dt): return timedelta(minutes=1439)
1657 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001658 t = cls(1, 1, 1, tzinfo=C3())
1659 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1660 self.assertEqual(t.dst(), timedelta(minutes=1439))
1661 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001662
1663 # Wrong types.
1664 class C4(tzinfo):
1665 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001666 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001667 def tzname(self, dt): return 0
1668 t = cls(1, 1, 1, tzinfo=C4())
1669 self.assertRaises(TypeError, t.utcoffset)
1670 self.assertRaises(TypeError, t.dst)
1671 self.assertRaises(TypeError, t.tzname)
1672
1673 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001674 class C6(tzinfo):
1675 def utcoffset(self, dt): return timedelta(hours=-24)
1676 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001677 t = cls(1, 1, 1, tzinfo=C6())
1678 self.assertRaises(ValueError, t.utcoffset)
1679 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001680
1681 # Not a whole number of minutes.
1682 class C7(tzinfo):
1683 def utcoffset(self, dt): return timedelta(seconds=61)
1684 def dst(self, dt): return timedelta(microseconds=-81)
1685 t = cls(1, 1, 1, tzinfo=C7())
1686 self.assertRaises(ValueError, t.utcoffset)
1687 self.assertRaises(ValueError, t.dst)
1688
Tim Peters4c0db782002-12-26 05:01:19 +00001689 def test_aware_compare(self):
1690 cls = self.theclass
1691
Tim Peters60c76e42002-12-27 00:41:11 +00001692 # Ensure that utcoffset() gets ignored if the comparands have
1693 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001694 class OperandDependentOffset(tzinfo):
1695 def utcoffset(self, t):
1696 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001697 # d0 and d1 equal after adjustment
1698 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001699 else:
Tim Peters397301e2003-01-02 21:28:08 +00001700 # d2 off in the weeds
1701 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001702
1703 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1704 d0 = base.replace(minute=3)
1705 d1 = base.replace(minute=9)
1706 d2 = base.replace(minute=11)
1707 for x in d0, d1, d2:
1708 for y in d0, d1, d2:
1709 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001710 expected = cmp(x.minute, y.minute)
1711 self.assertEqual(got, expected)
1712
1713 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001714 # Note that a time can't actually have an operand-depedent offset,
1715 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1716 # so skip this test for time.
1717 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001718 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1719 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1720 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1721 for x in d0, d1, d2:
1722 for y in d0, d1, d2:
1723 got = cmp(x, y)
1724 if (x is d0 or x is d1) and (y is d0 or y is d1):
1725 expected = 0
1726 elif x is y is d2:
1727 expected = 0
1728 elif x is d2:
1729 expected = -1
1730 else:
1731 assert y is d2
1732 expected = 1
1733 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001734
Tim Peters855fe882002-12-22 03:43:39 +00001735
Tim Peters0bf60bd2003-01-08 20:40:01 +00001736# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001737class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001738 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001739
1740 def test_empty(self):
1741 t = self.theclass()
1742 self.assertEqual(t.hour, 0)
1743 self.assertEqual(t.minute, 0)
1744 self.assertEqual(t.second, 0)
1745 self.assertEqual(t.microsecond, 0)
1746 self.failUnless(t.tzinfo is None)
1747
Tim Peters2a799bf2002-12-16 20:18:38 +00001748 def test_zones(self):
1749 est = FixedOffset(-300, "EST", 1)
1750 utc = FixedOffset(0, "UTC", -2)
1751 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001752 t1 = time( 7, 47, tzinfo=est)
1753 t2 = time(12, 47, tzinfo=utc)
1754 t3 = time(13, 47, tzinfo=met)
1755 t4 = time(microsecond=40)
1756 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001757
1758 self.assertEqual(t1.tzinfo, est)
1759 self.assertEqual(t2.tzinfo, utc)
1760 self.assertEqual(t3.tzinfo, met)
1761 self.failUnless(t4.tzinfo is None)
1762 self.assertEqual(t5.tzinfo, utc)
1763
Tim Peters855fe882002-12-22 03:43:39 +00001764 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1765 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1766 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001767 self.failUnless(t4.utcoffset() is None)
1768 self.assertRaises(TypeError, t1.utcoffset, "no args")
1769
1770 self.assertEqual(t1.tzname(), "EST")
1771 self.assertEqual(t2.tzname(), "UTC")
1772 self.assertEqual(t3.tzname(), "MET")
1773 self.failUnless(t4.tzname() is None)
1774 self.assertRaises(TypeError, t1.tzname, "no args")
1775
Tim Peters855fe882002-12-22 03:43:39 +00001776 self.assertEqual(t1.dst(), timedelta(minutes=1))
1777 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1778 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001779 self.failUnless(t4.dst() is None)
1780 self.assertRaises(TypeError, t1.dst, "no args")
1781
1782 self.assertEqual(hash(t1), hash(t2))
1783 self.assertEqual(hash(t1), hash(t3))
1784 self.assertEqual(hash(t2), hash(t3))
1785
1786 self.assertEqual(t1, t2)
1787 self.assertEqual(t1, t3)
1788 self.assertEqual(t2, t3)
1789 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1790 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1791 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1792
1793 self.assertEqual(str(t1), "07:47:00-05:00")
1794 self.assertEqual(str(t2), "12:47:00+00:00")
1795 self.assertEqual(str(t3), "13:47:00+01:00")
1796 self.assertEqual(str(t4), "00:00:00.000040")
1797 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1798
1799 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1800 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1801 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1802 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1803 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1804
Tim Peters0bf60bd2003-01-08 20:40:01 +00001805 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001806 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1807 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1808 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1809 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1810 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1811
1812 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1813 "07:47:00 %Z=EST %z=-0500")
1814 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1815 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1816
1817 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001818 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001819 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1820 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1821
Tim Petersb92bb712002-12-21 17:44:07 +00001822 # Check that an invalid tzname result raises an exception.
1823 class Badtzname(tzinfo):
1824 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001825 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001826 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1827 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001828
1829 def test_hash_edge_cases(self):
1830 # Offsets that overflow a basic time.
1831 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1832 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1833 self.assertEqual(hash(t1), hash(t2))
1834
1835 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1836 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1837 self.assertEqual(hash(t1), hash(t2))
1838
Tim Peters2a799bf2002-12-16 20:18:38 +00001839 def test_pickling(self):
1840 import pickle, cPickle
1841
1842 # Try one without a tzinfo.
1843 args = 20, 59, 16, 64**2
1844 orig = self.theclass(*args)
1845 state = orig.__getstate__()
1846 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
1847 derived = self.theclass()
1848 derived.__setstate__(state)
1849 self.assertEqual(orig, derived)
1850 for pickler in pickle, cPickle:
1851 for binary in 0, 1:
1852 green = pickler.dumps(orig, binary)
1853 derived = pickler.loads(green)
1854 self.assertEqual(orig, derived)
1855
1856 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001857 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001858 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
1859 state = orig.__getstate__()
Tim Peters37f39822003-01-10 03:49:02 +00001860 derived = self.theclass(tzinfo=FixedOffset(0, "UTC", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00001861 derived.__setstate__(state)
1862 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001863 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001864 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001865 self.assertEqual(derived.tzname(), 'cookie')
1866
1867 for pickler in pickle, cPickle:
1868 for binary in 0, 1:
1869 green = pickler.dumps(orig, binary)
1870 derived = pickler.loads(green)
1871 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001872 self.failUnless(isinstance(derived.tzinfo,
1873 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001874 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001875 self.assertEqual(derived.tzname(), 'cookie')
1876
1877 def test_more_bool(self):
1878 # Test cases with non-None tzinfo.
1879 cls = self.theclass
1880
1881 t = cls(0, tzinfo=FixedOffset(-300, ""))
1882 self.failUnless(t)
1883
1884 t = cls(5, tzinfo=FixedOffset(-300, ""))
1885 self.failUnless(t)
1886
1887 t = cls(5, tzinfo=FixedOffset(300, ""))
1888 self.failUnless(not t)
1889
1890 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1891 self.failUnless(not t)
1892
1893 # Mostly ensuring this doesn't overflow internally.
1894 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1895 self.failUnless(t)
1896
1897 # But this should yield a value error -- the utcoffset is bogus.
1898 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1899 self.assertRaises(ValueError, lambda: bool(t))
1900
1901 # Likewise.
1902 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1903 self.assertRaises(ValueError, lambda: bool(t))
1904
Tim Peters12bf3392002-12-24 05:41:27 +00001905 def test_replace(self):
1906 cls = self.theclass
1907 z100 = FixedOffset(100, "+100")
1908 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1909 args = [1, 2, 3, 4, z100]
1910 base = cls(*args)
1911 self.assertEqual(base, base.replace())
1912
1913 i = 0
1914 for name, newval in (("hour", 5),
1915 ("minute", 6),
1916 ("second", 7),
1917 ("microsecond", 8),
1918 ("tzinfo", zm200)):
1919 newargs = args[:]
1920 newargs[i] = newval
1921 expected = cls(*newargs)
1922 got = base.replace(**{name: newval})
1923 self.assertEqual(expected, got)
1924 i += 1
1925
1926 # Ensure we can get rid of a tzinfo.
1927 self.assertEqual(base.tzname(), "+100")
1928 base2 = base.replace(tzinfo=None)
1929 self.failUnless(base2.tzinfo is None)
1930 self.failUnless(base2.tzname() is None)
1931
1932 # Ensure we can add one.
1933 base3 = base2.replace(tzinfo=z100)
1934 self.assertEqual(base, base3)
1935 self.failUnless(base.tzinfo is base3.tzinfo)
1936
1937 # Out of bounds.
1938 base = cls(1)
1939 self.assertRaises(ValueError, base.replace, hour=24)
1940 self.assertRaises(ValueError, base.replace, minute=-1)
1941 self.assertRaises(ValueError, base.replace, second=100)
1942 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1943
Tim Peters60c76e42002-12-27 00:41:11 +00001944 def test_mixed_compare(self):
1945 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001946 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001947 self.assertEqual(t1, t2)
1948 t2 = t2.replace(tzinfo=None)
1949 self.assertEqual(t1, t2)
1950 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1951 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001952 if CMP_BUG_FIXED:
1953 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1954 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001955
Tim Peters0bf60bd2003-01-08 20:40:01 +00001956 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00001957 class Varies(tzinfo):
1958 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00001959 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00001960 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00001961 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00001962 return self.offset
1963
1964 v = Varies()
1965 t1 = t2.replace(tzinfo=v)
1966 t2 = t2.replace(tzinfo=v)
1967 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
1968 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
1969 self.assertEqual(t1, t2)
1970
1971 # But if they're not identical, it isn't ignored.
1972 t2 = t2.replace(tzinfo=Varies())
1973 self.failUnless(t1 < t2) # t1's offset counter still going up
1974
Tim Peters4c0db782002-12-26 05:01:19 +00001975
Tim Peters0bf60bd2003-01-08 20:40:01 +00001976# Testing datetime objects with a non-None tzinfo.
1977
Tim Peters855fe882002-12-22 03:43:39 +00001978class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001979 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00001980
1981 def test_trivial(self):
1982 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
1983 self.assertEqual(dt.year, 1)
1984 self.assertEqual(dt.month, 2)
1985 self.assertEqual(dt.day, 3)
1986 self.assertEqual(dt.hour, 4)
1987 self.assertEqual(dt.minute, 5)
1988 self.assertEqual(dt.second, 6)
1989 self.assertEqual(dt.microsecond, 7)
1990 self.assertEqual(dt.tzinfo, None)
1991
1992 def test_even_more_compare(self):
1993 # The test_compare() and test_more_compare() inherited from TestDate
1994 # and TestDateTime covered non-tzinfo cases.
1995
1996 # Smallest possible after UTC adjustment.
1997 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
1998 # Largest possible after UTC adjustment.
1999 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2000 tzinfo=FixedOffset(-1439, ""))
2001
2002 # Make sure those compare correctly, and w/o overflow.
2003 self.failUnless(t1 < t2)
2004 self.failUnless(t1 != t2)
2005 self.failUnless(t2 > t1)
2006
2007 self.failUnless(t1 == t1)
2008 self.failUnless(t2 == t2)
2009
2010 # Equal afer adjustment.
2011 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2012 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2013 self.assertEqual(t1, t2)
2014
2015 # Change t1 not to subtract a minute, and t1 should be larger.
2016 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2017 self.failUnless(t1 > t2)
2018
2019 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2020 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2021 self.failUnless(t1 < t2)
2022
2023 # Back to the original t1, but make seconds resolve it.
2024 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2025 second=1)
2026 self.failUnless(t1 > t2)
2027
2028 # Likewise, but make microseconds resolve it.
2029 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2030 microsecond=1)
2031 self.failUnless(t1 > t2)
2032
2033 # Make t2 naive and it should fail.
2034 t2 = self.theclass.min
2035 self.assertRaises(TypeError, lambda: t1 == t2)
2036 self.assertEqual(t2, t2)
2037
2038 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2039 class Naive(tzinfo):
2040 def utcoffset(self, dt): return None
2041 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2042 self.assertRaises(TypeError, lambda: t1 == t2)
2043 self.assertEqual(t2, t2)
2044
2045 # OTOH, it's OK to compare two of these mixing the two ways of being
2046 # naive.
2047 t1 = self.theclass(5, 6, 7)
2048 self.assertEqual(t1, t2)
2049
2050 # Try a bogus uctoffset.
2051 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002052 def utcoffset(self, dt):
2053 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002054 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2055 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002056 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002057
Tim Peters2a799bf2002-12-16 20:18:38 +00002058 def test_pickling(self):
2059 import pickle, cPickle
2060
2061 # Try one without a tzinfo.
2062 args = 6, 7, 23, 20, 59, 1, 64**2
2063 orig = self.theclass(*args)
2064 state = orig.__getstate__()
2065 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
2066 derived = self.theclass(1, 1, 1)
2067 derived.__setstate__(state)
2068 self.assertEqual(orig, derived)
2069 for pickler in pickle, cPickle:
2070 for binary in 0, 1:
2071 green = pickler.dumps(orig, binary)
2072 derived = pickler.loads(green)
2073 self.assertEqual(orig, derived)
2074
2075 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002076 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002077 orig = self.theclass(*args, **{'tzinfo': tinfo})
2078 state = orig.__getstate__()
Tim Petersa9bc1682003-01-11 03:39:11 +00002079 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00002080 derived.__setstate__(state)
2081 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002082 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002083 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002084 self.assertEqual(derived.tzname(), 'cookie')
2085
2086 for pickler in pickle, cPickle:
2087 for binary in 0, 1:
2088 green = pickler.dumps(orig, binary)
2089 derived = pickler.loads(green)
2090 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002091 self.failUnless(isinstance(derived.tzinfo,
2092 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002093 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002094 self.assertEqual(derived.tzname(), 'cookie')
2095
2096 def test_extreme_hashes(self):
2097 # If an attempt is made to hash these via subtracting the offset
2098 # then hashing a datetime object, OverflowError results. The
2099 # Python implementation used to blow up here.
2100 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2101 hash(t)
2102 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2103 tzinfo=FixedOffset(-1439, ""))
2104 hash(t)
2105
2106 # OTOH, an OOB offset should blow up.
2107 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2108 self.assertRaises(ValueError, hash, t)
2109
2110 def test_zones(self):
2111 est = FixedOffset(-300, "EST")
2112 utc = FixedOffset(0, "UTC")
2113 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002114 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2115 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2116 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002117 self.assertEqual(t1.tzinfo, est)
2118 self.assertEqual(t2.tzinfo, utc)
2119 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002120 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2121 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2122 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002123 self.assertEqual(t1.tzname(), "EST")
2124 self.assertEqual(t2.tzname(), "UTC")
2125 self.assertEqual(t3.tzname(), "MET")
2126 self.assertEqual(hash(t1), hash(t2))
2127 self.assertEqual(hash(t1), hash(t3))
2128 self.assertEqual(hash(t2), hash(t3))
2129 self.assertEqual(t1, t2)
2130 self.assertEqual(t1, t3)
2131 self.assertEqual(t2, t3)
2132 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2133 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2134 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002135 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002136 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2137 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2138 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2139
2140 def test_combine(self):
2141 met = FixedOffset(60, "MET")
2142 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002143 tz = time(18, 45, 3, 1234, tzinfo=met)
2144 dt = datetime.combine(d, tz)
2145 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002146 tzinfo=met))
2147
2148 def test_extract(self):
2149 met = FixedOffset(60, "MET")
2150 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2151 self.assertEqual(dt.date(), date(2002, 3, 4))
2152 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002153 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002154
2155 def test_tz_aware_arithmetic(self):
2156 import random
2157
2158 now = self.theclass.now()
2159 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002160 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002161 nowaware = self.theclass.combine(now.date(), timeaware)
2162 self.failUnless(nowaware.tzinfo is tz55)
2163 self.assertEqual(nowaware.timetz(), timeaware)
2164
2165 # Can't mix aware and non-aware.
2166 self.assertRaises(TypeError, lambda: now - nowaware)
2167 self.assertRaises(TypeError, lambda: nowaware - now)
2168
Tim Peters0bf60bd2003-01-08 20:40:01 +00002169 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002170 self.assertRaises(TypeError, lambda: now + nowaware)
2171 self.assertRaises(TypeError, lambda: nowaware + now)
2172 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2173
2174 # Subtracting should yield 0.
2175 self.assertEqual(now - now, timedelta(0))
2176 self.assertEqual(nowaware - nowaware, timedelta(0))
2177
2178 # Adding a delta should preserve tzinfo.
2179 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2180 nowawareplus = nowaware + delta
2181 self.failUnless(nowaware.tzinfo is tz55)
2182 nowawareplus2 = delta + nowaware
2183 self.failUnless(nowawareplus2.tzinfo is tz55)
2184 self.assertEqual(nowawareplus, nowawareplus2)
2185
2186 # that - delta should be what we started with, and that - what we
2187 # started with should be delta.
2188 diff = nowawareplus - delta
2189 self.failUnless(diff.tzinfo is tz55)
2190 self.assertEqual(nowaware, diff)
2191 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2192 self.assertEqual(nowawareplus - nowaware, delta)
2193
2194 # Make up a random timezone.
2195 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002196 # Attach it to nowawareplus.
2197 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002198 self.failUnless(nowawareplus.tzinfo is tzr)
2199 # Make sure the difference takes the timezone adjustments into account.
2200 got = nowaware - nowawareplus
2201 # Expected: (nowaware base - nowaware offset) -
2202 # (nowawareplus base - nowawareplus offset) =
2203 # (nowaware base - nowawareplus base) +
2204 # (nowawareplus offset - nowaware offset) =
2205 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002206 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002207 self.assertEqual(got, expected)
2208
2209 # Try max possible difference.
2210 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2211 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2212 tzinfo=FixedOffset(-1439, "max"))
2213 maxdiff = max - min
2214 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2215 timedelta(minutes=2*1439))
2216
2217 def test_tzinfo_now(self):
2218 meth = self.theclass.now
2219 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2220 base = meth()
2221 # Try with and without naming the keyword.
2222 off42 = FixedOffset(42, "42")
2223 another = meth(off42)
2224 again = meth(tzinfo=off42)
2225 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002226 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002227 # Bad argument with and w/o naming the keyword.
2228 self.assertRaises(TypeError, meth, 16)
2229 self.assertRaises(TypeError, meth, tzinfo=16)
2230 # Bad keyword name.
2231 self.assertRaises(TypeError, meth, tinfo=off42)
2232 # Too many args.
2233 self.assertRaises(TypeError, meth, off42, off42)
2234
2235 def test_tzinfo_fromtimestamp(self):
2236 import time
2237 meth = self.theclass.fromtimestamp
2238 ts = time.time()
2239 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2240 base = meth(ts)
2241 # Try with and without naming the keyword.
2242 off42 = FixedOffset(42, "42")
2243 another = meth(ts, off42)
2244 again = meth(ts, tzinfo=off42)
2245 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002246 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002247 # Bad argument with and w/o naming the keyword.
2248 self.assertRaises(TypeError, meth, ts, 16)
2249 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2250 # Bad keyword name.
2251 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2252 # Too many args.
2253 self.assertRaises(TypeError, meth, ts, off42, off42)
2254 # Too few args.
2255 self.assertRaises(TypeError, meth)
2256
2257 def test_tzinfo_utcnow(self):
2258 meth = self.theclass.utcnow
2259 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2260 base = meth()
2261 # Try with and without naming the keyword; for whatever reason,
2262 # utcnow() doesn't accept a tzinfo argument.
2263 off42 = FixedOffset(42, "42")
2264 self.assertRaises(TypeError, meth, off42)
2265 self.assertRaises(TypeError, meth, tzinfo=off42)
2266
2267 def test_tzinfo_utcfromtimestamp(self):
2268 import time
2269 meth = self.theclass.utcfromtimestamp
2270 ts = time.time()
2271 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2272 base = meth(ts)
2273 # Try with and without naming the keyword; for whatever reason,
2274 # utcfromtimestamp() doesn't accept a tzinfo argument.
2275 off42 = FixedOffset(42, "42")
2276 self.assertRaises(TypeError, meth, ts, off42)
2277 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2278
2279 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002280 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002281 # DST flag.
2282 class DST(tzinfo):
2283 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002284 if isinstance(dstvalue, int):
2285 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002286 self.dstvalue = dstvalue
2287 def dst(self, dt):
2288 return self.dstvalue
2289
2290 cls = self.theclass
2291 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2292 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2293 t = d.timetuple()
2294 self.assertEqual(1, t.tm_year)
2295 self.assertEqual(1, t.tm_mon)
2296 self.assertEqual(1, t.tm_mday)
2297 self.assertEqual(10, t.tm_hour)
2298 self.assertEqual(20, t.tm_min)
2299 self.assertEqual(30, t.tm_sec)
2300 self.assertEqual(0, t.tm_wday)
2301 self.assertEqual(1, t.tm_yday)
2302 self.assertEqual(flag, t.tm_isdst)
2303
2304 # dst() returns wrong type.
2305 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2306
2307 # dst() at the edge.
2308 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2309 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2310
2311 # dst() out of range.
2312 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2313 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2314
2315 def test_utctimetuple(self):
2316 class DST(tzinfo):
2317 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002318 if isinstance(dstvalue, int):
2319 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002320 self.dstvalue = dstvalue
2321 def dst(self, dt):
2322 return self.dstvalue
2323
2324 cls = self.theclass
2325 # This can't work: DST didn't implement utcoffset.
2326 self.assertRaises(NotImplementedError,
2327 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2328
2329 class UOFS(DST):
2330 def __init__(self, uofs, dofs=None):
2331 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002332 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002333 def utcoffset(self, dt):
2334 return self.uofs
2335
2336 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2337 # in effect for a UTC time.
2338 for dstvalue in -33, 33, 0, None:
2339 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2340 t = d.utctimetuple()
2341 self.assertEqual(d.year, t.tm_year)
2342 self.assertEqual(d.month, t.tm_mon)
2343 self.assertEqual(d.day, t.tm_mday)
2344 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2345 self.assertEqual(13, t.tm_min)
2346 self.assertEqual(d.second, t.tm_sec)
2347 self.assertEqual(d.weekday(), t.tm_wday)
2348 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2349 t.tm_yday)
2350 self.assertEqual(0, t.tm_isdst)
2351
2352 # At the edges, UTC adjustment can normalize into years out-of-range
2353 # for a datetime object. Ensure that a correct timetuple is
2354 # created anyway.
2355 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2356 # That goes back 1 minute less than a full day.
2357 t = tiny.utctimetuple()
2358 self.assertEqual(t.tm_year, MINYEAR-1)
2359 self.assertEqual(t.tm_mon, 12)
2360 self.assertEqual(t.tm_mday, 31)
2361 self.assertEqual(t.tm_hour, 0)
2362 self.assertEqual(t.tm_min, 1)
2363 self.assertEqual(t.tm_sec, 37)
2364 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2365 self.assertEqual(t.tm_isdst, 0)
2366
2367 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2368 # That goes forward 1 minute less than a full day.
2369 t = huge.utctimetuple()
2370 self.assertEqual(t.tm_year, MAXYEAR+1)
2371 self.assertEqual(t.tm_mon, 1)
2372 self.assertEqual(t.tm_mday, 1)
2373 self.assertEqual(t.tm_hour, 23)
2374 self.assertEqual(t.tm_min, 58)
2375 self.assertEqual(t.tm_sec, 37)
2376 self.assertEqual(t.tm_yday, 1)
2377 self.assertEqual(t.tm_isdst, 0)
2378
2379 def test_tzinfo_isoformat(self):
2380 zero = FixedOffset(0, "+00:00")
2381 plus = FixedOffset(220, "+03:40")
2382 minus = FixedOffset(-231, "-03:51")
2383 unknown = FixedOffset(None, "")
2384
2385 cls = self.theclass
2386 datestr = '0001-02-03'
2387 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002388 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002389 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2390 timestr = '04:05:59' + (us and '.987001' or '')
2391 ofsstr = ofs is not None and d.tzname() or ''
2392 tailstr = timestr + ofsstr
2393 iso = d.isoformat()
2394 self.assertEqual(iso, datestr + 'T' + tailstr)
2395 self.assertEqual(iso, d.isoformat('T'))
2396 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2397 self.assertEqual(str(d), datestr + ' ' + tailstr)
2398
Tim Peters12bf3392002-12-24 05:41:27 +00002399 def test_replace(self):
2400 cls = self.theclass
2401 z100 = FixedOffset(100, "+100")
2402 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2403 args = [1, 2, 3, 4, 5, 6, 7, z100]
2404 base = cls(*args)
2405 self.assertEqual(base, base.replace())
2406
2407 i = 0
2408 for name, newval in (("year", 2),
2409 ("month", 3),
2410 ("day", 4),
2411 ("hour", 5),
2412 ("minute", 6),
2413 ("second", 7),
2414 ("microsecond", 8),
2415 ("tzinfo", zm200)):
2416 newargs = args[:]
2417 newargs[i] = newval
2418 expected = cls(*newargs)
2419 got = base.replace(**{name: newval})
2420 self.assertEqual(expected, got)
2421 i += 1
2422
2423 # Ensure we can get rid of a tzinfo.
2424 self.assertEqual(base.tzname(), "+100")
2425 base2 = base.replace(tzinfo=None)
2426 self.failUnless(base2.tzinfo is None)
2427 self.failUnless(base2.tzname() is None)
2428
2429 # Ensure we can add one.
2430 base3 = base2.replace(tzinfo=z100)
2431 self.assertEqual(base, base3)
2432 self.failUnless(base.tzinfo is base3.tzinfo)
2433
2434 # Out of bounds.
2435 base = cls(2000, 2, 29)
2436 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002437
Tim Peters80475bb2002-12-25 07:40:55 +00002438 def test_more_astimezone(self):
2439 # The inherited test_astimezone covered some trivial and error cases.
2440 fnone = FixedOffset(None, "None")
2441 f44m = FixedOffset(44, "44")
2442 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2443
2444 dt = self.theclass.now(tzinfo=f44m)
2445 self.failUnless(dt.tzinfo is f44m)
2446 # Replacing with degenerate tzinfo doesn't do any adjustment.
2447 for x in dt.astimezone(fnone), dt.astimezone(tz=fnone):
2448 self.failUnless(x.tzinfo is fnone)
2449 self.assertEqual(x.date(), dt.date())
2450 self.assertEqual(x.time(), dt.time())
2451 # Ditt with None tz.
2452 x = dt.astimezone(tz=None)
2453 self.failUnless(x.tzinfo is None)
2454 self.assertEqual(x.date(), dt.date())
2455 self.assertEqual(x.time(), dt.time())
2456 # Ditto replacing with same tzinfo.
2457 x = dt.astimezone(dt.tzinfo)
2458 self.failUnless(x.tzinfo is f44m)
2459 self.assertEqual(x.date(), dt.date())
2460 self.assertEqual(x.time(), dt.time())
2461
2462 # Replacing with different tzinfo does adjust.
2463 got = dt.astimezone(fm5h)
2464 self.failUnless(got.tzinfo is fm5h)
2465 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2466 expected = dt - dt.utcoffset() # in effect, convert to UTC
2467 expected += fm5h.utcoffset(dt) # and from there to local time
2468 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2469 self.assertEqual(got.date(), expected.date())
2470 self.assertEqual(got.time(), expected.time())
2471 self.assertEqual(got.timetz(), expected.timetz())
2472 self.failUnless(got.tzinfo is expected.tzinfo)
2473 self.assertEqual(got, expected)
2474
Tim Peters4c0db782002-12-26 05:01:19 +00002475 def test_aware_subtract(self):
2476 cls = self.theclass
2477
Tim Peters60c76e42002-12-27 00:41:11 +00002478 # Ensure that utcoffset() is ignored when the operands have the
2479 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002480 class OperandDependentOffset(tzinfo):
2481 def utcoffset(self, t):
2482 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002483 # d0 and d1 equal after adjustment
2484 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002485 else:
Tim Peters397301e2003-01-02 21:28:08 +00002486 # d2 off in the weeds
2487 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002488
2489 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2490 d0 = base.replace(minute=3)
2491 d1 = base.replace(minute=9)
2492 d2 = base.replace(minute=11)
2493 for x in d0, d1, d2:
2494 for y in d0, d1, d2:
2495 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002496 expected = timedelta(minutes=x.minute - y.minute)
2497 self.assertEqual(got, expected)
2498
2499 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2500 # ignored.
2501 base = cls(8, 9, 10, 11, 12, 13, 14)
2502 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2503 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2504 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2505 for x in d0, d1, d2:
2506 for y in d0, d1, d2:
2507 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002508 if (x is d0 or x is d1) and (y is d0 or y is d1):
2509 expected = timedelta(0)
2510 elif x is y is d2:
2511 expected = timedelta(0)
2512 elif x is d2:
2513 expected = timedelta(minutes=(11-59)-0)
2514 else:
2515 assert y is d2
2516 expected = timedelta(minutes=0-(11-59))
2517 self.assertEqual(got, expected)
2518
Tim Peters60c76e42002-12-27 00:41:11 +00002519 def test_mixed_compare(self):
2520 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002521 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002522 self.assertEqual(t1, t2)
2523 t2 = t2.replace(tzinfo=None)
2524 self.assertEqual(t1, t2)
2525 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2526 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002527 if CMP_BUG_FIXED:
2528 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2529 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002530
Tim Peters0bf60bd2003-01-08 20:40:01 +00002531 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002532 class Varies(tzinfo):
2533 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002534 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002535 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002536 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002537 return self.offset
2538
2539 v = Varies()
2540 t1 = t2.replace(tzinfo=v)
2541 t2 = t2.replace(tzinfo=v)
2542 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2543 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2544 self.assertEqual(t1, t2)
2545
2546 # But if they're not identical, it isn't ignored.
2547 t2 = t2.replace(tzinfo=Varies())
2548 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002549
Tim Peters621818b2002-12-29 23:44:49 +00002550# Pain to set up DST-aware tzinfo classes.
2551
2552def first_sunday_on_or_after(dt):
2553 days_to_go = 6 - dt.weekday()
2554 if days_to_go:
2555 dt += timedelta(days_to_go)
2556 return dt
2557
2558ZERO = timedelta(0)
2559HOUR = timedelta(hours=1)
2560DAY = timedelta(days=1)
2561# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2562DSTSTART = datetime(1, 4, 1, 2)
2563# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
2564# which is the first Sunday on or after Oct 25.
2565DSTEND = datetime(1, 10, 25, 2)
2566
2567class USTimeZone(tzinfo):
2568
2569 def __init__(self, hours, reprname, stdname, dstname):
2570 self.stdoffset = timedelta(hours=hours)
2571 self.reprname = reprname
2572 self.stdname = stdname
2573 self.dstname = dstname
2574
2575 def __repr__(self):
2576 return self.reprname
2577
2578 def tzname(self, dt):
2579 if self.dst(dt):
2580 return self.dstname
2581 else:
2582 return self.stdname
2583
2584 def utcoffset(self, dt):
2585 return self.stdoffset + self.dst(dt)
2586
2587 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002588 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002589 # An exception instead may be sensible here, in one or more of
2590 # the cases.
2591 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002592 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002593
2594 # Find first Sunday in April.
2595 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2596 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2597
2598 # Find last Sunday in October.
2599 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2600 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2601
Tim Peters621818b2002-12-29 23:44:49 +00002602 # Can't compare naive to aware objects, so strip the timezone from
2603 # dt first.
2604 if start <= dt.astimezone(None) < end:
2605 return HOUR
2606 else:
2607 return ZERO
2608
Tim Peters521fc152002-12-31 17:36:56 +00002609Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2610Central = USTimeZone(-6, "Central", "CST", "CDT")
2611Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2612Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002613utc_real = FixedOffset(0, "UTC", 0)
2614# For better test coverage, we want another flavor of UTC that's west of
2615# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002616utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002617
2618class TestTimezoneConversions(unittest.TestCase):
2619 # The DST switch times for 2002, in local time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002620 dston = datetime(2002, 4, 7, 2)
2621 dstoff = datetime(2002, 10, 27, 2)
Tim Peters621818b2002-12-29 23:44:49 +00002622
Tim Peters0bf60bd2003-01-08 20:40:01 +00002623 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002624
Tim Peters521fc152002-12-31 17:36:56 +00002625 # Check a time that's inside DST.
2626 def checkinside(self, dt, tz, utc, dston, dstoff):
2627 self.assertEqual(dt.dst(), HOUR)
2628
2629 # Conversion to our own timezone is always an identity.
2630 self.assertEqual(dt.astimezone(tz), dt)
2631 # Conversion to None is always the same as stripping tzinfo.
2632 self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
2633
2634 asutc = dt.astimezone(utc)
2635 there_and_back = asutc.astimezone(tz)
2636
2637 # Conversion to UTC and back isn't always an identity here,
2638 # because there are redundant spellings (in local time) of
2639 # UTC time when DST begins: the clock jumps from 1:59:59
2640 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2641 # make sense then. The classes above treat 2:MM:SS as
2642 # daylight time then (it's "after 2am"), really an alias
2643 # for 1:MM:SS standard time. The latter form is what
2644 # conversion back from UTC produces.
2645 if dt.date() == dston.date() and dt.hour == 2:
2646 # We're in the redundant hour, and coming back from
2647 # UTC gives the 1:MM:SS standard-time spelling.
2648 self.assertEqual(there_and_back + HOUR, dt)
2649 # Although during was considered to be in daylight
2650 # time, there_and_back is not.
2651 self.assertEqual(there_and_back.dst(), ZERO)
2652 # They're the same times in UTC.
2653 self.assertEqual(there_and_back.astimezone(utc),
2654 dt.astimezone(utc))
2655 else:
2656 # We're not in the redundant hour.
2657 self.assertEqual(dt, there_and_back)
2658
2659 # Because we have a redundant spelling when DST begins,
2660 # there is (unforunately) an hour when DST ends that can't
2661 # be spelled at all in local time. When DST ends, the
2662 # clock jumps from 1:59:59 back to 1:00:00 again. The
2663 # hour beginning then has no spelling in local time:
2664 # 1:MM:SS is taken to be daylight time, and 2:MM:SS as
2665 # standard time. The hour 1:MM:SS standard time ==
2666 # 2:MM:SS daylight time can't be expressed in local time.
Tim Petersadf64202003-01-04 06:03:15 +00002667 # Nevertheless, we want conversion back from UTC to mimic
2668 # the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002669 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002670 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters521fc152002-12-31 17:36:56 +00002671 if dt.date() == dstoff.date() and dt.hour == 1:
2672 # We're in the hour before DST ends. The hour after
Tim Petersadf64202003-01-04 06:03:15 +00002673 # is ineffable. We want the conversion back to repeat 1:MM.
2674 expected_diff = ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002675 else:
Tim Petersadf64202003-01-04 06:03:15 +00002676 expected_diff = HOUR
2677 self.assertEqual(nexthour_tz - dt, expected_diff)
Tim Peters521fc152002-12-31 17:36:56 +00002678
2679 # Check a time that's outside DST.
2680 def checkoutside(self, dt, tz, utc):
2681 self.assertEqual(dt.dst(), ZERO)
2682
2683 # Conversion to our own timezone is always an identity.
2684 self.assertEqual(dt.astimezone(tz), dt)
2685 # Conversion to None is always the same as stripping tzinfo.
2686 self.assertEqual(dt.astimezone(None), dt.replace(tzinfo=None))
2687
Tim Peters1024bf82002-12-30 17:09:40 +00002688 def convert_between_tz_and_utc(self, tz, utc):
2689 dston = self.dston.replace(tzinfo=tz)
2690 dstoff = self.dstoff.replace(tzinfo=tz)
2691 for delta in (timedelta(weeks=13),
2692 DAY,
2693 HOUR,
2694 timedelta(minutes=1),
2695 timedelta(microseconds=1)):
2696
Tim Peters521fc152002-12-31 17:36:56 +00002697 self.checkinside(dston, tz, utc, dston, dstoff)
2698 for during in dston + delta, dstoff - delta:
2699 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002700
Tim Peters521fc152002-12-31 17:36:56 +00002701 self.checkoutside(dstoff, tz, utc)
2702 for outside in dston - delta, dstoff + delta:
2703 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002704
Tim Peters621818b2002-12-29 23:44:49 +00002705 def test_easy(self):
2706 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002707 self.convert_between_tz_and_utc(Eastern, utc_real)
2708 self.convert_between_tz_and_utc(Pacific, utc_real)
2709 self.convert_between_tz_and_utc(Eastern, utc_fake)
2710 self.convert_between_tz_and_utc(Pacific, utc_fake)
2711 # The next is really dancing near the edge. It works because
2712 # Pacific and Eastern are far enough apart that their "problem
2713 # hours" don't overlap.
2714 self.convert_between_tz_and_utc(Eastern, Pacific)
2715 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002716 # OTOH, these fail! Don't enable them. The difficulty is that
2717 # the edge case tests assume that every hour is representable in
2718 # the "utc" class. This is always true for a fixed-offset tzinfo
2719 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2720 # For these adjacent DST-aware time zones, the range of time offsets
2721 # tested ends up creating hours in the one that aren't representable
2722 # in the other. For the same reason, we would see failures in the
2723 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2724 # offset deltas in convert_between_tz_and_utc().
2725 #
2726 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2727 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002728
Tim Petersf3615152003-01-01 21:51:37 +00002729 def test_tricky(self):
2730 # 22:00 on day before daylight starts.
2731 fourback = self.dston - timedelta(hours=4)
2732 ninewest = FixedOffset(-9*60, "-0900", 0)
2733 fourback = fourback.astimezone(ninewest)
2734 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2735 # 2", we should get the 3 spelling.
2736 # If we plug 22:00 the day before into Eastern, it "looks like std
2737 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2738 # to 22:00 lands on 2:00, which makes no sense in local time (the
2739 # local clock jumps from 1 to 3). The point here is to make sure we
2740 # get the 3 spelling.
2741 expected = self.dston.replace(hour=3)
2742 got = fourback.astimezone(Eastern).astimezone(None)
2743 self.assertEqual(expected, got)
2744
2745 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2746 # case we want the 1:00 spelling.
2747 sixutc = self.dston.replace(hour=6).astimezone(utc_real)
2748 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2749 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2750 # spelling.
2751 expected = self.dston.replace(hour=1)
2752 got = sixutc.astimezone(Eastern).astimezone(None)
2753 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002754
Tim Petersadf64202003-01-04 06:03:15 +00002755 # Now on the day DST ends, we want "repeat an hour" behavior.
2756 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2757 # EST 23:MM 0:MM 1:MM 2:MM
2758 # EDT 0:MM 1:MM 2:MM 3:MM
2759 # wall 0:MM 1:MM 1:MM 2:MM against these
2760 for utc in utc_real, utc_fake:
2761 for tz in Eastern, Pacific:
2762 first_std_hour = self.dstoff - timedelta(hours=3) # 23:MM
2763 # Convert that to UTC.
2764 first_std_hour -= tz.utcoffset(None)
2765 # Adjust for possibly fake UTC.
2766 asutc = first_std_hour + utc.utcoffset(None)
2767 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2768 # tz=Eastern.
2769 asutcbase = asutc.replace(tzinfo=utc)
2770 for tzhour in (0, 1, 1, 2):
2771 expectedbase = self.dstoff.replace(hour=tzhour)
2772 for minute in 0, 30, 59:
2773 expected = expectedbase.replace(minute=minute)
2774 asutc = asutcbase.replace(minute=minute)
2775 astz = asutc.astimezone(tz)
2776 self.assertEqual(astz.replace(tzinfo=None), expected)
2777 asutcbase += HOUR
2778
2779
Tim Peters710fb152003-01-02 19:35:54 +00002780 def test_bogus_dst(self):
2781 class ok(tzinfo):
2782 def utcoffset(self, dt): return HOUR
2783 def dst(self, dt): return HOUR
2784
2785 now = self.theclass.now().replace(tzinfo=utc_real)
2786 # Doesn't blow up.
2787 now.astimezone(ok())
2788
2789 # Does blow up.
2790 class notok(ok):
2791 def dst(self, dt): return None
2792 self.assertRaises(ValueError, now.astimezone, notok())
2793
2794
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002795def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002796 allsuites = [unittest.makeSuite(klass, 'test')
2797 for klass in (TestModule,
2798 TestTZInfo,
2799 TestTimeDelta,
2800 TestDateOnly,
2801 TestDate,
2802 TestDateTime,
2803 TestTime,
2804 TestTimeTZ,
2805 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002806 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002807 )
2808 ]
2809 return unittest.TestSuite(allsuites)
2810
2811def test_main():
2812 import gc
2813 import sys
2814
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002815 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002816 lastrc = None
2817 while True:
2818 test_support.run_suite(thesuite)
2819 if 1: # change to 0, under a debug build, for some leak detection
2820 break
2821 gc.collect()
2822 if gc.garbage:
2823 raise SystemError("gc.garbage not empty after test run: %r" %
2824 gc.garbage)
2825 if hasattr(sys, 'gettotalrefcount'):
2826 thisrc = sys.gettotalrefcount()
2827 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2828 if lastrc:
2829 print >> sys.stderr, 'delta:', thisrc - lastrc
2830 else:
2831 print >> sys.stderr
2832 lastrc = thisrc
2833
2834if __name__ == "__main__":
2835 test_main()