blob: 995b6a03f16f34d5c4f0a6ab0c8423f4b63cd3e1 [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
Tim Peters8d81a012003-01-24 22:36:34 +0000883 def test_mixed_compare(self):
884 our = self.theclass(2000, 4, 5)
885 self.assertRaises(TypeError, cmp, our, 1)
886 self.assertRaises(TypeError, cmp, 1, our)
887
888 class AnotherDateTimeClass(object):
889 def __cmp__(self, other):
890 # Return "equal" so calling this can't be confused with
891 # compare-by-address (which never says "equal" for distinct
892 # objects).
893 return 0
894
895 # This still errors, because date and datetime comparison raise
896 # TypeError instead of NotImplemented when they don't know what to
897 # do, in order to stop comparison from falling back to the default
898 # compare-by-address.
899 their = AnotherDateTimeClass()
900 self.assertRaises(TypeError, cmp, our, their)
901 # Oops: The next stab raises TypeError in the C implementation,
902 # but not in the Python implementation of datetime. The difference
903 # is due to that the Python implementation defines __cmp__ but
904 # the C implementation defines tp_richcompare. This is more pain
905 # to fix than it's worth, so commenting out the test.
906 # self.assertEqual(cmp(their, our), 0)
907
908 # But date and datetime comparison return NotImplemented instead if the
909 # other object has a timetuple attr. This gives the other object a
910 # chance to do the comparison.
911 class Comparable(AnotherDateTimeClass):
912 def timetuple(self):
913 return ()
914
915 their = Comparable()
916 self.assertEqual(cmp(our, their), 0)
917 self.assertEqual(cmp(their, our), 0)
918 self.failUnless(our == their)
919 self.failUnless(their == our)
920
Tim Peters2a799bf2002-12-16 20:18:38 +0000921 def test_bool(self):
922 # All dates are considered true.
923 self.failUnless(self.theclass.min)
924 self.failUnless(self.theclass.max)
925
Tim Petersd6844152002-12-22 20:58:42 +0000926 def test_srftime_out_of_range(self):
927 # For nasty technical reasons, we can't handle years before 1900.
928 cls = self.theclass
929 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
930 for y in 1, 49, 51, 99, 100, 1000, 1899:
931 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000932
933 def test_replace(self):
934 cls = self.theclass
935 args = [1, 2, 3]
936 base = cls(*args)
937 self.assertEqual(base, base.replace())
938
939 i = 0
940 for name, newval in (("year", 2),
941 ("month", 3),
942 ("day", 4)):
943 newargs = args[:]
944 newargs[i] = newval
945 expected = cls(*newargs)
946 got = base.replace(**{name: newval})
947 self.assertEqual(expected, got)
948 i += 1
949
950 # Out of bounds.
951 base = cls(2000, 2, 29)
952 self.assertRaises(ValueError, base.replace, year=2001)
953
Tim Peters2a799bf2002-12-16 20:18:38 +0000954#############################################################################
955# datetime tests
956
957class TestDateTime(TestDate):
958
959 theclass = datetime
960
961 def test_basic_attributes(self):
962 dt = self.theclass(2002, 3, 1, 12, 0)
963 self.assertEqual(dt.year, 2002)
964 self.assertEqual(dt.month, 3)
965 self.assertEqual(dt.day, 1)
966 self.assertEqual(dt.hour, 12)
967 self.assertEqual(dt.minute, 0)
968 self.assertEqual(dt.second, 0)
969 self.assertEqual(dt.microsecond, 0)
970
971 def test_basic_attributes_nonzero(self):
972 # Make sure all attributes are non-zero so bugs in
973 # bit-shifting access show up.
974 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
975 self.assertEqual(dt.year, 2002)
976 self.assertEqual(dt.month, 3)
977 self.assertEqual(dt.day, 1)
978 self.assertEqual(dt.hour, 12)
979 self.assertEqual(dt.minute, 59)
980 self.assertEqual(dt.second, 59)
981 self.assertEqual(dt.microsecond, 8000)
982
983 def test_roundtrip(self):
984 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
985 self.theclass.now()):
986 # Verify dt -> string -> datetime identity.
987 s = repr(dt)
988 self.failUnless(s.startswith('datetime.'))
989 s = s[9:]
990 dt2 = eval(s)
991 self.assertEqual(dt, dt2)
992
993 # Verify identity via reconstructing from pieces.
994 dt2 = self.theclass(dt.year, dt.month, dt.day,
995 dt.hour, dt.minute, dt.second,
996 dt.microsecond)
997 self.assertEqual(dt, dt2)
998
999 def test_isoformat(self):
1000 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1001 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1002 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1003 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1004 # str is ISO format with the separator forced to a blank.
1005 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1006
1007 t = self.theclass(2, 3, 2)
1008 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1009 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1010 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1011 # str is ISO format with the separator forced to a blank.
1012 self.assertEqual(str(t), "0002-03-02 00:00:00")
1013
1014 def test_more_ctime(self):
1015 # Test fields that TestDate doesn't touch.
1016 import time
1017
1018 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1019 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1020 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1021 # out. The difference is that t.ctime() produces " 2" for the day,
1022 # but platform ctime() produces "02" for the day. According to
1023 # C99, t.ctime() is correct here.
1024 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1025
1026 # So test a case where that difference doesn't matter.
1027 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1028 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1029
1030 def test_tz_independent_comparing(self):
1031 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1032 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1033 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1034 self.assertEqual(dt1, dt3)
1035 self.assert_(dt2 > dt3)
1036
1037 # Make sure comparison doesn't forget microseconds, and isn't done
1038 # via comparing a float timestamp (an IEEE double doesn't have enough
1039 # precision to span microsecond resolution across years 1 thru 9999,
1040 # so comparing via timestamp necessarily calls some distinct values
1041 # equal).
1042 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1043 us = timedelta(microseconds=1)
1044 dt2 = dt1 + us
1045 self.assertEqual(dt2 - dt1, us)
1046 self.assert_(dt1 < dt2)
1047
1048 def test_bad_constructor_arguments(self):
1049 # bad years
1050 self.theclass(MINYEAR, 1, 1) # no exception
1051 self.theclass(MAXYEAR, 1, 1) # no exception
1052 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1053 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1054 # bad months
1055 self.theclass(2000, 1, 1) # no exception
1056 self.theclass(2000, 12, 1) # no exception
1057 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1058 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1059 # bad days
1060 self.theclass(2000, 2, 29) # no exception
1061 self.theclass(2004, 2, 29) # no exception
1062 self.theclass(2400, 2, 29) # no exception
1063 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1064 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1065 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1066 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1067 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1068 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1069 # bad hours
1070 self.theclass(2000, 1, 31, 0) # no exception
1071 self.theclass(2000, 1, 31, 23) # no exception
1072 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1073 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1074 # bad minutes
1075 self.theclass(2000, 1, 31, 23, 0) # no exception
1076 self.theclass(2000, 1, 31, 23, 59) # no exception
1077 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1078 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1079 # bad seconds
1080 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1081 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1082 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1083 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1084 # bad microseconds
1085 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1086 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1087 self.assertRaises(ValueError, self.theclass,
1088 2000, 1, 31, 23, 59, 59, -1)
1089 self.assertRaises(ValueError, self.theclass,
1090 2000, 1, 31, 23, 59, 59,
1091 1000000)
1092
1093 def test_hash_equality(self):
1094 d = self.theclass(2000, 12, 31, 23, 30, 17)
1095 e = self.theclass(2000, 12, 31, 23, 30, 17)
1096 self.assertEqual(d, e)
1097 self.assertEqual(hash(d), hash(e))
1098
1099 dic = {d: 1}
1100 dic[e] = 2
1101 self.assertEqual(len(dic), 1)
1102 self.assertEqual(dic[d], 2)
1103 self.assertEqual(dic[e], 2)
1104
1105 d = self.theclass(2001, 1, 1, 0, 5, 17)
1106 e = self.theclass(2001, 1, 1, 0, 5, 17)
1107 self.assertEqual(d, e)
1108 self.assertEqual(hash(d), hash(e))
1109
1110 dic = {d: 1}
1111 dic[e] = 2
1112 self.assertEqual(len(dic), 1)
1113 self.assertEqual(dic[d], 2)
1114 self.assertEqual(dic[e], 2)
1115
1116 def test_computations(self):
1117 a = self.theclass(2002, 1, 31)
1118 b = self.theclass(1956, 1, 31)
1119 diff = a-b
1120 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1121 self.assertEqual(diff.seconds, 0)
1122 self.assertEqual(diff.microseconds, 0)
1123 a = self.theclass(2002, 3, 2, 17, 6)
1124 millisec = timedelta(0, 0, 1000)
1125 hour = timedelta(0, 3600)
1126 day = timedelta(1)
1127 week = timedelta(7)
1128 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1129 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1130 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1131 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1132 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1133 self.assertEqual(a - hour, a + -hour)
1134 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1135 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1136 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1137 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1138 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1139 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1140 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1141 self.assertEqual((a + week) - a, week)
1142 self.assertEqual((a + day) - a, day)
1143 self.assertEqual((a + hour) - a, hour)
1144 self.assertEqual((a + millisec) - a, millisec)
1145 self.assertEqual((a - week) - a, -week)
1146 self.assertEqual((a - day) - a, -day)
1147 self.assertEqual((a - hour) - a, -hour)
1148 self.assertEqual((a - millisec) - a, -millisec)
1149 self.assertEqual(a - (a + week), -week)
1150 self.assertEqual(a - (a + day), -day)
1151 self.assertEqual(a - (a + hour), -hour)
1152 self.assertEqual(a - (a + millisec), -millisec)
1153 self.assertEqual(a - (a - week), week)
1154 self.assertEqual(a - (a - day), day)
1155 self.assertEqual(a - (a - hour), hour)
1156 self.assertEqual(a - (a - millisec), millisec)
1157 self.assertEqual(a + (week + day + hour + millisec),
1158 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1159 self.assertEqual(a + (week + day + hour + millisec),
1160 (((a + week) + day) + hour) + millisec)
1161 self.assertEqual(a - (week + day + hour + millisec),
1162 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1163 self.assertEqual(a - (week + day + hour + millisec),
1164 (((a - week) - day) - hour) - millisec)
1165 # Add/sub ints, longs, floats should be illegal
1166 for i in 1, 1L, 1.0:
1167 self.assertRaises(TypeError, lambda: a+i)
1168 self.assertRaises(TypeError, lambda: a-i)
1169 self.assertRaises(TypeError, lambda: i+a)
1170 self.assertRaises(TypeError, lambda: i-a)
1171
1172 # delta - datetime is senseless.
1173 self.assertRaises(TypeError, lambda: day - a)
1174 # mixing datetime and (delta or datetime) via * or // is senseless
1175 self.assertRaises(TypeError, lambda: day * a)
1176 self.assertRaises(TypeError, lambda: a * day)
1177 self.assertRaises(TypeError, lambda: day // a)
1178 self.assertRaises(TypeError, lambda: a // day)
1179 self.assertRaises(TypeError, lambda: a * a)
1180 self.assertRaises(TypeError, lambda: a // a)
1181 # datetime + datetime is senseless
1182 self.assertRaises(TypeError, lambda: a + a)
1183
1184 def test_pickling(self):
1185 import pickle, cPickle
1186 args = 6, 7, 23, 20, 59, 1, 64**2
1187 orig = self.theclass(*args)
1188 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001189 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001190 derived = self.theclass(1, 1, 1)
1191 derived.__setstate__(state)
1192 self.assertEqual(orig, derived)
1193 for pickler in pickle, cPickle:
1194 for binary in 0, 1:
1195 green = pickler.dumps(orig, binary)
1196 derived = pickler.loads(green)
1197 self.assertEqual(orig, derived)
1198
1199 def test_more_compare(self):
1200 # The test_compare() inherited from TestDate covers the error cases.
1201 # We just want to test lexicographic ordering on the members datetime
1202 # has that date lacks.
1203 args = [2000, 11, 29, 20, 58, 16, 999998]
1204 t1 = self.theclass(*args)
1205 t2 = self.theclass(*args)
1206 self.failUnless(t1 == t2)
1207 self.failUnless(t1 <= t2)
1208 self.failUnless(t1 >= t2)
1209 self.failUnless(not t1 != t2)
1210 self.failUnless(not t1 < t2)
1211 self.failUnless(not t1 > t2)
1212 self.assertEqual(cmp(t1, t2), 0)
1213 self.assertEqual(cmp(t2, t1), 0)
1214
1215 for i in range(len(args)):
1216 newargs = args[:]
1217 newargs[i] = args[i] + 1
1218 t2 = self.theclass(*newargs) # this is larger than t1
1219 self.failUnless(t1 < t2)
1220 self.failUnless(t2 > t1)
1221 self.failUnless(t1 <= t2)
1222 self.failUnless(t2 >= t1)
1223 self.failUnless(t1 != t2)
1224 self.failUnless(t2 != t1)
1225 self.failUnless(not t1 == t2)
1226 self.failUnless(not t2 == t1)
1227 self.failUnless(not t1 > t2)
1228 self.failUnless(not t2 < t1)
1229 self.failUnless(not t1 >= t2)
1230 self.failUnless(not t2 <= t1)
1231 self.assertEqual(cmp(t1, t2), -1)
1232 self.assertEqual(cmp(t2, t1), 1)
1233
1234
1235 # A helper for timestamp constructor tests.
1236 def verify_field_equality(self, expected, got):
1237 self.assertEqual(expected.tm_year, got.year)
1238 self.assertEqual(expected.tm_mon, got.month)
1239 self.assertEqual(expected.tm_mday, got.day)
1240 self.assertEqual(expected.tm_hour, got.hour)
1241 self.assertEqual(expected.tm_min, got.minute)
1242 self.assertEqual(expected.tm_sec, got.second)
1243
1244 def test_fromtimestamp(self):
1245 import time
1246
1247 ts = time.time()
1248 expected = time.localtime(ts)
1249 got = self.theclass.fromtimestamp(ts)
1250 self.verify_field_equality(expected, got)
1251
1252 def test_utcfromtimestamp(self):
1253 import time
1254
1255 ts = time.time()
1256 expected = time.gmtime(ts)
1257 got = self.theclass.utcfromtimestamp(ts)
1258 self.verify_field_equality(expected, got)
1259
1260 def test_utcnow(self):
1261 import time
1262
1263 # Call it a success if utcnow() and utcfromtimestamp() are within
1264 # a second of each other.
1265 tolerance = timedelta(seconds=1)
1266 for dummy in range(3):
1267 from_now = self.theclass.utcnow()
1268 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1269 if abs(from_timestamp - from_now) <= tolerance:
1270 break
1271 # Else try again a few times.
1272 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1273
1274 def test_more_timetuple(self):
1275 # This tests fields beyond those tested by the TestDate.test_timetuple.
1276 t = self.theclass(2004, 12, 31, 6, 22, 33)
1277 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1278 self.assertEqual(t.timetuple(),
1279 (t.year, t.month, t.day,
1280 t.hour, t.minute, t.second,
1281 t.weekday(),
1282 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1283 -1))
1284 tt = t.timetuple()
1285 self.assertEqual(tt.tm_year, t.year)
1286 self.assertEqual(tt.tm_mon, t.month)
1287 self.assertEqual(tt.tm_mday, t.day)
1288 self.assertEqual(tt.tm_hour, t.hour)
1289 self.assertEqual(tt.tm_min, t.minute)
1290 self.assertEqual(tt.tm_sec, t.second)
1291 self.assertEqual(tt.tm_wday, t.weekday())
1292 self.assertEqual(tt.tm_yday, t.toordinal() -
1293 date(t.year, 1, 1).toordinal() + 1)
1294 self.assertEqual(tt.tm_isdst, -1)
1295
1296 def test_more_strftime(self):
1297 # This tests fields beyond those tested by the TestDate.test_strftime.
1298 t = self.theclass(2004, 12, 31, 6, 22, 33)
1299 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1300 "12 31 04 33 22 06 366")
1301
1302 def test_extract(self):
1303 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1304 self.assertEqual(dt.date(), date(2002, 3, 4))
1305 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1306
1307 def test_combine(self):
1308 d = date(2002, 3, 4)
1309 t = time(18, 45, 3, 1234)
1310 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1311 combine = self.theclass.combine
1312 dt = combine(d, t)
1313 self.assertEqual(dt, expected)
1314
1315 dt = combine(time=t, date=d)
1316 self.assertEqual(dt, expected)
1317
1318 self.assertEqual(d, dt.date())
1319 self.assertEqual(t, dt.time())
1320 self.assertEqual(dt, combine(dt.date(), dt.time()))
1321
1322 self.assertRaises(TypeError, combine) # need an arg
1323 self.assertRaises(TypeError, combine, d) # need two args
1324 self.assertRaises(TypeError, combine, t, d) # args reversed
1325 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1326 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1327
Tim Peters12bf3392002-12-24 05:41:27 +00001328 def test_replace(self):
1329 cls = self.theclass
1330 args = [1, 2, 3, 4, 5, 6, 7]
1331 base = cls(*args)
1332 self.assertEqual(base, base.replace())
1333
1334 i = 0
1335 for name, newval in (("year", 2),
1336 ("month", 3),
1337 ("day", 4),
1338 ("hour", 5),
1339 ("minute", 6),
1340 ("second", 7),
1341 ("microsecond", 8)):
1342 newargs = args[:]
1343 newargs[i] = newval
1344 expected = cls(*newargs)
1345 got = base.replace(**{name: newval})
1346 self.assertEqual(expected, got)
1347 i += 1
1348
1349 # Out of bounds.
1350 base = cls(2000, 2, 29)
1351 self.assertRaises(ValueError, base.replace, year=2001)
1352
Tim Peters80475bb2002-12-25 07:40:55 +00001353 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001354 # Pretty boring! The TZ test is more interesting here. astimezone()
1355 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001356 dt = self.theclass.now()
1357 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001358 self.assertRaises(TypeError, dt.astimezone) # not enough args
1359 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1360 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001361 self.assertRaises(ValueError, dt.astimezone, f) # naive
1362 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001363
Tim Peters52dcce22003-01-23 16:36:11 +00001364 class Bogus(tzinfo):
1365 def utcoffset(self, dt): return None
1366 def dst(self, dt): return timedelta(0)
1367 bog = Bogus()
1368 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1369
1370 class AlsoBogus(tzinfo):
1371 def utcoffset(self, dt): return timedelta(0)
1372 def dst(self, dt): return None
1373 alsobog = AlsoBogus()
1374 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001375
Tim Peters2a799bf2002-12-16 20:18:38 +00001376class TestTime(unittest.TestCase):
1377
1378 theclass = time
1379
1380 def test_basic_attributes(self):
1381 t = self.theclass(12, 0)
1382 self.assertEqual(t.hour, 12)
1383 self.assertEqual(t.minute, 0)
1384 self.assertEqual(t.second, 0)
1385 self.assertEqual(t.microsecond, 0)
1386
1387 def test_basic_attributes_nonzero(self):
1388 # Make sure all attributes are non-zero so bugs in
1389 # bit-shifting access show up.
1390 t = self.theclass(12, 59, 59, 8000)
1391 self.assertEqual(t.hour, 12)
1392 self.assertEqual(t.minute, 59)
1393 self.assertEqual(t.second, 59)
1394 self.assertEqual(t.microsecond, 8000)
1395
1396 def test_roundtrip(self):
1397 t = self.theclass(1, 2, 3, 4)
1398
1399 # Verify t -> string -> time identity.
1400 s = repr(t)
1401 self.failUnless(s.startswith('datetime.'))
1402 s = s[9:]
1403 t2 = eval(s)
1404 self.assertEqual(t, t2)
1405
1406 # Verify identity via reconstructing from pieces.
1407 t2 = self.theclass(t.hour, t.minute, t.second,
1408 t.microsecond)
1409 self.assertEqual(t, t2)
1410
1411 def test_comparing(self):
1412 args = [1, 2, 3, 4]
1413 t1 = self.theclass(*args)
1414 t2 = self.theclass(*args)
1415 self.failUnless(t1 == t2)
1416 self.failUnless(t1 <= t2)
1417 self.failUnless(t1 >= t2)
1418 self.failUnless(not t1 != t2)
1419 self.failUnless(not t1 < t2)
1420 self.failUnless(not t1 > t2)
1421 self.assertEqual(cmp(t1, t2), 0)
1422 self.assertEqual(cmp(t2, t1), 0)
1423
1424 for i in range(len(args)):
1425 newargs = args[:]
1426 newargs[i] = args[i] + 1
1427 t2 = self.theclass(*newargs) # this is larger than t1
1428 self.failUnless(t1 < t2)
1429 self.failUnless(t2 > t1)
1430 self.failUnless(t1 <= t2)
1431 self.failUnless(t2 >= t1)
1432 self.failUnless(t1 != t2)
1433 self.failUnless(t2 != t1)
1434 self.failUnless(not t1 == t2)
1435 self.failUnless(not t2 == t1)
1436 self.failUnless(not t1 > t2)
1437 self.failUnless(not t2 < t1)
1438 self.failUnless(not t1 >= t2)
1439 self.failUnless(not t2 <= t1)
1440 self.assertEqual(cmp(t1, t2), -1)
1441 self.assertEqual(cmp(t2, t1), 1)
1442
Tim Peters0bf60bd2003-01-08 20:40:01 +00001443 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1444 if CMP_BUG_FIXED:
1445 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1446 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001447 self.assertRaises(TypeError, lambda: t1 == badarg)
1448 self.assertRaises(TypeError, lambda: t1 != badarg)
1449 self.assertRaises(TypeError, lambda: t1 <= badarg)
1450 self.assertRaises(TypeError, lambda: t1 < badarg)
1451 self.assertRaises(TypeError, lambda: t1 > badarg)
1452 self.assertRaises(TypeError, lambda: t1 >= badarg)
1453 self.assertRaises(TypeError, lambda: badarg == t1)
1454 self.assertRaises(TypeError, lambda: badarg != t1)
1455 self.assertRaises(TypeError, lambda: badarg <= t1)
1456 self.assertRaises(TypeError, lambda: badarg < t1)
1457 self.assertRaises(TypeError, lambda: badarg > t1)
1458 self.assertRaises(TypeError, lambda: badarg >= t1)
1459
1460 def test_bad_constructor_arguments(self):
1461 # bad hours
1462 self.theclass(0, 0) # no exception
1463 self.theclass(23, 0) # no exception
1464 self.assertRaises(ValueError, self.theclass, -1, 0)
1465 self.assertRaises(ValueError, self.theclass, 24, 0)
1466 # bad minutes
1467 self.theclass(23, 0) # no exception
1468 self.theclass(23, 59) # no exception
1469 self.assertRaises(ValueError, self.theclass, 23, -1)
1470 self.assertRaises(ValueError, self.theclass, 23, 60)
1471 # bad seconds
1472 self.theclass(23, 59, 0) # no exception
1473 self.theclass(23, 59, 59) # no exception
1474 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1475 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1476 # bad microseconds
1477 self.theclass(23, 59, 59, 0) # no exception
1478 self.theclass(23, 59, 59, 999999) # no exception
1479 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1480 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1481
1482 def test_hash_equality(self):
1483 d = self.theclass(23, 30, 17)
1484 e = self.theclass(23, 30, 17)
1485 self.assertEqual(d, e)
1486 self.assertEqual(hash(d), hash(e))
1487
1488 dic = {d: 1}
1489 dic[e] = 2
1490 self.assertEqual(len(dic), 1)
1491 self.assertEqual(dic[d], 2)
1492 self.assertEqual(dic[e], 2)
1493
1494 d = self.theclass(0, 5, 17)
1495 e = self.theclass(0, 5, 17)
1496 self.assertEqual(d, e)
1497 self.assertEqual(hash(d), hash(e))
1498
1499 dic = {d: 1}
1500 dic[e] = 2
1501 self.assertEqual(len(dic), 1)
1502 self.assertEqual(dic[d], 2)
1503 self.assertEqual(dic[e], 2)
1504
1505 def test_isoformat(self):
1506 t = self.theclass(4, 5, 1, 123)
1507 self.assertEqual(t.isoformat(), "04:05:01.000123")
1508 self.assertEqual(t.isoformat(), str(t))
1509
1510 t = self.theclass()
1511 self.assertEqual(t.isoformat(), "00:00:00")
1512 self.assertEqual(t.isoformat(), str(t))
1513
1514 t = self.theclass(microsecond=1)
1515 self.assertEqual(t.isoformat(), "00:00:00.000001")
1516 self.assertEqual(t.isoformat(), str(t))
1517
1518 t = self.theclass(microsecond=10)
1519 self.assertEqual(t.isoformat(), "00:00:00.000010")
1520 self.assertEqual(t.isoformat(), str(t))
1521
1522 t = self.theclass(microsecond=100)
1523 self.assertEqual(t.isoformat(), "00:00:00.000100")
1524 self.assertEqual(t.isoformat(), str(t))
1525
1526 t = self.theclass(microsecond=1000)
1527 self.assertEqual(t.isoformat(), "00:00:00.001000")
1528 self.assertEqual(t.isoformat(), str(t))
1529
1530 t = self.theclass(microsecond=10000)
1531 self.assertEqual(t.isoformat(), "00:00:00.010000")
1532 self.assertEqual(t.isoformat(), str(t))
1533
1534 t = self.theclass(microsecond=100000)
1535 self.assertEqual(t.isoformat(), "00:00:00.100000")
1536 self.assertEqual(t.isoformat(), str(t))
1537
1538 def test_strftime(self):
1539 t = self.theclass(1, 2, 3, 4)
1540 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1541 # A naive object replaces %z and %Z with empty strings.
1542 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1543
1544 def test_str(self):
1545 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1546 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1547 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1548 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1549 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1550
1551 def test_repr(self):
1552 name = 'datetime.' + self.theclass.__name__
1553 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1554 "%s(1, 2, 3, 4)" % name)
1555 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1556 "%s(10, 2, 3, 4000)" % name)
1557 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1558 "%s(0, 2, 3, 400000)" % name)
1559 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1560 "%s(12, 2, 3)" % name)
1561 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1562 "%s(23, 15)" % name)
1563
1564 def test_resolution_info(self):
1565 self.assert_(isinstance(self.theclass.min, self.theclass))
1566 self.assert_(isinstance(self.theclass.max, self.theclass))
1567 self.assert_(isinstance(self.theclass.resolution, timedelta))
1568 self.assert_(self.theclass.max > self.theclass.min)
1569
1570 def test_pickling(self):
1571 import pickle, cPickle
1572 args = 20, 59, 16, 64**2
1573 orig = self.theclass(*args)
1574 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001575 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001576 derived = self.theclass()
1577 derived.__setstate__(state)
1578 self.assertEqual(orig, derived)
1579 for pickler in pickle, cPickle:
1580 for binary in 0, 1:
1581 green = pickler.dumps(orig, binary)
1582 derived = pickler.loads(green)
1583 self.assertEqual(orig, derived)
1584
1585 def test_bool(self):
1586 cls = self.theclass
1587 self.failUnless(cls(1))
1588 self.failUnless(cls(0, 1))
1589 self.failUnless(cls(0, 0, 1))
1590 self.failUnless(cls(0, 0, 0, 1))
1591 self.failUnless(not cls(0))
1592 self.failUnless(not cls())
1593
Tim Peters12bf3392002-12-24 05:41:27 +00001594 def test_replace(self):
1595 cls = self.theclass
1596 args = [1, 2, 3, 4]
1597 base = cls(*args)
1598 self.assertEqual(base, base.replace())
1599
1600 i = 0
1601 for name, newval in (("hour", 5),
1602 ("minute", 6),
1603 ("second", 7),
1604 ("microsecond", 8)):
1605 newargs = args[:]
1606 newargs[i] = newval
1607 expected = cls(*newargs)
1608 got = base.replace(**{name: newval})
1609 self.assertEqual(expected, got)
1610 i += 1
1611
1612 # Out of bounds.
1613 base = cls(1)
1614 self.assertRaises(ValueError, base.replace, hour=24)
1615 self.assertRaises(ValueError, base.replace, minute=-1)
1616 self.assertRaises(ValueError, base.replace, second=100)
1617 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1618
Tim Peters855fe882002-12-22 03:43:39 +00001619# A mixin for classes with a tzinfo= argument. Subclasses must define
1620# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001621# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001622class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001623
Tim Petersbad8ff02002-12-30 20:52:32 +00001624 def test_argument_passing(self):
1625 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001626 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001627 class introspective(tzinfo):
1628 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001629 def utcoffset(self, dt):
1630 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001631 dst = utcoffset
1632
1633 obj = cls(1, 2, 3, tzinfo=introspective())
1634
Tim Peters0bf60bd2003-01-08 20:40:01 +00001635 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001636 self.assertEqual(obj.tzname(), expected)
1637
Tim Peters0bf60bd2003-01-08 20:40:01 +00001638 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001639 self.assertEqual(obj.utcoffset(), expected)
1640 self.assertEqual(obj.dst(), expected)
1641
Tim Peters855fe882002-12-22 03:43:39 +00001642 def test_bad_tzinfo_classes(self):
1643 cls = self.theclass
1644 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001645
Tim Peters855fe882002-12-22 03:43:39 +00001646 class NiceTry(object):
1647 def __init__(self): pass
1648 def utcoffset(self, dt): pass
1649 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1650
1651 class BetterTry(tzinfo):
1652 def __init__(self): pass
1653 def utcoffset(self, dt): pass
1654 b = BetterTry()
1655 t = cls(1, 1, 1, tzinfo=b)
1656 self.failUnless(t.tzinfo is b)
1657
1658 def test_utc_offset_out_of_bounds(self):
1659 class Edgy(tzinfo):
1660 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001661 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001662 def utcoffset(self, dt):
1663 return self.offset
1664
1665 cls = self.theclass
1666 for offset, legit in ((-1440, False),
1667 (-1439, True),
1668 (1439, True),
1669 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001670 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001671 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001672 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001673 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001674 else:
1675 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001676 if legit:
1677 aofs = abs(offset)
1678 h, m = divmod(aofs, 60)
1679 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001680 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001681 t = t.timetz()
1682 self.assertEqual(str(t), "01:02:03" + tag)
1683 else:
1684 self.assertRaises(ValueError, str, t)
1685
1686 def test_tzinfo_classes(self):
1687 cls = self.theclass
1688 class C1(tzinfo):
1689 def utcoffset(self, dt): return None
1690 def dst(self, dt): return None
1691 def tzname(self, dt): return None
1692 for t in (cls(1, 1, 1),
1693 cls(1, 1, 1, tzinfo=None),
1694 cls(1, 1, 1, tzinfo=C1())):
1695 self.failUnless(t.utcoffset() is None)
1696 self.failUnless(t.dst() is None)
1697 self.failUnless(t.tzname() is None)
1698
Tim Peters855fe882002-12-22 03:43:39 +00001699 class C3(tzinfo):
1700 def utcoffset(self, dt): return timedelta(minutes=-1439)
1701 def dst(self, dt): return timedelta(minutes=1439)
1702 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001703 t = cls(1, 1, 1, tzinfo=C3())
1704 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1705 self.assertEqual(t.dst(), timedelta(minutes=1439))
1706 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001707
1708 # Wrong types.
1709 class C4(tzinfo):
1710 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001711 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001712 def tzname(self, dt): return 0
1713 t = cls(1, 1, 1, tzinfo=C4())
1714 self.assertRaises(TypeError, t.utcoffset)
1715 self.assertRaises(TypeError, t.dst)
1716 self.assertRaises(TypeError, t.tzname)
1717
1718 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001719 class C6(tzinfo):
1720 def utcoffset(self, dt): return timedelta(hours=-24)
1721 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001722 t = cls(1, 1, 1, tzinfo=C6())
1723 self.assertRaises(ValueError, t.utcoffset)
1724 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001725
1726 # Not a whole number of minutes.
1727 class C7(tzinfo):
1728 def utcoffset(self, dt): return timedelta(seconds=61)
1729 def dst(self, dt): return timedelta(microseconds=-81)
1730 t = cls(1, 1, 1, tzinfo=C7())
1731 self.assertRaises(ValueError, t.utcoffset)
1732 self.assertRaises(ValueError, t.dst)
1733
Tim Peters4c0db782002-12-26 05:01:19 +00001734 def test_aware_compare(self):
1735 cls = self.theclass
1736
Tim Peters60c76e42002-12-27 00:41:11 +00001737 # Ensure that utcoffset() gets ignored if the comparands have
1738 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001739 class OperandDependentOffset(tzinfo):
1740 def utcoffset(self, t):
1741 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001742 # d0 and d1 equal after adjustment
1743 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001744 else:
Tim Peters397301e2003-01-02 21:28:08 +00001745 # d2 off in the weeds
1746 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001747
1748 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1749 d0 = base.replace(minute=3)
1750 d1 = base.replace(minute=9)
1751 d2 = base.replace(minute=11)
1752 for x in d0, d1, d2:
1753 for y in d0, d1, d2:
1754 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001755 expected = cmp(x.minute, y.minute)
1756 self.assertEqual(got, expected)
1757
1758 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001759 # Note that a time can't actually have an operand-depedent offset,
1760 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1761 # so skip this test for time.
1762 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001763 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1764 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1765 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1766 for x in d0, d1, d2:
1767 for y in d0, d1, d2:
1768 got = cmp(x, y)
1769 if (x is d0 or x is d1) and (y is d0 or y is d1):
1770 expected = 0
1771 elif x is y is d2:
1772 expected = 0
1773 elif x is d2:
1774 expected = -1
1775 else:
1776 assert y is d2
1777 expected = 1
1778 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001779
Tim Peters855fe882002-12-22 03:43:39 +00001780
Tim Peters0bf60bd2003-01-08 20:40:01 +00001781# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001782class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001783 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001784
1785 def test_empty(self):
1786 t = self.theclass()
1787 self.assertEqual(t.hour, 0)
1788 self.assertEqual(t.minute, 0)
1789 self.assertEqual(t.second, 0)
1790 self.assertEqual(t.microsecond, 0)
1791 self.failUnless(t.tzinfo is None)
1792
Tim Peters2a799bf2002-12-16 20:18:38 +00001793 def test_zones(self):
1794 est = FixedOffset(-300, "EST", 1)
1795 utc = FixedOffset(0, "UTC", -2)
1796 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001797 t1 = time( 7, 47, tzinfo=est)
1798 t2 = time(12, 47, tzinfo=utc)
1799 t3 = time(13, 47, tzinfo=met)
1800 t4 = time(microsecond=40)
1801 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001802
1803 self.assertEqual(t1.tzinfo, est)
1804 self.assertEqual(t2.tzinfo, utc)
1805 self.assertEqual(t3.tzinfo, met)
1806 self.failUnless(t4.tzinfo is None)
1807 self.assertEqual(t5.tzinfo, utc)
1808
Tim Peters855fe882002-12-22 03:43:39 +00001809 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1810 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1811 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001812 self.failUnless(t4.utcoffset() is None)
1813 self.assertRaises(TypeError, t1.utcoffset, "no args")
1814
1815 self.assertEqual(t1.tzname(), "EST")
1816 self.assertEqual(t2.tzname(), "UTC")
1817 self.assertEqual(t3.tzname(), "MET")
1818 self.failUnless(t4.tzname() is None)
1819 self.assertRaises(TypeError, t1.tzname, "no args")
1820
Tim Peters855fe882002-12-22 03:43:39 +00001821 self.assertEqual(t1.dst(), timedelta(minutes=1))
1822 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1823 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001824 self.failUnless(t4.dst() is None)
1825 self.assertRaises(TypeError, t1.dst, "no args")
1826
1827 self.assertEqual(hash(t1), hash(t2))
1828 self.assertEqual(hash(t1), hash(t3))
1829 self.assertEqual(hash(t2), hash(t3))
1830
1831 self.assertEqual(t1, t2)
1832 self.assertEqual(t1, t3)
1833 self.assertEqual(t2, t3)
1834 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1835 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1836 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1837
1838 self.assertEqual(str(t1), "07:47:00-05:00")
1839 self.assertEqual(str(t2), "12:47:00+00:00")
1840 self.assertEqual(str(t3), "13:47:00+01:00")
1841 self.assertEqual(str(t4), "00:00:00.000040")
1842 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1843
1844 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1845 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1846 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1847 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1848 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1849
Tim Peters0bf60bd2003-01-08 20:40:01 +00001850 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001851 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1852 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1853 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1854 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1855 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1856
1857 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1858 "07:47:00 %Z=EST %z=-0500")
1859 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1860 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1861
1862 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001863 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001864 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1865 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1866
Tim Petersb92bb712002-12-21 17:44:07 +00001867 # Check that an invalid tzname result raises an exception.
1868 class Badtzname(tzinfo):
1869 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001870 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001871 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1872 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001873
1874 def test_hash_edge_cases(self):
1875 # Offsets that overflow a basic time.
1876 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1877 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1878 self.assertEqual(hash(t1), hash(t2))
1879
1880 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1881 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1882 self.assertEqual(hash(t1), hash(t2))
1883
Tim Peters2a799bf2002-12-16 20:18:38 +00001884 def test_pickling(self):
1885 import pickle, cPickle
1886
1887 # Try one without a tzinfo.
1888 args = 20, 59, 16, 64**2
1889 orig = self.theclass(*args)
1890 state = orig.__getstate__()
1891 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
1892 derived = self.theclass()
1893 derived.__setstate__(state)
1894 self.assertEqual(orig, derived)
1895 for pickler in pickle, cPickle:
1896 for binary in 0, 1:
1897 green = pickler.dumps(orig, binary)
1898 derived = pickler.loads(green)
1899 self.assertEqual(orig, derived)
1900
1901 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001902 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001903 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
1904 state = orig.__getstate__()
Tim Peters37f39822003-01-10 03:49:02 +00001905 derived = self.theclass(tzinfo=FixedOffset(0, "UTC", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00001906 derived.__setstate__(state)
1907 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001908 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001909 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001910 self.assertEqual(derived.tzname(), 'cookie')
1911
1912 for pickler in pickle, cPickle:
1913 for binary in 0, 1:
1914 green = pickler.dumps(orig, binary)
1915 derived = pickler.loads(green)
1916 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001917 self.failUnless(isinstance(derived.tzinfo,
1918 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001919 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001920 self.assertEqual(derived.tzname(), 'cookie')
1921
1922 def test_more_bool(self):
1923 # Test cases with non-None tzinfo.
1924 cls = self.theclass
1925
1926 t = cls(0, tzinfo=FixedOffset(-300, ""))
1927 self.failUnless(t)
1928
1929 t = cls(5, tzinfo=FixedOffset(-300, ""))
1930 self.failUnless(t)
1931
1932 t = cls(5, tzinfo=FixedOffset(300, ""))
1933 self.failUnless(not t)
1934
1935 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1936 self.failUnless(not t)
1937
1938 # Mostly ensuring this doesn't overflow internally.
1939 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1940 self.failUnless(t)
1941
1942 # But this should yield a value error -- the utcoffset is bogus.
1943 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1944 self.assertRaises(ValueError, lambda: bool(t))
1945
1946 # Likewise.
1947 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1948 self.assertRaises(ValueError, lambda: bool(t))
1949
Tim Peters12bf3392002-12-24 05:41:27 +00001950 def test_replace(self):
1951 cls = self.theclass
1952 z100 = FixedOffset(100, "+100")
1953 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1954 args = [1, 2, 3, 4, z100]
1955 base = cls(*args)
1956 self.assertEqual(base, base.replace())
1957
1958 i = 0
1959 for name, newval in (("hour", 5),
1960 ("minute", 6),
1961 ("second", 7),
1962 ("microsecond", 8),
1963 ("tzinfo", zm200)):
1964 newargs = args[:]
1965 newargs[i] = newval
1966 expected = cls(*newargs)
1967 got = base.replace(**{name: newval})
1968 self.assertEqual(expected, got)
1969 i += 1
1970
1971 # Ensure we can get rid of a tzinfo.
1972 self.assertEqual(base.tzname(), "+100")
1973 base2 = base.replace(tzinfo=None)
1974 self.failUnless(base2.tzinfo is None)
1975 self.failUnless(base2.tzname() is None)
1976
1977 # Ensure we can add one.
1978 base3 = base2.replace(tzinfo=z100)
1979 self.assertEqual(base, base3)
1980 self.failUnless(base.tzinfo is base3.tzinfo)
1981
1982 # Out of bounds.
1983 base = cls(1)
1984 self.assertRaises(ValueError, base.replace, hour=24)
1985 self.assertRaises(ValueError, base.replace, minute=-1)
1986 self.assertRaises(ValueError, base.replace, second=100)
1987 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1988
Tim Peters60c76e42002-12-27 00:41:11 +00001989 def test_mixed_compare(self):
1990 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001991 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001992 self.assertEqual(t1, t2)
1993 t2 = t2.replace(tzinfo=None)
1994 self.assertEqual(t1, t2)
1995 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1996 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001997 if CMP_BUG_FIXED:
1998 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1999 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002000
Tim Peters0bf60bd2003-01-08 20:40:01 +00002001 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002002 class Varies(tzinfo):
2003 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002004 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002005 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002006 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002007 return self.offset
2008
2009 v = Varies()
2010 t1 = t2.replace(tzinfo=v)
2011 t2 = t2.replace(tzinfo=v)
2012 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2013 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2014 self.assertEqual(t1, t2)
2015
2016 # But if they're not identical, it isn't ignored.
2017 t2 = t2.replace(tzinfo=Varies())
2018 self.failUnless(t1 < t2) # t1's offset counter still going up
2019
Tim Peters4c0db782002-12-26 05:01:19 +00002020
Tim Peters0bf60bd2003-01-08 20:40:01 +00002021# Testing datetime objects with a non-None tzinfo.
2022
Tim Peters855fe882002-12-22 03:43:39 +00002023class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002024 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002025
2026 def test_trivial(self):
2027 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2028 self.assertEqual(dt.year, 1)
2029 self.assertEqual(dt.month, 2)
2030 self.assertEqual(dt.day, 3)
2031 self.assertEqual(dt.hour, 4)
2032 self.assertEqual(dt.minute, 5)
2033 self.assertEqual(dt.second, 6)
2034 self.assertEqual(dt.microsecond, 7)
2035 self.assertEqual(dt.tzinfo, None)
2036
2037 def test_even_more_compare(self):
2038 # The test_compare() and test_more_compare() inherited from TestDate
2039 # and TestDateTime covered non-tzinfo cases.
2040
2041 # Smallest possible after UTC adjustment.
2042 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2043 # Largest possible after UTC adjustment.
2044 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2045 tzinfo=FixedOffset(-1439, ""))
2046
2047 # Make sure those compare correctly, and w/o overflow.
2048 self.failUnless(t1 < t2)
2049 self.failUnless(t1 != t2)
2050 self.failUnless(t2 > t1)
2051
2052 self.failUnless(t1 == t1)
2053 self.failUnless(t2 == t2)
2054
2055 # Equal afer adjustment.
2056 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2057 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2058 self.assertEqual(t1, t2)
2059
2060 # Change t1 not to subtract a minute, and t1 should be larger.
2061 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2062 self.failUnless(t1 > t2)
2063
2064 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2065 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2066 self.failUnless(t1 < t2)
2067
2068 # Back to the original t1, but make seconds resolve it.
2069 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2070 second=1)
2071 self.failUnless(t1 > t2)
2072
2073 # Likewise, but make microseconds resolve it.
2074 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2075 microsecond=1)
2076 self.failUnless(t1 > t2)
2077
2078 # Make t2 naive and it should fail.
2079 t2 = self.theclass.min
2080 self.assertRaises(TypeError, lambda: t1 == t2)
2081 self.assertEqual(t2, t2)
2082
2083 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2084 class Naive(tzinfo):
2085 def utcoffset(self, dt): return None
2086 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2087 self.assertRaises(TypeError, lambda: t1 == t2)
2088 self.assertEqual(t2, t2)
2089
2090 # OTOH, it's OK to compare two of these mixing the two ways of being
2091 # naive.
2092 t1 = self.theclass(5, 6, 7)
2093 self.assertEqual(t1, t2)
2094
2095 # Try a bogus uctoffset.
2096 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002097 def utcoffset(self, dt):
2098 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002099 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2100 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002101 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002102
Tim Peters2a799bf2002-12-16 20:18:38 +00002103 def test_pickling(self):
2104 import pickle, cPickle
2105
2106 # Try one without a tzinfo.
2107 args = 6, 7, 23, 20, 59, 1, 64**2
2108 orig = self.theclass(*args)
2109 state = orig.__getstate__()
2110 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
2111 derived = self.theclass(1, 1, 1)
2112 derived.__setstate__(state)
2113 self.assertEqual(orig, derived)
2114 for pickler in pickle, cPickle:
2115 for binary in 0, 1:
2116 green = pickler.dumps(orig, binary)
2117 derived = pickler.loads(green)
2118 self.assertEqual(orig, derived)
2119
2120 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002121 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002122 orig = self.theclass(*args, **{'tzinfo': tinfo})
2123 state = orig.__getstate__()
Tim Petersa9bc1682003-01-11 03:39:11 +00002124 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00002125 derived.__setstate__(state)
2126 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002127 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002128 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002129 self.assertEqual(derived.tzname(), 'cookie')
2130
2131 for pickler in pickle, cPickle:
2132 for binary in 0, 1:
2133 green = pickler.dumps(orig, binary)
2134 derived = pickler.loads(green)
2135 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002136 self.failUnless(isinstance(derived.tzinfo,
2137 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002138 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002139 self.assertEqual(derived.tzname(), 'cookie')
2140
2141 def test_extreme_hashes(self):
2142 # If an attempt is made to hash these via subtracting the offset
2143 # then hashing a datetime object, OverflowError results. The
2144 # Python implementation used to blow up here.
2145 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2146 hash(t)
2147 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2148 tzinfo=FixedOffset(-1439, ""))
2149 hash(t)
2150
2151 # OTOH, an OOB offset should blow up.
2152 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2153 self.assertRaises(ValueError, hash, t)
2154
2155 def test_zones(self):
2156 est = FixedOffset(-300, "EST")
2157 utc = FixedOffset(0, "UTC")
2158 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002159 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2160 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2161 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002162 self.assertEqual(t1.tzinfo, est)
2163 self.assertEqual(t2.tzinfo, utc)
2164 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002165 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2166 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2167 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002168 self.assertEqual(t1.tzname(), "EST")
2169 self.assertEqual(t2.tzname(), "UTC")
2170 self.assertEqual(t3.tzname(), "MET")
2171 self.assertEqual(hash(t1), hash(t2))
2172 self.assertEqual(hash(t1), hash(t3))
2173 self.assertEqual(hash(t2), hash(t3))
2174 self.assertEqual(t1, t2)
2175 self.assertEqual(t1, t3)
2176 self.assertEqual(t2, t3)
2177 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2178 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2179 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002180 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002181 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2182 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2183 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2184
2185 def test_combine(self):
2186 met = FixedOffset(60, "MET")
2187 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002188 tz = time(18, 45, 3, 1234, tzinfo=met)
2189 dt = datetime.combine(d, tz)
2190 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002191 tzinfo=met))
2192
2193 def test_extract(self):
2194 met = FixedOffset(60, "MET")
2195 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2196 self.assertEqual(dt.date(), date(2002, 3, 4))
2197 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002198 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002199
2200 def test_tz_aware_arithmetic(self):
2201 import random
2202
2203 now = self.theclass.now()
2204 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002205 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002206 nowaware = self.theclass.combine(now.date(), timeaware)
2207 self.failUnless(nowaware.tzinfo is tz55)
2208 self.assertEqual(nowaware.timetz(), timeaware)
2209
2210 # Can't mix aware and non-aware.
2211 self.assertRaises(TypeError, lambda: now - nowaware)
2212 self.assertRaises(TypeError, lambda: nowaware - now)
2213
Tim Peters0bf60bd2003-01-08 20:40:01 +00002214 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002215 self.assertRaises(TypeError, lambda: now + nowaware)
2216 self.assertRaises(TypeError, lambda: nowaware + now)
2217 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2218
2219 # Subtracting should yield 0.
2220 self.assertEqual(now - now, timedelta(0))
2221 self.assertEqual(nowaware - nowaware, timedelta(0))
2222
2223 # Adding a delta should preserve tzinfo.
2224 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2225 nowawareplus = nowaware + delta
2226 self.failUnless(nowaware.tzinfo is tz55)
2227 nowawareplus2 = delta + nowaware
2228 self.failUnless(nowawareplus2.tzinfo is tz55)
2229 self.assertEqual(nowawareplus, nowawareplus2)
2230
2231 # that - delta should be what we started with, and that - what we
2232 # started with should be delta.
2233 diff = nowawareplus - delta
2234 self.failUnless(diff.tzinfo is tz55)
2235 self.assertEqual(nowaware, diff)
2236 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2237 self.assertEqual(nowawareplus - nowaware, delta)
2238
2239 # Make up a random timezone.
2240 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002241 # Attach it to nowawareplus.
2242 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002243 self.failUnless(nowawareplus.tzinfo is tzr)
2244 # Make sure the difference takes the timezone adjustments into account.
2245 got = nowaware - nowawareplus
2246 # Expected: (nowaware base - nowaware offset) -
2247 # (nowawareplus base - nowawareplus offset) =
2248 # (nowaware base - nowawareplus base) +
2249 # (nowawareplus offset - nowaware offset) =
2250 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002251 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002252 self.assertEqual(got, expected)
2253
2254 # Try max possible difference.
2255 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2256 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2257 tzinfo=FixedOffset(-1439, "max"))
2258 maxdiff = max - min
2259 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2260 timedelta(minutes=2*1439))
2261
2262 def test_tzinfo_now(self):
2263 meth = self.theclass.now
2264 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2265 base = meth()
2266 # Try with and without naming the keyword.
2267 off42 = FixedOffset(42, "42")
2268 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002269 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002270 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002271 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002272 # Bad argument with and w/o naming the keyword.
2273 self.assertRaises(TypeError, meth, 16)
2274 self.assertRaises(TypeError, meth, tzinfo=16)
2275 # Bad keyword name.
2276 self.assertRaises(TypeError, meth, tinfo=off42)
2277 # Too many args.
2278 self.assertRaises(TypeError, meth, off42, off42)
2279
Tim Peters10cadce2003-01-23 19:58:02 +00002280 # We don't know which time zone we're in, and don't have a tzinfo
2281 # class to represent it, so seeing whether a tz argument actually
2282 # does a conversion is tricky.
2283 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2284 utc = FixedOffset(0, "utc", 0)
2285 for dummy in range(3):
2286 now = datetime.now(weirdtz)
2287 self.failUnless(now.tzinfo is weirdtz)
2288 utcnow = datetime.utcnow().replace(tzinfo=utc)
2289 now2 = utcnow.astimezone(weirdtz)
2290 if abs(now - now2) < timedelta(seconds=30):
2291 break
2292 # Else the code is broken, or more than 30 seconds passed between
2293 # calls; assuming the latter, just try again.
2294 else:
2295 # Three strikes and we're out.
2296 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2297
Tim Peters2a799bf2002-12-16 20:18:38 +00002298 def test_tzinfo_fromtimestamp(self):
2299 import time
2300 meth = self.theclass.fromtimestamp
2301 ts = time.time()
2302 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2303 base = meth(ts)
2304 # Try with and without naming the keyword.
2305 off42 = FixedOffset(42, "42")
2306 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002307 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002308 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002309 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002310 # Bad argument with and w/o naming the keyword.
2311 self.assertRaises(TypeError, meth, ts, 16)
2312 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2313 # Bad keyword name.
2314 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2315 # Too many args.
2316 self.assertRaises(TypeError, meth, ts, off42, off42)
2317 # Too few args.
2318 self.assertRaises(TypeError, meth)
2319
Tim Peters2a44a8d2003-01-23 20:53:10 +00002320 # Try to make sure tz= actually does some conversion.
2321 timestamp = 1000000000 # 2001-09-09 01:46:40 UTC, give or take
2322 utc = FixedOffset(0, "utc", 0)
2323 expected = datetime(2001, 9, 9, 1, 46, 40)
2324 got = datetime.utcfromtimestamp(timestamp)
2325 # We don't support leap seconds, but maybe the platfrom insists
2326 # on using them, so don't demand exact equality).
2327 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2328
2329 est = FixedOffset(-5*60, "est", 0)
2330 expected -= timedelta(hours=5)
2331 got = datetime.fromtimestamp(timestamp, est).replace(tzinfo=None)
2332 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2333
Tim Peters2a799bf2002-12-16 20:18:38 +00002334 def test_tzinfo_utcnow(self):
2335 meth = self.theclass.utcnow
2336 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2337 base = meth()
2338 # Try with and without naming the keyword; for whatever reason,
2339 # utcnow() doesn't accept a tzinfo argument.
2340 off42 = FixedOffset(42, "42")
2341 self.assertRaises(TypeError, meth, off42)
2342 self.assertRaises(TypeError, meth, tzinfo=off42)
2343
2344 def test_tzinfo_utcfromtimestamp(self):
2345 import time
2346 meth = self.theclass.utcfromtimestamp
2347 ts = time.time()
2348 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2349 base = meth(ts)
2350 # Try with and without naming the keyword; for whatever reason,
2351 # utcfromtimestamp() doesn't accept a tzinfo argument.
2352 off42 = FixedOffset(42, "42")
2353 self.assertRaises(TypeError, meth, ts, off42)
2354 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2355
2356 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002357 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002358 # DST flag.
2359 class DST(tzinfo):
2360 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002361 if isinstance(dstvalue, int):
2362 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002363 self.dstvalue = dstvalue
2364 def dst(self, dt):
2365 return self.dstvalue
2366
2367 cls = self.theclass
2368 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2369 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2370 t = d.timetuple()
2371 self.assertEqual(1, t.tm_year)
2372 self.assertEqual(1, t.tm_mon)
2373 self.assertEqual(1, t.tm_mday)
2374 self.assertEqual(10, t.tm_hour)
2375 self.assertEqual(20, t.tm_min)
2376 self.assertEqual(30, t.tm_sec)
2377 self.assertEqual(0, t.tm_wday)
2378 self.assertEqual(1, t.tm_yday)
2379 self.assertEqual(flag, t.tm_isdst)
2380
2381 # dst() returns wrong type.
2382 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2383
2384 # dst() at the edge.
2385 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2386 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2387
2388 # dst() out of range.
2389 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2390 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2391
2392 def test_utctimetuple(self):
2393 class DST(tzinfo):
2394 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002395 if isinstance(dstvalue, int):
2396 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002397 self.dstvalue = dstvalue
2398 def dst(self, dt):
2399 return self.dstvalue
2400
2401 cls = self.theclass
2402 # This can't work: DST didn't implement utcoffset.
2403 self.assertRaises(NotImplementedError,
2404 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2405
2406 class UOFS(DST):
2407 def __init__(self, uofs, dofs=None):
2408 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002409 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002410 def utcoffset(self, dt):
2411 return self.uofs
2412
2413 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2414 # in effect for a UTC time.
2415 for dstvalue in -33, 33, 0, None:
2416 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2417 t = d.utctimetuple()
2418 self.assertEqual(d.year, t.tm_year)
2419 self.assertEqual(d.month, t.tm_mon)
2420 self.assertEqual(d.day, t.tm_mday)
2421 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2422 self.assertEqual(13, t.tm_min)
2423 self.assertEqual(d.second, t.tm_sec)
2424 self.assertEqual(d.weekday(), t.tm_wday)
2425 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2426 t.tm_yday)
2427 self.assertEqual(0, t.tm_isdst)
2428
2429 # At the edges, UTC adjustment can normalize into years out-of-range
2430 # for a datetime object. Ensure that a correct timetuple is
2431 # created anyway.
2432 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2433 # That goes back 1 minute less than a full day.
2434 t = tiny.utctimetuple()
2435 self.assertEqual(t.tm_year, MINYEAR-1)
2436 self.assertEqual(t.tm_mon, 12)
2437 self.assertEqual(t.tm_mday, 31)
2438 self.assertEqual(t.tm_hour, 0)
2439 self.assertEqual(t.tm_min, 1)
2440 self.assertEqual(t.tm_sec, 37)
2441 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2442 self.assertEqual(t.tm_isdst, 0)
2443
2444 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2445 # That goes forward 1 minute less than a full day.
2446 t = huge.utctimetuple()
2447 self.assertEqual(t.tm_year, MAXYEAR+1)
2448 self.assertEqual(t.tm_mon, 1)
2449 self.assertEqual(t.tm_mday, 1)
2450 self.assertEqual(t.tm_hour, 23)
2451 self.assertEqual(t.tm_min, 58)
2452 self.assertEqual(t.tm_sec, 37)
2453 self.assertEqual(t.tm_yday, 1)
2454 self.assertEqual(t.tm_isdst, 0)
2455
2456 def test_tzinfo_isoformat(self):
2457 zero = FixedOffset(0, "+00:00")
2458 plus = FixedOffset(220, "+03:40")
2459 minus = FixedOffset(-231, "-03:51")
2460 unknown = FixedOffset(None, "")
2461
2462 cls = self.theclass
2463 datestr = '0001-02-03'
2464 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002465 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002466 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2467 timestr = '04:05:59' + (us and '.987001' or '')
2468 ofsstr = ofs is not None and d.tzname() or ''
2469 tailstr = timestr + ofsstr
2470 iso = d.isoformat()
2471 self.assertEqual(iso, datestr + 'T' + tailstr)
2472 self.assertEqual(iso, d.isoformat('T'))
2473 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2474 self.assertEqual(str(d), datestr + ' ' + tailstr)
2475
Tim Peters12bf3392002-12-24 05:41:27 +00002476 def test_replace(self):
2477 cls = self.theclass
2478 z100 = FixedOffset(100, "+100")
2479 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2480 args = [1, 2, 3, 4, 5, 6, 7, z100]
2481 base = cls(*args)
2482 self.assertEqual(base, base.replace())
2483
2484 i = 0
2485 for name, newval in (("year", 2),
2486 ("month", 3),
2487 ("day", 4),
2488 ("hour", 5),
2489 ("minute", 6),
2490 ("second", 7),
2491 ("microsecond", 8),
2492 ("tzinfo", zm200)):
2493 newargs = args[:]
2494 newargs[i] = newval
2495 expected = cls(*newargs)
2496 got = base.replace(**{name: newval})
2497 self.assertEqual(expected, got)
2498 i += 1
2499
2500 # Ensure we can get rid of a tzinfo.
2501 self.assertEqual(base.tzname(), "+100")
2502 base2 = base.replace(tzinfo=None)
2503 self.failUnless(base2.tzinfo is None)
2504 self.failUnless(base2.tzname() is None)
2505
2506 # Ensure we can add one.
2507 base3 = base2.replace(tzinfo=z100)
2508 self.assertEqual(base, base3)
2509 self.failUnless(base.tzinfo is base3.tzinfo)
2510
2511 # Out of bounds.
2512 base = cls(2000, 2, 29)
2513 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002514
Tim Peters80475bb2002-12-25 07:40:55 +00002515 def test_more_astimezone(self):
2516 # The inherited test_astimezone covered some trivial and error cases.
2517 fnone = FixedOffset(None, "None")
2518 f44m = FixedOffset(44, "44")
2519 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2520
Tim Peters10cadce2003-01-23 19:58:02 +00002521 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002522 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002523 # Replacing with degenerate tzinfo raises an exception.
2524 self.assertRaises(ValueError, dt.astimezone, fnone)
2525 # Ditto with None tz.
2526 self.assertRaises(TypeError, dt.astimezone, None)
2527 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002528 x = dt.astimezone(dt.tzinfo)
2529 self.failUnless(x.tzinfo is f44m)
2530 self.assertEqual(x.date(), dt.date())
2531 self.assertEqual(x.time(), dt.time())
2532
2533 # Replacing with different tzinfo does adjust.
2534 got = dt.astimezone(fm5h)
2535 self.failUnless(got.tzinfo is fm5h)
2536 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2537 expected = dt - dt.utcoffset() # in effect, convert to UTC
2538 expected += fm5h.utcoffset(dt) # and from there to local time
2539 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2540 self.assertEqual(got.date(), expected.date())
2541 self.assertEqual(got.time(), expected.time())
2542 self.assertEqual(got.timetz(), expected.timetz())
2543 self.failUnless(got.tzinfo is expected.tzinfo)
2544 self.assertEqual(got, expected)
2545
Tim Peters4c0db782002-12-26 05:01:19 +00002546 def test_aware_subtract(self):
2547 cls = self.theclass
2548
Tim Peters60c76e42002-12-27 00:41:11 +00002549 # Ensure that utcoffset() is ignored when the operands have the
2550 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002551 class OperandDependentOffset(tzinfo):
2552 def utcoffset(self, t):
2553 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002554 # d0 and d1 equal after adjustment
2555 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002556 else:
Tim Peters397301e2003-01-02 21:28:08 +00002557 # d2 off in the weeds
2558 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002559
2560 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2561 d0 = base.replace(minute=3)
2562 d1 = base.replace(minute=9)
2563 d2 = base.replace(minute=11)
2564 for x in d0, d1, d2:
2565 for y in d0, d1, d2:
2566 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002567 expected = timedelta(minutes=x.minute - y.minute)
2568 self.assertEqual(got, expected)
2569
2570 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2571 # ignored.
2572 base = cls(8, 9, 10, 11, 12, 13, 14)
2573 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2574 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2575 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2576 for x in d0, d1, d2:
2577 for y in d0, d1, d2:
2578 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002579 if (x is d0 or x is d1) and (y is d0 or y is d1):
2580 expected = timedelta(0)
2581 elif x is y is d2:
2582 expected = timedelta(0)
2583 elif x is d2:
2584 expected = timedelta(minutes=(11-59)-0)
2585 else:
2586 assert y is d2
2587 expected = timedelta(minutes=0-(11-59))
2588 self.assertEqual(got, expected)
2589
Tim Peters60c76e42002-12-27 00:41:11 +00002590 def test_mixed_compare(self):
2591 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002592 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002593 self.assertEqual(t1, t2)
2594 t2 = t2.replace(tzinfo=None)
2595 self.assertEqual(t1, t2)
2596 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2597 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002598 if CMP_BUG_FIXED:
2599 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2600 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002601
Tim Peters0bf60bd2003-01-08 20:40:01 +00002602 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002603 class Varies(tzinfo):
2604 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002605 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002606 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002607 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002608 return self.offset
2609
2610 v = Varies()
2611 t1 = t2.replace(tzinfo=v)
2612 t2 = t2.replace(tzinfo=v)
2613 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2614 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2615 self.assertEqual(t1, t2)
2616
2617 # But if they're not identical, it isn't ignored.
2618 t2 = t2.replace(tzinfo=Varies())
2619 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002620
Tim Peters621818b2002-12-29 23:44:49 +00002621# Pain to set up DST-aware tzinfo classes.
2622
2623def first_sunday_on_or_after(dt):
2624 days_to_go = 6 - dt.weekday()
2625 if days_to_go:
2626 dt += timedelta(days_to_go)
2627 return dt
2628
2629ZERO = timedelta(0)
2630HOUR = timedelta(hours=1)
2631DAY = timedelta(days=1)
2632# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2633DSTSTART = datetime(1, 4, 1, 2)
2634# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002635# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2636# being standard time on that day, there is no spelling in local time of
2637# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2638DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002639
2640class USTimeZone(tzinfo):
2641
2642 def __init__(self, hours, reprname, stdname, dstname):
2643 self.stdoffset = timedelta(hours=hours)
2644 self.reprname = reprname
2645 self.stdname = stdname
2646 self.dstname = dstname
2647
2648 def __repr__(self):
2649 return self.reprname
2650
2651 def tzname(self, dt):
2652 if self.dst(dt):
2653 return self.dstname
2654 else:
2655 return self.stdname
2656
2657 def utcoffset(self, dt):
2658 return self.stdoffset + self.dst(dt)
2659
2660 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002661 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002662 # An exception instead may be sensible here, in one or more of
2663 # the cases.
2664 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002665 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002666
2667 # Find first Sunday in April.
2668 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2669 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2670
2671 # Find last Sunday in October.
2672 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2673 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2674
Tim Peters621818b2002-12-29 23:44:49 +00002675 # Can't compare naive to aware objects, so strip the timezone from
2676 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002677 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002678 return HOUR
2679 else:
2680 return ZERO
2681
Tim Peters521fc152002-12-31 17:36:56 +00002682Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2683Central = USTimeZone(-6, "Central", "CST", "CDT")
2684Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2685Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002686utc_real = FixedOffset(0, "UTC", 0)
2687# For better test coverage, we want another flavor of UTC that's west of
2688# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002689utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002690
2691class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002692 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002693 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002694 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002695
Tim Peters0bf60bd2003-01-08 20:40:01 +00002696 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002697
Tim Peters521fc152002-12-31 17:36:56 +00002698 # Check a time that's inside DST.
2699 def checkinside(self, dt, tz, utc, dston, dstoff):
2700 self.assertEqual(dt.dst(), HOUR)
2701
2702 # Conversion to our own timezone is always an identity.
2703 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002704
2705 asutc = dt.astimezone(utc)
2706 there_and_back = asutc.astimezone(tz)
2707
2708 # Conversion to UTC and back isn't always an identity here,
2709 # because there are redundant spellings (in local time) of
2710 # UTC time when DST begins: the clock jumps from 1:59:59
2711 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2712 # make sense then. The classes above treat 2:MM:SS as
2713 # daylight time then (it's "after 2am"), really an alias
2714 # for 1:MM:SS standard time. The latter form is what
2715 # conversion back from UTC produces.
2716 if dt.date() == dston.date() and dt.hour == 2:
2717 # We're in the redundant hour, and coming back from
2718 # UTC gives the 1:MM:SS standard-time spelling.
2719 self.assertEqual(there_and_back + HOUR, dt)
2720 # Although during was considered to be in daylight
2721 # time, there_and_back is not.
2722 self.assertEqual(there_and_back.dst(), ZERO)
2723 # They're the same times in UTC.
2724 self.assertEqual(there_and_back.astimezone(utc),
2725 dt.astimezone(utc))
2726 else:
2727 # We're not in the redundant hour.
2728 self.assertEqual(dt, there_and_back)
2729
Tim Peters327098a2003-01-20 22:54:38 +00002730 # Because we have a redundant spelling when DST begins, there is
2731 # (unforunately) an hour when DST ends that can't be spelled at all in
2732 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2733 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2734 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2735 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2736 # expressed in local time. Nevertheless, we want conversion back
2737 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002738 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002739 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002740 if dt.date() == dstoff.date() and dt.hour == 0:
2741 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002742 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002743 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2744 nexthour_utc += HOUR
2745 nexthour_tz = nexthour_utc.astimezone(tz)
2746 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002747 else:
Tim Peters327098a2003-01-20 22:54:38 +00002748 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002749
2750 # Check a time that's outside DST.
2751 def checkoutside(self, dt, tz, utc):
2752 self.assertEqual(dt.dst(), ZERO)
2753
2754 # Conversion to our own timezone is always an identity.
2755 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002756
2757 # Converting to UTC and back is an identity too.
2758 asutc = dt.astimezone(utc)
2759 there_and_back = asutc.astimezone(tz)
2760 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002761
Tim Peters1024bf82002-12-30 17:09:40 +00002762 def convert_between_tz_and_utc(self, tz, utc):
2763 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002764 # Because 1:MM on the day DST ends is taken as being standard time,
2765 # there is no spelling in tz for the last hour of daylight time.
2766 # For purposes of the test, the last hour of DST is 0:MM, which is
2767 # taken as being daylight time (and 1:MM is taken as being standard
2768 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002769 dstoff = self.dstoff.replace(tzinfo=tz)
2770 for delta in (timedelta(weeks=13),
2771 DAY,
2772 HOUR,
2773 timedelta(minutes=1),
2774 timedelta(microseconds=1)):
2775
Tim Peters521fc152002-12-31 17:36:56 +00002776 self.checkinside(dston, tz, utc, dston, dstoff)
2777 for during in dston + delta, dstoff - delta:
2778 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002779
Tim Peters521fc152002-12-31 17:36:56 +00002780 self.checkoutside(dstoff, tz, utc)
2781 for outside in dston - delta, dstoff + delta:
2782 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002783
Tim Peters621818b2002-12-29 23:44:49 +00002784 def test_easy(self):
2785 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002786 self.convert_between_tz_and_utc(Eastern, utc_real)
2787 self.convert_between_tz_and_utc(Pacific, utc_real)
2788 self.convert_between_tz_and_utc(Eastern, utc_fake)
2789 self.convert_between_tz_and_utc(Pacific, utc_fake)
2790 # The next is really dancing near the edge. It works because
2791 # Pacific and Eastern are far enough apart that their "problem
2792 # hours" don't overlap.
2793 self.convert_between_tz_and_utc(Eastern, Pacific)
2794 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002795 # OTOH, these fail! Don't enable them. The difficulty is that
2796 # the edge case tests assume that every hour is representable in
2797 # the "utc" class. This is always true for a fixed-offset tzinfo
2798 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2799 # For these adjacent DST-aware time zones, the range of time offsets
2800 # tested ends up creating hours in the one that aren't representable
2801 # in the other. For the same reason, we would see failures in the
2802 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2803 # offset deltas in convert_between_tz_and_utc().
2804 #
2805 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2806 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002807
Tim Petersf3615152003-01-01 21:51:37 +00002808 def test_tricky(self):
2809 # 22:00 on day before daylight starts.
2810 fourback = self.dston - timedelta(hours=4)
2811 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002812 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002813 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2814 # 2", we should get the 3 spelling.
2815 # If we plug 22:00 the day before into Eastern, it "looks like std
2816 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2817 # to 22:00 lands on 2:00, which makes no sense in local time (the
2818 # local clock jumps from 1 to 3). The point here is to make sure we
2819 # get the 3 spelling.
2820 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002821 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002822 self.assertEqual(expected, got)
2823
2824 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2825 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002826 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002827 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2828 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2829 # spelling.
2830 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002831 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002832 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002833
Tim Petersadf64202003-01-04 06:03:15 +00002834 # Now on the day DST ends, we want "repeat an hour" behavior.
2835 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2836 # EST 23:MM 0:MM 1:MM 2:MM
2837 # EDT 0:MM 1:MM 2:MM 3:MM
2838 # wall 0:MM 1:MM 1:MM 2:MM against these
2839 for utc in utc_real, utc_fake:
2840 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002841 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002842 # Convert that to UTC.
2843 first_std_hour -= tz.utcoffset(None)
2844 # Adjust for possibly fake UTC.
2845 asutc = first_std_hour + utc.utcoffset(None)
2846 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2847 # tz=Eastern.
2848 asutcbase = asutc.replace(tzinfo=utc)
2849 for tzhour in (0, 1, 1, 2):
2850 expectedbase = self.dstoff.replace(hour=tzhour)
2851 for minute in 0, 30, 59:
2852 expected = expectedbase.replace(minute=minute)
2853 asutc = asutcbase.replace(minute=minute)
2854 astz = asutc.astimezone(tz)
2855 self.assertEqual(astz.replace(tzinfo=None), expected)
2856 asutcbase += HOUR
2857
2858
Tim Peters710fb152003-01-02 19:35:54 +00002859 def test_bogus_dst(self):
2860 class ok(tzinfo):
2861 def utcoffset(self, dt): return HOUR
2862 def dst(self, dt): return HOUR
2863
2864 now = self.theclass.now().replace(tzinfo=utc_real)
2865 # Doesn't blow up.
2866 now.astimezone(ok())
2867
2868 # Does blow up.
2869 class notok(ok):
2870 def dst(self, dt): return None
2871 self.assertRaises(ValueError, now.astimezone, notok())
2872
Tim Peters52dcce22003-01-23 16:36:11 +00002873 def test_fromutc(self):
2874 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2875 now = datetime.utcnow().replace(tzinfo=utc_real)
2876 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2877 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2878 enow = Eastern.fromutc(now) # doesn't blow up
2879 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2880 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2881 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2882
2883 # Always converts UTC to standard time.
2884 class FauxUSTimeZone(USTimeZone):
2885 def fromutc(self, dt):
2886 return dt + self.stdoffset
2887 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2888
2889 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2890 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2891 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2892
2893 # Check around DST start.
2894 start = self.dston.replace(hour=4, tzinfo=Eastern)
2895 fstart = start.replace(tzinfo=FEastern)
2896 for wall in 23, 0, 1, 3, 4, 5:
2897 expected = start.replace(hour=wall)
2898 if wall == 23:
2899 expected -= timedelta(days=1)
2900 got = Eastern.fromutc(start)
2901 self.assertEqual(expected, got)
2902
2903 expected = fstart + FEastern.stdoffset
2904 got = FEastern.fromutc(fstart)
2905 self.assertEqual(expected, got)
2906
2907 # Ensure astimezone() calls fromutc() too.
2908 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2909 self.assertEqual(expected, got)
2910
2911 start += HOUR
2912 fstart += HOUR
2913
2914 # Check around DST end.
2915 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2916 fstart = start.replace(tzinfo=FEastern)
2917 for wall in 0, 1, 1, 2, 3, 4:
2918 expected = start.replace(hour=wall)
2919 got = Eastern.fromutc(start)
2920 self.assertEqual(expected, got)
2921
2922 expected = fstart + FEastern.stdoffset
2923 got = FEastern.fromutc(fstart)
2924 self.assertEqual(expected, got)
2925
2926 # Ensure astimezone() calls fromutc() too.
2927 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2928 self.assertEqual(expected, got)
2929
2930 start += HOUR
2931 fstart += HOUR
2932
Tim Peters710fb152003-01-02 19:35:54 +00002933
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002934def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002935 allsuites = [unittest.makeSuite(klass, 'test')
2936 for klass in (TestModule,
2937 TestTZInfo,
2938 TestTimeDelta,
2939 TestDateOnly,
2940 TestDate,
2941 TestDateTime,
2942 TestTime,
2943 TestTimeTZ,
2944 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002945 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002946 )
2947 ]
2948 return unittest.TestSuite(allsuites)
2949
2950def test_main():
2951 import gc
2952 import sys
2953
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002954 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002955 lastrc = None
2956 while True:
2957 test_support.run_suite(thesuite)
2958 if 1: # change to 0, under a debug build, for some leak detection
2959 break
2960 gc.collect()
2961 if gc.garbage:
2962 raise SystemError("gc.garbage not empty after test run: %r" %
2963 gc.garbage)
2964 if hasattr(sys, 'gettotalrefcount'):
2965 thisrc = sys.gettotalrefcount()
2966 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2967 if lastrc:
2968 print >> sys.stderr, 'delta:', thisrc - lastrc
2969 else:
2970 print >> sys.stderr
2971 lastrc = thisrc
2972
2973if __name__ == "__main__":
2974 test_main()