blob: 73db0333266b348bac649f61b207907345024f85 [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
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
19
Guido van Rossum177e41a2003-01-30 22:06:23 +000020pickle_choices = [
21 (pickle, pickle, 0),
22 (pickle, pickle, 1),
23 (pickle, pickle, 2),
24 (cPickle, cPickle, 0),
25 (cPickle, cPickle, 1),
26## (cPickle, cPickle, 2),
27 (pickle, cPickle, 0),
28 (pickle, cPickle, 1),
29## (pickle, cPickle, 2),
30 (cPickle, pickle, 0),
31 (cPickle, pickle, 1),
32## (cPickle, pickle, 2),
33 ]
34
35
Tim Peters0bf60bd2003-01-08 20:40:01 +000036# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are
37# XXX instances of new-style classes (like date and time) that both
38# XXX define __cmp__, and x is compared to y, and one of the __cmp__
39# XXX implementations raises an exception, the exception can get dropped
40# XXX on the floor when it occurs, and pop up again at some "random" time
41# XXX later (it depends on when the next opcode gets executed that
42# XXX bothers to check). There isn't a workaround for this, so instead
43# XXX we disable the parts of the tests that trigger it unless
44# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid
45# XXX provoking it here.
46# XXX Guido checked into a fix that will go into 2.2.3. The bug was
47# XXX already fixed in 2.3 CVS via a different means.
48CMP_BUG_FIXED = sys.version_info >= (2, 2, 3)
49
Tim Peters2a799bf2002-12-16 20:18:38 +000050
51#############################################################################
52# module tests
53
54class TestModule(unittest.TestCase):
55
56 def test_constants(self):
57 import datetime
58 self.assertEqual(datetime.MINYEAR, 1)
59 self.assertEqual(datetime.MAXYEAR, 9999)
60
61#############################################################################
62# tzinfo tests
63
64class FixedOffset(tzinfo):
65 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000066 if isinstance(offset, int):
67 offset = timedelta(minutes=offset)
68 if isinstance(dstoffset, int):
69 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000070 self.__offset = offset
71 self.__name = name
72 self.__dstoffset = dstoffset
73 def __repr__(self):
74 return self.__name.lower()
75 def utcoffset(self, dt):
76 return self.__offset
77 def tzname(self, dt):
78 return self.__name
79 def dst(self, dt):
80 return self.__dstoffset
81
Tim Petersfb8472c2002-12-21 05:04:42 +000082class PicklableFixedOffset(FixedOffset):
83 def __init__(self, offset=None, name=None, dstoffset=None):
84 FixedOffset.__init__(self, offset, name, dstoffset)
85
Tim Peters2a799bf2002-12-16 20:18:38 +000086class TestTZInfo(unittest.TestCase):
87
88 def test_non_abstractness(self):
89 # In order to allow subclasses to get pickled, the C implementation
90 # wasn't able to get away with having __init__ raise
91 # NotImplementedError.
92 useless = tzinfo()
93 dt = datetime.max
94 self.assertRaises(NotImplementedError, useless.tzname, dt)
95 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
96 self.assertRaises(NotImplementedError, useless.dst, dt)
97
98 def test_subclass_must_override(self):
99 class NotEnough(tzinfo):
100 def __init__(self, offset, name):
101 self.__offset = offset
102 self.__name = name
103 self.failUnless(issubclass(NotEnough, tzinfo))
104 ne = NotEnough(3, "NotByALongShot")
105 self.failUnless(isinstance(ne, tzinfo))
106
107 dt = datetime.now()
108 self.assertRaises(NotImplementedError, ne.tzname, dt)
109 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
110 self.assertRaises(NotImplementedError, ne.dst, dt)
111
112 def test_normal(self):
113 fo = FixedOffset(3, "Three")
114 self.failUnless(isinstance(fo, tzinfo))
115 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000118 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000119
120 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000121 # There's no point to pickling tzinfo objects on their own (they
122 # carry no data), but they need to be picklable anyway else
123 # concrete subclasses can't be pickled.
124 orig = tzinfo.__new__(tzinfo)
125 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000126 for pickler, unpickler, proto in pickle_choices:
127 green = pickler.dumps(orig, proto)
128 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000129 self.failUnless(type(derived) is tzinfo)
130
131 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000132 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000133 offset = timedelta(minutes=-300)
134 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000135 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000136 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000137 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000138 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000139 for pickler, unpickler, proto in pickle_choices:
140 green = pickler.dumps(orig, proto)
141 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000142 self.failUnless(isinstance(derived, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000143 self.failUnless(type(derived) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000144 self.assertEqual(derived.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000145 self.assertEqual(derived.tzname(None), 'cookie')
146
147#############################################################################
148# timedelta tests
149
150class TestTimeDelta(unittest.TestCase):
151
152 def test_constructor(self):
153 eq = self.assertEqual
154 td = timedelta
155
156 # Check keyword args to constructor
157 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
158 milliseconds=0, microseconds=0))
159 eq(td(1), td(days=1))
160 eq(td(0, 1), td(seconds=1))
161 eq(td(0, 0, 1), td(microseconds=1))
162 eq(td(weeks=1), td(days=7))
163 eq(td(days=1), td(hours=24))
164 eq(td(hours=1), td(minutes=60))
165 eq(td(minutes=1), td(seconds=60))
166 eq(td(seconds=1), td(milliseconds=1000))
167 eq(td(milliseconds=1), td(microseconds=1000))
168
169 # Check float args to constructor
170 eq(td(weeks=1.0/7), td(days=1))
171 eq(td(days=1.0/24), td(hours=1))
172 eq(td(hours=1.0/60), td(minutes=1))
173 eq(td(minutes=1.0/60), td(seconds=1))
174 eq(td(seconds=0.001), td(milliseconds=1))
175 eq(td(milliseconds=0.001), td(microseconds=1))
176
177 def test_computations(self):
178 eq = self.assertEqual
179 td = timedelta
180
181 a = td(7) # One week
182 b = td(0, 60) # One minute
183 c = td(0, 0, 1000) # One millisecond
184 eq(a+b+c, td(7, 60, 1000))
185 eq(a-b, td(6, 24*3600 - 60))
186 eq(-a, td(-7))
187 eq(+a, td(7))
188 eq(-b, td(-1, 24*3600 - 60))
189 eq(-c, td(-1, 24*3600 - 1, 999000))
190 eq(abs(a), a)
191 eq(abs(-a), a)
192 eq(td(6, 24*3600), a)
193 eq(td(0, 0, 60*1000000), b)
194 eq(a*10, td(70))
195 eq(a*10, 10*a)
196 eq(a*10L, 10*a)
197 eq(b*10, td(0, 600))
198 eq(10*b, td(0, 600))
199 eq(b*10L, td(0, 600))
200 eq(c*10, td(0, 0, 10000))
201 eq(10*c, td(0, 0, 10000))
202 eq(c*10L, td(0, 0, 10000))
203 eq(a*-1, -a)
204 eq(b*-2, -b-b)
205 eq(c*-2, -c+-c)
206 eq(b*(60*24), (b*60)*24)
207 eq(b*(60*24), (60*b)*24)
208 eq(c*1000, td(0, 1))
209 eq(1000*c, td(0, 1))
210 eq(a//7, td(1))
211 eq(b//10, td(0, 6))
212 eq(c//1000, td(0, 0, 1))
213 eq(a//10, td(0, 7*24*360))
214 eq(a//3600000, td(0, 0, 7*24*1000))
215
216 def test_disallowed_computations(self):
217 a = timedelta(42)
218
219 # Add/sub ints, longs, floats should be illegal
220 for i in 1, 1L, 1.0:
221 self.assertRaises(TypeError, lambda: a+i)
222 self.assertRaises(TypeError, lambda: a-i)
223 self.assertRaises(TypeError, lambda: i+a)
224 self.assertRaises(TypeError, lambda: i-a)
225
226 # Mul/div by float isn't supported.
227 x = 2.3
228 self.assertRaises(TypeError, lambda: a*x)
229 self.assertRaises(TypeError, lambda: x*a)
230 self.assertRaises(TypeError, lambda: a/x)
231 self.assertRaises(TypeError, lambda: x/a)
232 self.assertRaises(TypeError, lambda: a // x)
233 self.assertRaises(TypeError, lambda: x // a)
234
235 # Divison of int by timedelta doesn't make sense.
236 # Division by zero doesn't make sense.
237 for zero in 0, 0L:
238 self.assertRaises(TypeError, lambda: zero // a)
239 self.assertRaises(ZeroDivisionError, lambda: a // zero)
240
241 def test_basic_attributes(self):
242 days, seconds, us = 1, 7, 31
243 td = timedelta(days, seconds, us)
244 self.assertEqual(td.days, days)
245 self.assertEqual(td.seconds, seconds)
246 self.assertEqual(td.microseconds, us)
247
248 def test_carries(self):
249 t1 = timedelta(days=100,
250 weeks=-7,
251 hours=-24*(100-49),
252 minutes=-3,
253 seconds=12,
254 microseconds=(3*60 - 12) * 1e6 + 1)
255 t2 = timedelta(microseconds=1)
256 self.assertEqual(t1, t2)
257
258 def test_hash_equality(self):
259 t1 = timedelta(days=100,
260 weeks=-7,
261 hours=-24*(100-49),
262 minutes=-3,
263 seconds=12,
264 microseconds=(3*60 - 12) * 1000000)
265 t2 = timedelta()
266 self.assertEqual(hash(t1), hash(t2))
267
268 t1 += timedelta(weeks=7)
269 t2 += timedelta(days=7*7)
270 self.assertEqual(t1, t2)
271 self.assertEqual(hash(t1), hash(t2))
272
273 d = {t1: 1}
274 d[t2] = 2
275 self.assertEqual(len(d), 1)
276 self.assertEqual(d[t1], 2)
277
278 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000279 args = 12, 34, 56
280 orig = timedelta(*args)
281 state = orig.__getstate__()
282 self.assertEqual(args, state)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000283 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000284 green = pickler.dumps(orig, proto)
285 derived = unpickler.loads(green)
286 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000287
288 def test_compare(self):
289 t1 = timedelta(2, 3, 4)
290 t2 = timedelta(2, 3, 4)
291 self.failUnless(t1 == t2)
292 self.failUnless(t1 <= t2)
293 self.failUnless(t1 >= t2)
294 self.failUnless(not t1 != t2)
295 self.failUnless(not t1 < t2)
296 self.failUnless(not t1 > t2)
297 self.assertEqual(cmp(t1, t2), 0)
298 self.assertEqual(cmp(t2, t1), 0)
299
300 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
301 t2 = timedelta(*args) # this is larger than t1
302 self.failUnless(t1 < t2)
303 self.failUnless(t2 > t1)
304 self.failUnless(t1 <= t2)
305 self.failUnless(t2 >= t1)
306 self.failUnless(t1 != t2)
307 self.failUnless(t2 != t1)
308 self.failUnless(not t1 == t2)
309 self.failUnless(not t2 == t1)
310 self.failUnless(not t1 > t2)
311 self.failUnless(not t2 < t1)
312 self.failUnless(not t1 >= t2)
313 self.failUnless(not t2 <= t1)
314 self.assertEqual(cmp(t1, t2), -1)
315 self.assertEqual(cmp(t2, t1), 1)
316
317 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
318 self.assertRaises(TypeError, lambda: t1 == badarg)
319 self.assertRaises(TypeError, lambda: t1 != badarg)
320 self.assertRaises(TypeError, lambda: t1 <= badarg)
321 self.assertRaises(TypeError, lambda: t1 < badarg)
322 self.assertRaises(TypeError, lambda: t1 > badarg)
323 self.assertRaises(TypeError, lambda: t1 >= badarg)
324 self.assertRaises(TypeError, lambda: badarg == t1)
325 self.assertRaises(TypeError, lambda: badarg != t1)
326 self.assertRaises(TypeError, lambda: badarg <= t1)
327 self.assertRaises(TypeError, lambda: badarg < t1)
328 self.assertRaises(TypeError, lambda: badarg > t1)
329 self.assertRaises(TypeError, lambda: badarg >= t1)
330
331 def test_str(self):
332 td = timedelta
333 eq = self.assertEqual
334
335 eq(str(td(1)), "1 day, 0:00:00")
336 eq(str(td(-1)), "-1 day, 0:00:00")
337 eq(str(td(2)), "2 days, 0:00:00")
338 eq(str(td(-2)), "-2 days, 0:00:00")
339
340 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
341 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
342 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
343 "-210 days, 23:12:34")
344
345 eq(str(td(milliseconds=1)), "0:00:00.001000")
346 eq(str(td(microseconds=3)), "0:00:00.000003")
347
348 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
349 microseconds=999999)),
350 "999999999 days, 23:59:59.999999")
351
352 def test_roundtrip(self):
353 for td in (timedelta(days=999999999, hours=23, minutes=59,
354 seconds=59, microseconds=999999),
355 timedelta(days=-999999999),
356 timedelta(days=1, seconds=2, microseconds=3)):
357
358 # Verify td -> string -> td identity.
359 s = repr(td)
360 self.failUnless(s.startswith('datetime.'))
361 s = s[9:]
362 td2 = eval(s)
363 self.assertEqual(td, td2)
364
365 # Verify identity via reconstructing from pieces.
366 td2 = timedelta(td.days, td.seconds, td.microseconds)
367 self.assertEqual(td, td2)
368
369 def test_resolution_info(self):
370 self.assert_(isinstance(timedelta.min, timedelta))
371 self.assert_(isinstance(timedelta.max, timedelta))
372 self.assert_(isinstance(timedelta.resolution, timedelta))
373 self.assert_(timedelta.max > timedelta.min)
374 self.assertEqual(timedelta.min, timedelta(-999999999))
375 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
376 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
377
378 def test_overflow(self):
379 tiny = timedelta.resolution
380
381 td = timedelta.min + tiny
382 td -= tiny # no problem
383 self.assertRaises(OverflowError, td.__sub__, tiny)
384 self.assertRaises(OverflowError, td.__add__, -tiny)
385
386 td = timedelta.max - tiny
387 td += tiny # no problem
388 self.assertRaises(OverflowError, td.__add__, tiny)
389 self.assertRaises(OverflowError, td.__sub__, -tiny)
390
391 self.assertRaises(OverflowError, lambda: -timedelta.max)
392
393 def test_microsecond_rounding(self):
394 td = timedelta
395 eq = self.assertEqual
396
397 # Single-field rounding.
398 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
399 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
400 eq(td(milliseconds=0.6/1000), td(microseconds=1))
401 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
402
403 # Rounding due to contributions from more than one field.
404 us_per_hour = 3600e6
405 us_per_day = us_per_hour * 24
406 eq(td(days=.4/us_per_day), td(0))
407 eq(td(hours=.2/us_per_hour), td(0))
408 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
409
410 eq(td(days=-.4/us_per_day), td(0))
411 eq(td(hours=-.2/us_per_hour), td(0))
412 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
413
414 def test_massive_normalization(self):
415 td = timedelta(microseconds=-1)
416 self.assertEqual((td.days, td.seconds, td.microseconds),
417 (-1, 24*3600-1, 999999))
418
419 def test_bool(self):
420 self.failUnless(timedelta(1))
421 self.failUnless(timedelta(0, 1))
422 self.failUnless(timedelta(0, 0, 1))
423 self.failUnless(timedelta(microseconds=1))
424 self.failUnless(not timedelta(0))
425
426#############################################################################
427# date tests
428
429class TestDateOnly(unittest.TestCase):
430 # Tests here won't pass if also run on datetime objects, so don't
431 # subclass this to test datetimes too.
432
433 def test_delta_non_days_ignored(self):
434 dt = date(2000, 1, 2)
435 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
436 microseconds=5)
437 days = timedelta(delta.days)
438 self.assertEqual(days, timedelta(1))
439
440 dt2 = dt + delta
441 self.assertEqual(dt2, dt + days)
442
443 dt2 = delta + dt
444 self.assertEqual(dt2, dt + days)
445
446 dt2 = dt - delta
447 self.assertEqual(dt2, dt - days)
448
449 delta = -delta
450 days = timedelta(delta.days)
451 self.assertEqual(days, timedelta(-2))
452
453 dt2 = dt + delta
454 self.assertEqual(dt2, dt + days)
455
456 dt2 = delta + dt
457 self.assertEqual(dt2, dt + days)
458
459 dt2 = dt - delta
460 self.assertEqual(dt2, dt - days)
461
462class TestDate(unittest.TestCase):
463 # Tests here should pass for both dates and datetimes, except for a
464 # few tests that TestDateTime overrides.
465
466 theclass = date
467
468 def test_basic_attributes(self):
469 dt = self.theclass(2002, 3, 1)
470 self.assertEqual(dt.year, 2002)
471 self.assertEqual(dt.month, 3)
472 self.assertEqual(dt.day, 1)
473
474 def test_roundtrip(self):
475 for dt in (self.theclass(1, 2, 3),
476 self.theclass.today()):
477 # Verify dt -> string -> date identity.
478 s = repr(dt)
479 self.failUnless(s.startswith('datetime.'))
480 s = s[9:]
481 dt2 = eval(s)
482 self.assertEqual(dt, dt2)
483
484 # Verify identity via reconstructing from pieces.
485 dt2 = self.theclass(dt.year, dt.month, dt.day)
486 self.assertEqual(dt, dt2)
487
488 def test_ordinal_conversions(self):
489 # Check some fixed values.
490 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
491 (1, 12, 31, 365),
492 (2, 1, 1, 366),
493 # first example from "Calendrical Calculations"
494 (1945, 11, 12, 710347)]:
495 d = self.theclass(y, m, d)
496 self.assertEqual(n, d.toordinal())
497 fromord = self.theclass.fromordinal(n)
498 self.assertEqual(d, fromord)
499 if hasattr(fromord, "hour"):
500 # if we're checking something fancier than a date, verify
501 # the extra fields have been zeroed out
502 self.assertEqual(fromord.hour, 0)
503 self.assertEqual(fromord.minute, 0)
504 self.assertEqual(fromord.second, 0)
505 self.assertEqual(fromord.microsecond, 0)
506
Tim Peters0bf60bd2003-01-08 20:40:01 +0000507 # Check first and last days of year spottily across the whole
508 # range of years supported.
509 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000510 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
511 d = self.theclass(year, 1, 1)
512 n = d.toordinal()
513 d2 = self.theclass.fromordinal(n)
514 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000515 # Verify that moving back a day gets to the end of year-1.
516 if year > 1:
517 d = self.theclass.fromordinal(n-1)
518 d2 = self.theclass(year-1, 12, 31)
519 self.assertEqual(d, d2)
520 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000521
522 # Test every day in a leap-year and a non-leap year.
523 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
524 for year, isleap in (2000, True), (2002, False):
525 n = self.theclass(year, 1, 1).toordinal()
526 for month, maxday in zip(range(1, 13), dim):
527 if month == 2 and isleap:
528 maxday += 1
529 for day in range(1, maxday+1):
530 d = self.theclass(year, month, day)
531 self.assertEqual(d.toordinal(), n)
532 self.assertEqual(d, self.theclass.fromordinal(n))
533 n += 1
534
535 def test_extreme_ordinals(self):
536 a = self.theclass.min
537 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
538 aord = a.toordinal()
539 b = a.fromordinal(aord)
540 self.assertEqual(a, b)
541
542 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
543
544 b = a + timedelta(days=1)
545 self.assertEqual(b.toordinal(), aord + 1)
546 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
547
548 a = self.theclass.max
549 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
550 aord = a.toordinal()
551 b = a.fromordinal(aord)
552 self.assertEqual(a, b)
553
554 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
555
556 b = a - timedelta(days=1)
557 self.assertEqual(b.toordinal(), aord - 1)
558 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
559
560 def test_bad_constructor_arguments(self):
561 # bad years
562 self.theclass(MINYEAR, 1, 1) # no exception
563 self.theclass(MAXYEAR, 1, 1) # no exception
564 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
565 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
566 # bad months
567 self.theclass(2000, 1, 1) # no exception
568 self.theclass(2000, 12, 1) # no exception
569 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
570 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
571 # bad days
572 self.theclass(2000, 2, 29) # no exception
573 self.theclass(2004, 2, 29) # no exception
574 self.theclass(2400, 2, 29) # no exception
575 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
576 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
577 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
578 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
579 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
580 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
581
582 def test_hash_equality(self):
583 d = self.theclass(2000, 12, 31)
584 # same thing
585 e = self.theclass(2000, 12, 31)
586 self.assertEqual(d, e)
587 self.assertEqual(hash(d), hash(e))
588
589 dic = {d: 1}
590 dic[e] = 2
591 self.assertEqual(len(dic), 1)
592 self.assertEqual(dic[d], 2)
593 self.assertEqual(dic[e], 2)
594
595 d = self.theclass(2001, 1, 1)
596 # same thing
597 e = self.theclass(2001, 1, 1)
598 self.assertEqual(d, e)
599 self.assertEqual(hash(d), hash(e))
600
601 dic = {d: 1}
602 dic[e] = 2
603 self.assertEqual(len(dic), 1)
604 self.assertEqual(dic[d], 2)
605 self.assertEqual(dic[e], 2)
606
607 def test_computations(self):
608 a = self.theclass(2002, 1, 31)
609 b = self.theclass(1956, 1, 31)
610
611 diff = a-b
612 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
613 self.assertEqual(diff.seconds, 0)
614 self.assertEqual(diff.microseconds, 0)
615
616 day = timedelta(1)
617 week = timedelta(7)
618 a = self.theclass(2002, 3, 2)
619 self.assertEqual(a + day, self.theclass(2002, 3, 3))
620 self.assertEqual(day + a, self.theclass(2002, 3, 3))
621 self.assertEqual(a - day, self.theclass(2002, 3, 1))
622 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
623 self.assertEqual(a + week, self.theclass(2002, 3, 9))
624 self.assertEqual(a - week, self.theclass(2002, 2, 23))
625 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
626 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
627 self.assertEqual((a + week) - a, week)
628 self.assertEqual((a + day) - a, day)
629 self.assertEqual((a - week) - a, -week)
630 self.assertEqual((a - day) - a, -day)
631 self.assertEqual(a - (a + week), -week)
632 self.assertEqual(a - (a + day), -day)
633 self.assertEqual(a - (a - week), week)
634 self.assertEqual(a - (a - day), day)
635
636 # Add/sub ints, longs, floats should be illegal
637 for i in 1, 1L, 1.0:
638 self.assertRaises(TypeError, lambda: a+i)
639 self.assertRaises(TypeError, lambda: a-i)
640 self.assertRaises(TypeError, lambda: i+a)
641 self.assertRaises(TypeError, lambda: i-a)
642
643 # delta - date is senseless.
644 self.assertRaises(TypeError, lambda: day - a)
645 # mixing date and (delta or date) via * or // is senseless
646 self.assertRaises(TypeError, lambda: day * a)
647 self.assertRaises(TypeError, lambda: a * day)
648 self.assertRaises(TypeError, lambda: day // a)
649 self.assertRaises(TypeError, lambda: a // day)
650 self.assertRaises(TypeError, lambda: a * a)
651 self.assertRaises(TypeError, lambda: a // a)
652 # date + date is senseless
653 self.assertRaises(TypeError, lambda: a + a)
654
655 def test_overflow(self):
656 tiny = self.theclass.resolution
657
658 dt = self.theclass.min + tiny
659 dt -= tiny # no problem
660 self.assertRaises(OverflowError, dt.__sub__, tiny)
661 self.assertRaises(OverflowError, dt.__add__, -tiny)
662
663 dt = self.theclass.max - tiny
664 dt += tiny # no problem
665 self.assertRaises(OverflowError, dt.__add__, tiny)
666 self.assertRaises(OverflowError, dt.__sub__, -tiny)
667
668 def test_fromtimestamp(self):
669 import time
670
671 # Try an arbitrary fixed value.
672 year, month, day = 1999, 9, 19
673 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
674 d = self.theclass.fromtimestamp(ts)
675 self.assertEqual(d.year, year)
676 self.assertEqual(d.month, month)
677 self.assertEqual(d.day, day)
678
679 def test_today(self):
680 import time
681
682 # We claim that today() is like fromtimestamp(time.time()), so
683 # prove it.
684 for dummy in range(3):
685 today = self.theclass.today()
686 ts = time.time()
687 todayagain = self.theclass.fromtimestamp(ts)
688 if today == todayagain:
689 break
690 # There are several legit reasons that could fail:
691 # 1. It recently became midnight, between the today() and the
692 # time() calls.
693 # 2. The platform time() has such fine resolution that we'll
694 # never get the same value twice.
695 # 3. The platform time() has poor resolution, and we just
696 # happened to call today() right before a resolution quantum
697 # boundary.
698 # 4. The system clock got fiddled between calls.
699 # In any case, wait a little while and try again.
700 time.sleep(0.1)
701
702 # It worked or it didn't. If it didn't, assume it's reason #2, and
703 # let the test pass if they're within half a second of each other.
704 self.failUnless(today == todayagain or
705 abs(todayagain - today) < timedelta(seconds=0.5))
706
707 def test_weekday(self):
708 for i in range(7):
709 # March 4, 2002 is a Monday
710 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
711 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
712 # January 2, 1956 is a Monday
713 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
714 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
715
716 def test_isocalendar(self):
717 # Check examples from
718 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
719 for i in range(7):
720 d = self.theclass(2003, 12, 22+i)
721 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
722 d = self.theclass(2003, 12, 29) + timedelta(i)
723 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
724 d = self.theclass(2004, 1, 5+i)
725 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
726 d = self.theclass(2009, 12, 21+i)
727 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
728 d = self.theclass(2009, 12, 28) + timedelta(i)
729 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
730 d = self.theclass(2010, 1, 4+i)
731 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
732
733 def test_iso_long_years(self):
734 # Calculate long ISO years and compare to table from
735 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
736 ISO_LONG_YEARS_TABLE = """
737 4 32 60 88
738 9 37 65 93
739 15 43 71 99
740 20 48 76
741 26 54 82
742
743 105 133 161 189
744 111 139 167 195
745 116 144 172
746 122 150 178
747 128 156 184
748
749 201 229 257 285
750 207 235 263 291
751 212 240 268 296
752 218 246 274
753 224 252 280
754
755 303 331 359 387
756 308 336 364 392
757 314 342 370 398
758 320 348 376
759 325 353 381
760 """
761 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
762 iso_long_years.sort()
763 L = []
764 for i in range(400):
765 d = self.theclass(2000+i, 12, 31)
766 d1 = self.theclass(1600+i, 12, 31)
767 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
768 if d.isocalendar()[1] == 53:
769 L.append(i)
770 self.assertEqual(L, iso_long_years)
771
772 def test_isoformat(self):
773 t = self.theclass(2, 3, 2)
774 self.assertEqual(t.isoformat(), "0002-03-02")
775
776 def test_ctime(self):
777 t = self.theclass(2002, 3, 2)
778 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
779
780 def test_strftime(self):
781 t = self.theclass(2005, 3, 2)
782 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
783
784 self.assertRaises(TypeError, t.strftime) # needs an arg
785 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
786 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
787
788 # A naive object replaces %z and %Z w/ empty strings.
789 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
790
791 def test_resolution_info(self):
792 self.assert_(isinstance(self.theclass.min, self.theclass))
793 self.assert_(isinstance(self.theclass.max, self.theclass))
794 self.assert_(isinstance(self.theclass.resolution, timedelta))
795 self.assert_(self.theclass.max > self.theclass.min)
796
797 def test_extreme_timedelta(self):
798 big = self.theclass.max - self.theclass.min
799 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
800 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
801 # n == 315537897599999999 ~= 2**58.13
802 justasbig = timedelta(0, 0, n)
803 self.assertEqual(big, justasbig)
804 self.assertEqual(self.theclass.min + big, self.theclass.max)
805 self.assertEqual(self.theclass.max - big, self.theclass.min)
806
807 def test_timetuple(self):
808 for i in range(7):
809 # January 2, 1956 is a Monday (0)
810 d = self.theclass(1956, 1, 2+i)
811 t = d.timetuple()
812 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
813 # February 1, 1956 is a Wednesday (2)
814 d = self.theclass(1956, 2, 1+i)
815 t = d.timetuple()
816 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
817 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
818 # of the year.
819 d = self.theclass(1956, 3, 1+i)
820 t = d.timetuple()
821 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
822 self.assertEqual(t.tm_year, 1956)
823 self.assertEqual(t.tm_mon, 3)
824 self.assertEqual(t.tm_mday, 1+i)
825 self.assertEqual(t.tm_hour, 0)
826 self.assertEqual(t.tm_min, 0)
827 self.assertEqual(t.tm_sec, 0)
828 self.assertEqual(t.tm_wday, (3+i)%7)
829 self.assertEqual(t.tm_yday, 61+i)
830 self.assertEqual(t.tm_isdst, -1)
831
832 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000833 args = 6, 7, 23
834 orig = self.theclass(*args)
835 state = orig.__getstate__()
Guido van Rossum177e41a2003-01-30 22:06:23 +0000836 self.assertEqual(state, ('\x00\x06\x07\x17',), self.theclass)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000837 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000838 green = pickler.dumps(orig, proto)
839 derived = unpickler.loads(green)
840 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000841
842 def test_compare(self):
843 t1 = self.theclass(2, 3, 4)
844 t2 = self.theclass(2, 3, 4)
845 self.failUnless(t1 == t2)
846 self.failUnless(t1 <= t2)
847 self.failUnless(t1 >= t2)
848 self.failUnless(not t1 != t2)
849 self.failUnless(not t1 < t2)
850 self.failUnless(not t1 > t2)
851 self.assertEqual(cmp(t1, t2), 0)
852 self.assertEqual(cmp(t2, t1), 0)
853
854 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
855 t2 = self.theclass(*args) # this is larger than t1
856 self.failUnless(t1 < t2)
857 self.failUnless(t2 > t1)
858 self.failUnless(t1 <= t2)
859 self.failUnless(t2 >= t1)
860 self.failUnless(t1 != t2)
861 self.failUnless(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.failUnless(not t1 >= t2)
867 self.failUnless(not t2 <= t1)
868 self.assertEqual(cmp(t1, t2), -1)
869 self.assertEqual(cmp(t2, t1), 1)
870
871 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
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: t1 > badarg)
877 self.assertRaises(TypeError, lambda: t1 >= badarg)
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 self.assertRaises(TypeError, lambda: badarg > t1)
883 self.assertRaises(TypeError, lambda: badarg >= t1)
884
Tim Peters8d81a012003-01-24 22:36:34 +0000885 def test_mixed_compare(self):
886 our = self.theclass(2000, 4, 5)
887 self.assertRaises(TypeError, cmp, our, 1)
888 self.assertRaises(TypeError, cmp, 1, our)
889
890 class AnotherDateTimeClass(object):
891 def __cmp__(self, other):
892 # Return "equal" so calling this can't be confused with
893 # compare-by-address (which never says "equal" for distinct
894 # objects).
895 return 0
896
897 # This still errors, because date and datetime comparison raise
898 # TypeError instead of NotImplemented when they don't know what to
899 # do, in order to stop comparison from falling back to the default
900 # compare-by-address.
901 their = AnotherDateTimeClass()
902 self.assertRaises(TypeError, cmp, our, their)
903 # Oops: The next stab raises TypeError in the C implementation,
904 # but not in the Python implementation of datetime. The difference
905 # is due to that the Python implementation defines __cmp__ but
906 # the C implementation defines tp_richcompare. This is more pain
907 # to fix than it's worth, so commenting out the test.
908 # self.assertEqual(cmp(their, our), 0)
909
910 # But date and datetime comparison return NotImplemented instead if the
911 # other object has a timetuple attr. This gives the other object a
912 # chance to do the comparison.
913 class Comparable(AnotherDateTimeClass):
914 def timetuple(self):
915 return ()
916
917 their = Comparable()
918 self.assertEqual(cmp(our, their), 0)
919 self.assertEqual(cmp(their, our), 0)
920 self.failUnless(our == their)
921 self.failUnless(their == our)
922
Tim Peters2a799bf2002-12-16 20:18:38 +0000923 def test_bool(self):
924 # All dates are considered true.
925 self.failUnless(self.theclass.min)
926 self.failUnless(self.theclass.max)
927
Tim Petersd6844152002-12-22 20:58:42 +0000928 def test_srftime_out_of_range(self):
929 # For nasty technical reasons, we can't handle years before 1900.
930 cls = self.theclass
931 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
932 for y in 1, 49, 51, 99, 100, 1000, 1899:
933 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000934
935 def test_replace(self):
936 cls = self.theclass
937 args = [1, 2, 3]
938 base = cls(*args)
939 self.assertEqual(base, base.replace())
940
941 i = 0
942 for name, newval in (("year", 2),
943 ("month", 3),
944 ("day", 4)):
945 newargs = args[:]
946 newargs[i] = newval
947 expected = cls(*newargs)
948 got = base.replace(**{name: newval})
949 self.assertEqual(expected, got)
950 i += 1
951
952 # Out of bounds.
953 base = cls(2000, 2, 29)
954 self.assertRaises(ValueError, base.replace, year=2001)
955
Tim Peters2a799bf2002-12-16 20:18:38 +0000956#############################################################################
957# datetime tests
958
959class TestDateTime(TestDate):
960
961 theclass = datetime
962
963 def test_basic_attributes(self):
964 dt = self.theclass(2002, 3, 1, 12, 0)
965 self.assertEqual(dt.year, 2002)
966 self.assertEqual(dt.month, 3)
967 self.assertEqual(dt.day, 1)
968 self.assertEqual(dt.hour, 12)
969 self.assertEqual(dt.minute, 0)
970 self.assertEqual(dt.second, 0)
971 self.assertEqual(dt.microsecond, 0)
972
973 def test_basic_attributes_nonzero(self):
974 # Make sure all attributes are non-zero so bugs in
975 # bit-shifting access show up.
976 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
977 self.assertEqual(dt.year, 2002)
978 self.assertEqual(dt.month, 3)
979 self.assertEqual(dt.day, 1)
980 self.assertEqual(dt.hour, 12)
981 self.assertEqual(dt.minute, 59)
982 self.assertEqual(dt.second, 59)
983 self.assertEqual(dt.microsecond, 8000)
984
985 def test_roundtrip(self):
986 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
987 self.theclass.now()):
988 # Verify dt -> string -> datetime identity.
989 s = repr(dt)
990 self.failUnless(s.startswith('datetime.'))
991 s = s[9:]
992 dt2 = eval(s)
993 self.assertEqual(dt, dt2)
994
995 # Verify identity via reconstructing from pieces.
996 dt2 = self.theclass(dt.year, dt.month, dt.day,
997 dt.hour, dt.minute, dt.second,
998 dt.microsecond)
999 self.assertEqual(dt, dt2)
1000
1001 def test_isoformat(self):
1002 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1003 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1004 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1005 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1006 # str is ISO format with the separator forced to a blank.
1007 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1008
1009 t = self.theclass(2, 3, 2)
1010 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1011 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1012 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1013 # str is ISO format with the separator forced to a blank.
1014 self.assertEqual(str(t), "0002-03-02 00:00:00")
1015
1016 def test_more_ctime(self):
1017 # Test fields that TestDate doesn't touch.
1018 import time
1019
1020 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1021 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1022 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1023 # out. The difference is that t.ctime() produces " 2" for the day,
1024 # but platform ctime() produces "02" for the day. According to
1025 # C99, t.ctime() is correct here.
1026 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1027
1028 # So test a case where that difference doesn't matter.
1029 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1030 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1031
1032 def test_tz_independent_comparing(self):
1033 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1034 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1035 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1036 self.assertEqual(dt1, dt3)
1037 self.assert_(dt2 > dt3)
1038
1039 # Make sure comparison doesn't forget microseconds, and isn't done
1040 # via comparing a float timestamp (an IEEE double doesn't have enough
1041 # precision to span microsecond resolution across years 1 thru 9999,
1042 # so comparing via timestamp necessarily calls some distinct values
1043 # equal).
1044 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1045 us = timedelta(microseconds=1)
1046 dt2 = dt1 + us
1047 self.assertEqual(dt2 - dt1, us)
1048 self.assert_(dt1 < dt2)
1049
1050 def test_bad_constructor_arguments(self):
1051 # bad years
1052 self.theclass(MINYEAR, 1, 1) # no exception
1053 self.theclass(MAXYEAR, 1, 1) # no exception
1054 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1055 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1056 # bad months
1057 self.theclass(2000, 1, 1) # no exception
1058 self.theclass(2000, 12, 1) # no exception
1059 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1060 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1061 # bad days
1062 self.theclass(2000, 2, 29) # no exception
1063 self.theclass(2004, 2, 29) # no exception
1064 self.theclass(2400, 2, 29) # no exception
1065 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1066 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1067 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1068 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1069 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1070 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1071 # bad hours
1072 self.theclass(2000, 1, 31, 0) # no exception
1073 self.theclass(2000, 1, 31, 23) # no exception
1074 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1075 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1076 # bad minutes
1077 self.theclass(2000, 1, 31, 23, 0) # no exception
1078 self.theclass(2000, 1, 31, 23, 59) # no exception
1079 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1080 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1081 # bad seconds
1082 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1083 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1084 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1085 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1086 # bad microseconds
1087 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1088 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1089 self.assertRaises(ValueError, self.theclass,
1090 2000, 1, 31, 23, 59, 59, -1)
1091 self.assertRaises(ValueError, self.theclass,
1092 2000, 1, 31, 23, 59, 59,
1093 1000000)
1094
1095 def test_hash_equality(self):
1096 d = self.theclass(2000, 12, 31, 23, 30, 17)
1097 e = self.theclass(2000, 12, 31, 23, 30, 17)
1098 self.assertEqual(d, e)
1099 self.assertEqual(hash(d), hash(e))
1100
1101 dic = {d: 1}
1102 dic[e] = 2
1103 self.assertEqual(len(dic), 1)
1104 self.assertEqual(dic[d], 2)
1105 self.assertEqual(dic[e], 2)
1106
1107 d = self.theclass(2001, 1, 1, 0, 5, 17)
1108 e = self.theclass(2001, 1, 1, 0, 5, 17)
1109 self.assertEqual(d, e)
1110 self.assertEqual(hash(d), hash(e))
1111
1112 dic = {d: 1}
1113 dic[e] = 2
1114 self.assertEqual(len(dic), 1)
1115 self.assertEqual(dic[d], 2)
1116 self.assertEqual(dic[e], 2)
1117
1118 def test_computations(self):
1119 a = self.theclass(2002, 1, 31)
1120 b = self.theclass(1956, 1, 31)
1121 diff = a-b
1122 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1123 self.assertEqual(diff.seconds, 0)
1124 self.assertEqual(diff.microseconds, 0)
1125 a = self.theclass(2002, 3, 2, 17, 6)
1126 millisec = timedelta(0, 0, 1000)
1127 hour = timedelta(0, 3600)
1128 day = timedelta(1)
1129 week = timedelta(7)
1130 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1131 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1132 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1133 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1134 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1135 self.assertEqual(a - hour, a + -hour)
1136 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1137 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1138 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1139 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1140 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1141 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1142 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1143 self.assertEqual((a + week) - a, week)
1144 self.assertEqual((a + day) - a, day)
1145 self.assertEqual((a + hour) - a, hour)
1146 self.assertEqual((a + millisec) - a, millisec)
1147 self.assertEqual((a - week) - a, -week)
1148 self.assertEqual((a - day) - a, -day)
1149 self.assertEqual((a - hour) - a, -hour)
1150 self.assertEqual((a - millisec) - a, -millisec)
1151 self.assertEqual(a - (a + week), -week)
1152 self.assertEqual(a - (a + day), -day)
1153 self.assertEqual(a - (a + hour), -hour)
1154 self.assertEqual(a - (a + millisec), -millisec)
1155 self.assertEqual(a - (a - week), week)
1156 self.assertEqual(a - (a - day), day)
1157 self.assertEqual(a - (a - hour), hour)
1158 self.assertEqual(a - (a - millisec), millisec)
1159 self.assertEqual(a + (week + day + hour + millisec),
1160 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1161 self.assertEqual(a + (week + day + hour + millisec),
1162 (((a + week) + day) + hour) + millisec)
1163 self.assertEqual(a - (week + day + hour + millisec),
1164 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1165 self.assertEqual(a - (week + day + hour + millisec),
1166 (((a - week) - day) - hour) - millisec)
1167 # Add/sub ints, longs, floats should be illegal
1168 for i in 1, 1L, 1.0:
1169 self.assertRaises(TypeError, lambda: a+i)
1170 self.assertRaises(TypeError, lambda: a-i)
1171 self.assertRaises(TypeError, lambda: i+a)
1172 self.assertRaises(TypeError, lambda: i-a)
1173
1174 # delta - datetime is senseless.
1175 self.assertRaises(TypeError, lambda: day - a)
1176 # mixing datetime and (delta or datetime) via * or // is senseless
1177 self.assertRaises(TypeError, lambda: day * a)
1178 self.assertRaises(TypeError, lambda: a * day)
1179 self.assertRaises(TypeError, lambda: day // a)
1180 self.assertRaises(TypeError, lambda: a // day)
1181 self.assertRaises(TypeError, lambda: a * a)
1182 self.assertRaises(TypeError, lambda: a // a)
1183 # datetime + datetime is senseless
1184 self.assertRaises(TypeError, lambda: a + a)
1185
1186 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001187 args = 6, 7, 23, 20, 59, 1, 64**2
1188 orig = self.theclass(*args)
1189 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001190 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Guido van Rossum177e41a2003-01-30 22:06:23 +00001191 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001192 green = pickler.dumps(orig, proto)
1193 derived = unpickler.loads(green)
1194 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001195
1196 def test_more_compare(self):
1197 # The test_compare() inherited from TestDate covers the error cases.
1198 # We just want to test lexicographic ordering on the members datetime
1199 # has that date lacks.
1200 args = [2000, 11, 29, 20, 58, 16, 999998]
1201 t1 = self.theclass(*args)
1202 t2 = self.theclass(*args)
1203 self.failUnless(t1 == t2)
1204 self.failUnless(t1 <= t2)
1205 self.failUnless(t1 >= t2)
1206 self.failUnless(not t1 != t2)
1207 self.failUnless(not t1 < t2)
1208 self.failUnless(not t1 > t2)
1209 self.assertEqual(cmp(t1, t2), 0)
1210 self.assertEqual(cmp(t2, t1), 0)
1211
1212 for i in range(len(args)):
1213 newargs = args[:]
1214 newargs[i] = args[i] + 1
1215 t2 = self.theclass(*newargs) # this is larger than t1
1216 self.failUnless(t1 < t2)
1217 self.failUnless(t2 > t1)
1218 self.failUnless(t1 <= t2)
1219 self.failUnless(t2 >= t1)
1220 self.failUnless(t1 != t2)
1221 self.failUnless(t2 != t1)
1222 self.failUnless(not t1 == t2)
1223 self.failUnless(not t2 == t1)
1224 self.failUnless(not t1 > t2)
1225 self.failUnless(not t2 < t1)
1226 self.failUnless(not t1 >= t2)
1227 self.failUnless(not t2 <= t1)
1228 self.assertEqual(cmp(t1, t2), -1)
1229 self.assertEqual(cmp(t2, t1), 1)
1230
1231
1232 # A helper for timestamp constructor tests.
1233 def verify_field_equality(self, expected, got):
1234 self.assertEqual(expected.tm_year, got.year)
1235 self.assertEqual(expected.tm_mon, got.month)
1236 self.assertEqual(expected.tm_mday, got.day)
1237 self.assertEqual(expected.tm_hour, got.hour)
1238 self.assertEqual(expected.tm_min, got.minute)
1239 self.assertEqual(expected.tm_sec, got.second)
1240
1241 def test_fromtimestamp(self):
1242 import time
1243
1244 ts = time.time()
1245 expected = time.localtime(ts)
1246 got = self.theclass.fromtimestamp(ts)
1247 self.verify_field_equality(expected, got)
1248
1249 def test_utcfromtimestamp(self):
1250 import time
1251
1252 ts = time.time()
1253 expected = time.gmtime(ts)
1254 got = self.theclass.utcfromtimestamp(ts)
1255 self.verify_field_equality(expected, got)
1256
1257 def test_utcnow(self):
1258 import time
1259
1260 # Call it a success if utcnow() and utcfromtimestamp() are within
1261 # a second of each other.
1262 tolerance = timedelta(seconds=1)
1263 for dummy in range(3):
1264 from_now = self.theclass.utcnow()
1265 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1266 if abs(from_timestamp - from_now) <= tolerance:
1267 break
1268 # Else try again a few times.
1269 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1270
1271 def test_more_timetuple(self):
1272 # This tests fields beyond those tested by the TestDate.test_timetuple.
1273 t = self.theclass(2004, 12, 31, 6, 22, 33)
1274 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1275 self.assertEqual(t.timetuple(),
1276 (t.year, t.month, t.day,
1277 t.hour, t.minute, t.second,
1278 t.weekday(),
1279 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1280 -1))
1281 tt = t.timetuple()
1282 self.assertEqual(tt.tm_year, t.year)
1283 self.assertEqual(tt.tm_mon, t.month)
1284 self.assertEqual(tt.tm_mday, t.day)
1285 self.assertEqual(tt.tm_hour, t.hour)
1286 self.assertEqual(tt.tm_min, t.minute)
1287 self.assertEqual(tt.tm_sec, t.second)
1288 self.assertEqual(tt.tm_wday, t.weekday())
1289 self.assertEqual(tt.tm_yday, t.toordinal() -
1290 date(t.year, 1, 1).toordinal() + 1)
1291 self.assertEqual(tt.tm_isdst, -1)
1292
1293 def test_more_strftime(self):
1294 # This tests fields beyond those tested by the TestDate.test_strftime.
1295 t = self.theclass(2004, 12, 31, 6, 22, 33)
1296 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1297 "12 31 04 33 22 06 366")
1298
1299 def test_extract(self):
1300 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1301 self.assertEqual(dt.date(), date(2002, 3, 4))
1302 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1303
1304 def test_combine(self):
1305 d = date(2002, 3, 4)
1306 t = time(18, 45, 3, 1234)
1307 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1308 combine = self.theclass.combine
1309 dt = combine(d, t)
1310 self.assertEqual(dt, expected)
1311
1312 dt = combine(time=t, date=d)
1313 self.assertEqual(dt, expected)
1314
1315 self.assertEqual(d, dt.date())
1316 self.assertEqual(t, dt.time())
1317 self.assertEqual(dt, combine(dt.date(), dt.time()))
1318
1319 self.assertRaises(TypeError, combine) # need an arg
1320 self.assertRaises(TypeError, combine, d) # need two args
1321 self.assertRaises(TypeError, combine, t, d) # args reversed
1322 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1323 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1324
Tim Peters12bf3392002-12-24 05:41:27 +00001325 def test_replace(self):
1326 cls = self.theclass
1327 args = [1, 2, 3, 4, 5, 6, 7]
1328 base = cls(*args)
1329 self.assertEqual(base, base.replace())
1330
1331 i = 0
1332 for name, newval in (("year", 2),
1333 ("month", 3),
1334 ("day", 4),
1335 ("hour", 5),
1336 ("minute", 6),
1337 ("second", 7),
1338 ("microsecond", 8)):
1339 newargs = args[:]
1340 newargs[i] = newval
1341 expected = cls(*newargs)
1342 got = base.replace(**{name: newval})
1343 self.assertEqual(expected, got)
1344 i += 1
1345
1346 # Out of bounds.
1347 base = cls(2000, 2, 29)
1348 self.assertRaises(ValueError, base.replace, year=2001)
1349
Tim Peters80475bb2002-12-25 07:40:55 +00001350 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001351 # Pretty boring! The TZ test is more interesting here. astimezone()
1352 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001353 dt = self.theclass.now()
1354 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001355 self.assertRaises(TypeError, dt.astimezone) # not enough args
1356 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1357 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001358 self.assertRaises(ValueError, dt.astimezone, f) # naive
1359 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001360
Tim Peters52dcce22003-01-23 16:36:11 +00001361 class Bogus(tzinfo):
1362 def utcoffset(self, dt): return None
1363 def dst(self, dt): return timedelta(0)
1364 bog = Bogus()
1365 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1366
1367 class AlsoBogus(tzinfo):
1368 def utcoffset(self, dt): return timedelta(0)
1369 def dst(self, dt): return None
1370 alsobog = AlsoBogus()
1371 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001372
Tim Peters2a799bf2002-12-16 20:18:38 +00001373class TestTime(unittest.TestCase):
1374
1375 theclass = time
1376
1377 def test_basic_attributes(self):
1378 t = self.theclass(12, 0)
1379 self.assertEqual(t.hour, 12)
1380 self.assertEqual(t.minute, 0)
1381 self.assertEqual(t.second, 0)
1382 self.assertEqual(t.microsecond, 0)
1383
1384 def test_basic_attributes_nonzero(self):
1385 # Make sure all attributes are non-zero so bugs in
1386 # bit-shifting access show up.
1387 t = self.theclass(12, 59, 59, 8000)
1388 self.assertEqual(t.hour, 12)
1389 self.assertEqual(t.minute, 59)
1390 self.assertEqual(t.second, 59)
1391 self.assertEqual(t.microsecond, 8000)
1392
1393 def test_roundtrip(self):
1394 t = self.theclass(1, 2, 3, 4)
1395
1396 # Verify t -> string -> time identity.
1397 s = repr(t)
1398 self.failUnless(s.startswith('datetime.'))
1399 s = s[9:]
1400 t2 = eval(s)
1401 self.assertEqual(t, t2)
1402
1403 # Verify identity via reconstructing from pieces.
1404 t2 = self.theclass(t.hour, t.minute, t.second,
1405 t.microsecond)
1406 self.assertEqual(t, t2)
1407
1408 def test_comparing(self):
1409 args = [1, 2, 3, 4]
1410 t1 = self.theclass(*args)
1411 t2 = self.theclass(*args)
1412 self.failUnless(t1 == t2)
1413 self.failUnless(t1 <= t2)
1414 self.failUnless(t1 >= t2)
1415 self.failUnless(not t1 != t2)
1416 self.failUnless(not t1 < t2)
1417 self.failUnless(not t1 > t2)
1418 self.assertEqual(cmp(t1, t2), 0)
1419 self.assertEqual(cmp(t2, t1), 0)
1420
1421 for i in range(len(args)):
1422 newargs = args[:]
1423 newargs[i] = args[i] + 1
1424 t2 = self.theclass(*newargs) # this is larger than t1
1425 self.failUnless(t1 < t2)
1426 self.failUnless(t2 > t1)
1427 self.failUnless(t1 <= t2)
1428 self.failUnless(t2 >= t1)
1429 self.failUnless(t1 != t2)
1430 self.failUnless(t2 != t1)
1431 self.failUnless(not t1 == t2)
1432 self.failUnless(not t2 == t1)
1433 self.failUnless(not t1 > t2)
1434 self.failUnless(not t2 < t1)
1435 self.failUnless(not t1 >= t2)
1436 self.failUnless(not t2 <= t1)
1437 self.assertEqual(cmp(t1, t2), -1)
1438 self.assertEqual(cmp(t2, t1), 1)
1439
Tim Peters0bf60bd2003-01-08 20:40:01 +00001440 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1441 if CMP_BUG_FIXED:
1442 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1443 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001444 self.assertRaises(TypeError, lambda: t1 == badarg)
1445 self.assertRaises(TypeError, lambda: t1 != badarg)
1446 self.assertRaises(TypeError, lambda: t1 <= badarg)
1447 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: badarg == t1)
1451 self.assertRaises(TypeError, lambda: badarg != t1)
1452 self.assertRaises(TypeError, lambda: badarg <= t1)
1453 self.assertRaises(TypeError, lambda: badarg < t1)
1454 self.assertRaises(TypeError, lambda: badarg > t1)
1455 self.assertRaises(TypeError, lambda: badarg >= t1)
1456
1457 def test_bad_constructor_arguments(self):
1458 # bad hours
1459 self.theclass(0, 0) # no exception
1460 self.theclass(23, 0) # no exception
1461 self.assertRaises(ValueError, self.theclass, -1, 0)
1462 self.assertRaises(ValueError, self.theclass, 24, 0)
1463 # bad minutes
1464 self.theclass(23, 0) # no exception
1465 self.theclass(23, 59) # no exception
1466 self.assertRaises(ValueError, self.theclass, 23, -1)
1467 self.assertRaises(ValueError, self.theclass, 23, 60)
1468 # bad seconds
1469 self.theclass(23, 59, 0) # no exception
1470 self.theclass(23, 59, 59) # no exception
1471 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1472 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1473 # bad microseconds
1474 self.theclass(23, 59, 59, 0) # no exception
1475 self.theclass(23, 59, 59, 999999) # no exception
1476 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1477 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1478
1479 def test_hash_equality(self):
1480 d = self.theclass(23, 30, 17)
1481 e = self.theclass(23, 30, 17)
1482 self.assertEqual(d, e)
1483 self.assertEqual(hash(d), hash(e))
1484
1485 dic = {d: 1}
1486 dic[e] = 2
1487 self.assertEqual(len(dic), 1)
1488 self.assertEqual(dic[d], 2)
1489 self.assertEqual(dic[e], 2)
1490
1491 d = self.theclass(0, 5, 17)
1492 e = self.theclass(0, 5, 17)
1493 self.assertEqual(d, e)
1494 self.assertEqual(hash(d), hash(e))
1495
1496 dic = {d: 1}
1497 dic[e] = 2
1498 self.assertEqual(len(dic), 1)
1499 self.assertEqual(dic[d], 2)
1500 self.assertEqual(dic[e], 2)
1501
1502 def test_isoformat(self):
1503 t = self.theclass(4, 5, 1, 123)
1504 self.assertEqual(t.isoformat(), "04:05:01.000123")
1505 self.assertEqual(t.isoformat(), str(t))
1506
1507 t = self.theclass()
1508 self.assertEqual(t.isoformat(), "00:00:00")
1509 self.assertEqual(t.isoformat(), str(t))
1510
1511 t = self.theclass(microsecond=1)
1512 self.assertEqual(t.isoformat(), "00:00:00.000001")
1513 self.assertEqual(t.isoformat(), str(t))
1514
1515 t = self.theclass(microsecond=10)
1516 self.assertEqual(t.isoformat(), "00:00:00.000010")
1517 self.assertEqual(t.isoformat(), str(t))
1518
1519 t = self.theclass(microsecond=100)
1520 self.assertEqual(t.isoformat(), "00:00:00.000100")
1521 self.assertEqual(t.isoformat(), str(t))
1522
1523 t = self.theclass(microsecond=1000)
1524 self.assertEqual(t.isoformat(), "00:00:00.001000")
1525 self.assertEqual(t.isoformat(), str(t))
1526
1527 t = self.theclass(microsecond=10000)
1528 self.assertEqual(t.isoformat(), "00:00:00.010000")
1529 self.assertEqual(t.isoformat(), str(t))
1530
1531 t = self.theclass(microsecond=100000)
1532 self.assertEqual(t.isoformat(), "00:00:00.100000")
1533 self.assertEqual(t.isoformat(), str(t))
1534
1535 def test_strftime(self):
1536 t = self.theclass(1, 2, 3, 4)
1537 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1538 # A naive object replaces %z and %Z with empty strings.
1539 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1540
1541 def test_str(self):
1542 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1543 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1544 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1545 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1546 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1547
1548 def test_repr(self):
1549 name = 'datetime.' + self.theclass.__name__
1550 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1551 "%s(1, 2, 3, 4)" % name)
1552 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1553 "%s(10, 2, 3, 4000)" % name)
1554 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1555 "%s(0, 2, 3, 400000)" % name)
1556 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1557 "%s(12, 2, 3)" % name)
1558 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1559 "%s(23, 15)" % name)
1560
1561 def test_resolution_info(self):
1562 self.assert_(isinstance(self.theclass.min, self.theclass))
1563 self.assert_(isinstance(self.theclass.max, self.theclass))
1564 self.assert_(isinstance(self.theclass.resolution, timedelta))
1565 self.assert_(self.theclass.max > self.theclass.min)
1566
1567 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001568 args = 20, 59, 16, 64**2
1569 orig = self.theclass(*args)
1570 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001571 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Guido van Rossum177e41a2003-01-30 22:06:23 +00001572 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001573 green = pickler.dumps(orig, proto)
1574 derived = unpickler.loads(green)
1575 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001576
1577 def test_bool(self):
1578 cls = self.theclass
1579 self.failUnless(cls(1))
1580 self.failUnless(cls(0, 1))
1581 self.failUnless(cls(0, 0, 1))
1582 self.failUnless(cls(0, 0, 0, 1))
1583 self.failUnless(not cls(0))
1584 self.failUnless(not cls())
1585
Tim Peters12bf3392002-12-24 05:41:27 +00001586 def test_replace(self):
1587 cls = self.theclass
1588 args = [1, 2, 3, 4]
1589 base = cls(*args)
1590 self.assertEqual(base, base.replace())
1591
1592 i = 0
1593 for name, newval in (("hour", 5),
1594 ("minute", 6),
1595 ("second", 7),
1596 ("microsecond", 8)):
1597 newargs = args[:]
1598 newargs[i] = newval
1599 expected = cls(*newargs)
1600 got = base.replace(**{name: newval})
1601 self.assertEqual(expected, got)
1602 i += 1
1603
1604 # Out of bounds.
1605 base = cls(1)
1606 self.assertRaises(ValueError, base.replace, hour=24)
1607 self.assertRaises(ValueError, base.replace, minute=-1)
1608 self.assertRaises(ValueError, base.replace, second=100)
1609 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1610
Tim Peters855fe882002-12-22 03:43:39 +00001611# A mixin for classes with a tzinfo= argument. Subclasses must define
1612# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001613# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001614class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001615
Tim Petersbad8ff02002-12-30 20:52:32 +00001616 def test_argument_passing(self):
1617 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001618 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001619 class introspective(tzinfo):
1620 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001621 def utcoffset(self, dt):
1622 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001623 dst = utcoffset
1624
1625 obj = cls(1, 2, 3, tzinfo=introspective())
1626
Tim Peters0bf60bd2003-01-08 20:40:01 +00001627 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001628 self.assertEqual(obj.tzname(), expected)
1629
Tim Peters0bf60bd2003-01-08 20:40:01 +00001630 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001631 self.assertEqual(obj.utcoffset(), expected)
1632 self.assertEqual(obj.dst(), expected)
1633
Tim Peters855fe882002-12-22 03:43:39 +00001634 def test_bad_tzinfo_classes(self):
1635 cls = self.theclass
1636 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001637
Tim Peters855fe882002-12-22 03:43:39 +00001638 class NiceTry(object):
1639 def __init__(self): pass
1640 def utcoffset(self, dt): pass
1641 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1642
1643 class BetterTry(tzinfo):
1644 def __init__(self): pass
1645 def utcoffset(self, dt): pass
1646 b = BetterTry()
1647 t = cls(1, 1, 1, tzinfo=b)
1648 self.failUnless(t.tzinfo is b)
1649
1650 def test_utc_offset_out_of_bounds(self):
1651 class Edgy(tzinfo):
1652 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001653 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001654 def utcoffset(self, dt):
1655 return self.offset
1656
1657 cls = self.theclass
1658 for offset, legit in ((-1440, False),
1659 (-1439, True),
1660 (1439, True),
1661 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001662 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001663 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001664 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001665 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001666 else:
1667 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001668 if legit:
1669 aofs = abs(offset)
1670 h, m = divmod(aofs, 60)
1671 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001672 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001673 t = t.timetz()
1674 self.assertEqual(str(t), "01:02:03" + tag)
1675 else:
1676 self.assertRaises(ValueError, str, t)
1677
1678 def test_tzinfo_classes(self):
1679 cls = self.theclass
1680 class C1(tzinfo):
1681 def utcoffset(self, dt): return None
1682 def dst(self, dt): return None
1683 def tzname(self, dt): return None
1684 for t in (cls(1, 1, 1),
1685 cls(1, 1, 1, tzinfo=None),
1686 cls(1, 1, 1, tzinfo=C1())):
1687 self.failUnless(t.utcoffset() is None)
1688 self.failUnless(t.dst() is None)
1689 self.failUnless(t.tzname() is None)
1690
Tim Peters855fe882002-12-22 03:43:39 +00001691 class C3(tzinfo):
1692 def utcoffset(self, dt): return timedelta(minutes=-1439)
1693 def dst(self, dt): return timedelta(minutes=1439)
1694 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001695 t = cls(1, 1, 1, tzinfo=C3())
1696 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1697 self.assertEqual(t.dst(), timedelta(minutes=1439))
1698 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001699
1700 # Wrong types.
1701 class C4(tzinfo):
1702 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001703 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001704 def tzname(self, dt): return 0
1705 t = cls(1, 1, 1, tzinfo=C4())
1706 self.assertRaises(TypeError, t.utcoffset)
1707 self.assertRaises(TypeError, t.dst)
1708 self.assertRaises(TypeError, t.tzname)
1709
1710 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001711 class C6(tzinfo):
1712 def utcoffset(self, dt): return timedelta(hours=-24)
1713 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001714 t = cls(1, 1, 1, tzinfo=C6())
1715 self.assertRaises(ValueError, t.utcoffset)
1716 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001717
1718 # Not a whole number of minutes.
1719 class C7(tzinfo):
1720 def utcoffset(self, dt): return timedelta(seconds=61)
1721 def dst(self, dt): return timedelta(microseconds=-81)
1722 t = cls(1, 1, 1, tzinfo=C7())
1723 self.assertRaises(ValueError, t.utcoffset)
1724 self.assertRaises(ValueError, t.dst)
1725
Tim Peters4c0db782002-12-26 05:01:19 +00001726 def test_aware_compare(self):
1727 cls = self.theclass
1728
Tim Peters60c76e42002-12-27 00:41:11 +00001729 # Ensure that utcoffset() gets ignored if the comparands have
1730 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001731 class OperandDependentOffset(tzinfo):
1732 def utcoffset(self, t):
1733 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001734 # d0 and d1 equal after adjustment
1735 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001736 else:
Tim Peters397301e2003-01-02 21:28:08 +00001737 # d2 off in the weeds
1738 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001739
1740 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1741 d0 = base.replace(minute=3)
1742 d1 = base.replace(minute=9)
1743 d2 = base.replace(minute=11)
1744 for x in d0, d1, d2:
1745 for y in d0, d1, d2:
1746 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001747 expected = cmp(x.minute, y.minute)
1748 self.assertEqual(got, expected)
1749
1750 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001751 # Note that a time can't actually have an operand-depedent offset,
1752 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1753 # so skip this test for time.
1754 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001755 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1756 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1757 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1758 for x in d0, d1, d2:
1759 for y in d0, d1, d2:
1760 got = cmp(x, y)
1761 if (x is d0 or x is d1) and (y is d0 or y is d1):
1762 expected = 0
1763 elif x is y is d2:
1764 expected = 0
1765 elif x is d2:
1766 expected = -1
1767 else:
1768 assert y is d2
1769 expected = 1
1770 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001771
Tim Peters855fe882002-12-22 03:43:39 +00001772
Tim Peters0bf60bd2003-01-08 20:40:01 +00001773# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001774class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001775 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001776
1777 def test_empty(self):
1778 t = self.theclass()
1779 self.assertEqual(t.hour, 0)
1780 self.assertEqual(t.minute, 0)
1781 self.assertEqual(t.second, 0)
1782 self.assertEqual(t.microsecond, 0)
1783 self.failUnless(t.tzinfo is None)
1784
Tim Peters2a799bf2002-12-16 20:18:38 +00001785 def test_zones(self):
1786 est = FixedOffset(-300, "EST", 1)
1787 utc = FixedOffset(0, "UTC", -2)
1788 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001789 t1 = time( 7, 47, tzinfo=est)
1790 t2 = time(12, 47, tzinfo=utc)
1791 t3 = time(13, 47, tzinfo=met)
1792 t4 = time(microsecond=40)
1793 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001794
1795 self.assertEqual(t1.tzinfo, est)
1796 self.assertEqual(t2.tzinfo, utc)
1797 self.assertEqual(t3.tzinfo, met)
1798 self.failUnless(t4.tzinfo is None)
1799 self.assertEqual(t5.tzinfo, utc)
1800
Tim Peters855fe882002-12-22 03:43:39 +00001801 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1802 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1803 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001804 self.failUnless(t4.utcoffset() is None)
1805 self.assertRaises(TypeError, t1.utcoffset, "no args")
1806
1807 self.assertEqual(t1.tzname(), "EST")
1808 self.assertEqual(t2.tzname(), "UTC")
1809 self.assertEqual(t3.tzname(), "MET")
1810 self.failUnless(t4.tzname() is None)
1811 self.assertRaises(TypeError, t1.tzname, "no args")
1812
Tim Peters855fe882002-12-22 03:43:39 +00001813 self.assertEqual(t1.dst(), timedelta(minutes=1))
1814 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1815 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001816 self.failUnless(t4.dst() is None)
1817 self.assertRaises(TypeError, t1.dst, "no args")
1818
1819 self.assertEqual(hash(t1), hash(t2))
1820 self.assertEqual(hash(t1), hash(t3))
1821 self.assertEqual(hash(t2), hash(t3))
1822
1823 self.assertEqual(t1, t2)
1824 self.assertEqual(t1, t3)
1825 self.assertEqual(t2, t3)
1826 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1827 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1828 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1829
1830 self.assertEqual(str(t1), "07:47:00-05:00")
1831 self.assertEqual(str(t2), "12:47:00+00:00")
1832 self.assertEqual(str(t3), "13:47:00+01:00")
1833 self.assertEqual(str(t4), "00:00:00.000040")
1834 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1835
1836 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1837 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1838 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1839 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1840 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1841
Tim Peters0bf60bd2003-01-08 20:40:01 +00001842 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001843 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1844 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1845 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1846 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1847 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1848
1849 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1850 "07:47:00 %Z=EST %z=-0500")
1851 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1852 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1853
1854 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001855 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001856 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1857 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1858
Tim Petersb92bb712002-12-21 17:44:07 +00001859 # Check that an invalid tzname result raises an exception.
1860 class Badtzname(tzinfo):
1861 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001862 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001863 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1864 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001865
1866 def test_hash_edge_cases(self):
1867 # Offsets that overflow a basic time.
1868 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1869 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1870 self.assertEqual(hash(t1), hash(t2))
1871
1872 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1873 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1874 self.assertEqual(hash(t1), hash(t2))
1875
Tim Peters2a799bf2002-12-16 20:18:38 +00001876 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001877 # Try one without a tzinfo.
1878 args = 20, 59, 16, 64**2
1879 orig = self.theclass(*args)
1880 state = orig.__getstate__()
1881 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Guido van Rossum177e41a2003-01-30 22:06:23 +00001882 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001883 green = pickler.dumps(orig, proto)
1884 derived = unpickler.loads(green)
1885 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001886
1887 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001888 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001889 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001890 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001891 green = pickler.dumps(orig, proto)
1892 derived = unpickler.loads(green)
1893 self.assertEqual(orig, derived)
1894 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1895 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1896 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001897
1898 def test_more_bool(self):
1899 # Test cases with non-None tzinfo.
1900 cls = self.theclass
1901
1902 t = cls(0, tzinfo=FixedOffset(-300, ""))
1903 self.failUnless(t)
1904
1905 t = cls(5, tzinfo=FixedOffset(-300, ""))
1906 self.failUnless(t)
1907
1908 t = cls(5, tzinfo=FixedOffset(300, ""))
1909 self.failUnless(not t)
1910
1911 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1912 self.failUnless(not t)
1913
1914 # Mostly ensuring this doesn't overflow internally.
1915 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1916 self.failUnless(t)
1917
1918 # But this should yield a value error -- the utcoffset is bogus.
1919 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1920 self.assertRaises(ValueError, lambda: bool(t))
1921
1922 # Likewise.
1923 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1924 self.assertRaises(ValueError, lambda: bool(t))
1925
Tim Peters12bf3392002-12-24 05:41:27 +00001926 def test_replace(self):
1927 cls = self.theclass
1928 z100 = FixedOffset(100, "+100")
1929 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1930 args = [1, 2, 3, 4, z100]
1931 base = cls(*args)
1932 self.assertEqual(base, base.replace())
1933
1934 i = 0
1935 for name, newval in (("hour", 5),
1936 ("minute", 6),
1937 ("second", 7),
1938 ("microsecond", 8),
1939 ("tzinfo", zm200)):
1940 newargs = args[:]
1941 newargs[i] = newval
1942 expected = cls(*newargs)
1943 got = base.replace(**{name: newval})
1944 self.assertEqual(expected, got)
1945 i += 1
1946
1947 # Ensure we can get rid of a tzinfo.
1948 self.assertEqual(base.tzname(), "+100")
1949 base2 = base.replace(tzinfo=None)
1950 self.failUnless(base2.tzinfo is None)
1951 self.failUnless(base2.tzname() is None)
1952
1953 # Ensure we can add one.
1954 base3 = base2.replace(tzinfo=z100)
1955 self.assertEqual(base, base3)
1956 self.failUnless(base.tzinfo is base3.tzinfo)
1957
1958 # Out of bounds.
1959 base = cls(1)
1960 self.assertRaises(ValueError, base.replace, hour=24)
1961 self.assertRaises(ValueError, base.replace, minute=-1)
1962 self.assertRaises(ValueError, base.replace, second=100)
1963 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1964
Tim Peters60c76e42002-12-27 00:41:11 +00001965 def test_mixed_compare(self):
1966 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001967 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001968 self.assertEqual(t1, t2)
1969 t2 = t2.replace(tzinfo=None)
1970 self.assertEqual(t1, t2)
1971 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1972 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001973 if CMP_BUG_FIXED:
1974 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1975 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001976
Tim Peters0bf60bd2003-01-08 20:40:01 +00001977 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00001978 class Varies(tzinfo):
1979 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00001980 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00001981 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00001982 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00001983 return self.offset
1984
1985 v = Varies()
1986 t1 = t2.replace(tzinfo=v)
1987 t2 = t2.replace(tzinfo=v)
1988 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
1989 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
1990 self.assertEqual(t1, t2)
1991
1992 # But if they're not identical, it isn't ignored.
1993 t2 = t2.replace(tzinfo=Varies())
1994 self.failUnless(t1 < t2) # t1's offset counter still going up
1995
Tim Peters4c0db782002-12-26 05:01:19 +00001996
Tim Peters0bf60bd2003-01-08 20:40:01 +00001997# Testing datetime objects with a non-None tzinfo.
1998
Tim Peters855fe882002-12-22 03:43:39 +00001999class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002000 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002001
2002 def test_trivial(self):
2003 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2004 self.assertEqual(dt.year, 1)
2005 self.assertEqual(dt.month, 2)
2006 self.assertEqual(dt.day, 3)
2007 self.assertEqual(dt.hour, 4)
2008 self.assertEqual(dt.minute, 5)
2009 self.assertEqual(dt.second, 6)
2010 self.assertEqual(dt.microsecond, 7)
2011 self.assertEqual(dt.tzinfo, None)
2012
2013 def test_even_more_compare(self):
2014 # The test_compare() and test_more_compare() inherited from TestDate
2015 # and TestDateTime covered non-tzinfo cases.
2016
2017 # Smallest possible after UTC adjustment.
2018 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2019 # Largest possible after UTC adjustment.
2020 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2021 tzinfo=FixedOffset(-1439, ""))
2022
2023 # Make sure those compare correctly, and w/o overflow.
2024 self.failUnless(t1 < t2)
2025 self.failUnless(t1 != t2)
2026 self.failUnless(t2 > t1)
2027
2028 self.failUnless(t1 == t1)
2029 self.failUnless(t2 == t2)
2030
2031 # Equal afer adjustment.
2032 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2033 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2034 self.assertEqual(t1, t2)
2035
2036 # Change t1 not to subtract a minute, and t1 should be larger.
2037 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2038 self.failUnless(t1 > t2)
2039
2040 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2041 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2042 self.failUnless(t1 < t2)
2043
2044 # Back to the original t1, but make seconds resolve it.
2045 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2046 second=1)
2047 self.failUnless(t1 > t2)
2048
2049 # Likewise, but make microseconds resolve it.
2050 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2051 microsecond=1)
2052 self.failUnless(t1 > t2)
2053
2054 # Make t2 naive and it should fail.
2055 t2 = self.theclass.min
2056 self.assertRaises(TypeError, lambda: t1 == t2)
2057 self.assertEqual(t2, t2)
2058
2059 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2060 class Naive(tzinfo):
2061 def utcoffset(self, dt): return None
2062 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2063 self.assertRaises(TypeError, lambda: t1 == t2)
2064 self.assertEqual(t2, t2)
2065
2066 # OTOH, it's OK to compare two of these mixing the two ways of being
2067 # naive.
2068 t1 = self.theclass(5, 6, 7)
2069 self.assertEqual(t1, t2)
2070
2071 # Try a bogus uctoffset.
2072 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002073 def utcoffset(self, dt):
2074 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002075 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2076 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002077 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002078
Tim Peters2a799bf2002-12-16 20:18:38 +00002079 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002080 # Try one without a tzinfo.
2081 args = 6, 7, 23, 20, 59, 1, 64**2
2082 orig = self.theclass(*args)
2083 state = orig.__getstate__()
2084 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002085 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002086 green = pickler.dumps(orig, proto)
2087 derived = unpickler.loads(green)
2088 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002089
2090 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002091 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002092 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002093 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002094 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002095 green = pickler.dumps(orig, proto)
2096 derived = unpickler.loads(green)
2097 self.assertEqual(orig, derived)
2098 self.failUnless(isinstance(derived.tzinfo,
2099 PicklableFixedOffset))
2100 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2101 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002102
2103 def test_extreme_hashes(self):
2104 # If an attempt is made to hash these via subtracting the offset
2105 # then hashing a datetime object, OverflowError results. The
2106 # Python implementation used to blow up here.
2107 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2108 hash(t)
2109 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2110 tzinfo=FixedOffset(-1439, ""))
2111 hash(t)
2112
2113 # OTOH, an OOB offset should blow up.
2114 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2115 self.assertRaises(ValueError, hash, t)
2116
2117 def test_zones(self):
2118 est = FixedOffset(-300, "EST")
2119 utc = FixedOffset(0, "UTC")
2120 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002121 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2122 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2123 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002124 self.assertEqual(t1.tzinfo, est)
2125 self.assertEqual(t2.tzinfo, utc)
2126 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002127 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2128 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2129 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002130 self.assertEqual(t1.tzname(), "EST")
2131 self.assertEqual(t2.tzname(), "UTC")
2132 self.assertEqual(t3.tzname(), "MET")
2133 self.assertEqual(hash(t1), hash(t2))
2134 self.assertEqual(hash(t1), hash(t3))
2135 self.assertEqual(hash(t2), hash(t3))
2136 self.assertEqual(t1, t2)
2137 self.assertEqual(t1, t3)
2138 self.assertEqual(t2, t3)
2139 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2140 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2141 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002142 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002143 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2144 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2145 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2146
2147 def test_combine(self):
2148 met = FixedOffset(60, "MET")
2149 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002150 tz = time(18, 45, 3, 1234, tzinfo=met)
2151 dt = datetime.combine(d, tz)
2152 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002153 tzinfo=met))
2154
2155 def test_extract(self):
2156 met = FixedOffset(60, "MET")
2157 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2158 self.assertEqual(dt.date(), date(2002, 3, 4))
2159 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002160 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002161
2162 def test_tz_aware_arithmetic(self):
2163 import random
2164
2165 now = self.theclass.now()
2166 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002167 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002168 nowaware = self.theclass.combine(now.date(), timeaware)
2169 self.failUnless(nowaware.tzinfo is tz55)
2170 self.assertEqual(nowaware.timetz(), timeaware)
2171
2172 # Can't mix aware and non-aware.
2173 self.assertRaises(TypeError, lambda: now - nowaware)
2174 self.assertRaises(TypeError, lambda: nowaware - now)
2175
Tim Peters0bf60bd2003-01-08 20:40:01 +00002176 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002177 self.assertRaises(TypeError, lambda: now + nowaware)
2178 self.assertRaises(TypeError, lambda: nowaware + now)
2179 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2180
2181 # Subtracting should yield 0.
2182 self.assertEqual(now - now, timedelta(0))
2183 self.assertEqual(nowaware - nowaware, timedelta(0))
2184
2185 # Adding a delta should preserve tzinfo.
2186 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2187 nowawareplus = nowaware + delta
2188 self.failUnless(nowaware.tzinfo is tz55)
2189 nowawareplus2 = delta + nowaware
2190 self.failUnless(nowawareplus2.tzinfo is tz55)
2191 self.assertEqual(nowawareplus, nowawareplus2)
2192
2193 # that - delta should be what we started with, and that - what we
2194 # started with should be delta.
2195 diff = nowawareplus - delta
2196 self.failUnless(diff.tzinfo is tz55)
2197 self.assertEqual(nowaware, diff)
2198 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2199 self.assertEqual(nowawareplus - nowaware, delta)
2200
2201 # Make up a random timezone.
2202 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002203 # Attach it to nowawareplus.
2204 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002205 self.failUnless(nowawareplus.tzinfo is tzr)
2206 # Make sure the difference takes the timezone adjustments into account.
2207 got = nowaware - nowawareplus
2208 # Expected: (nowaware base - nowaware offset) -
2209 # (nowawareplus base - nowawareplus offset) =
2210 # (nowaware base - nowawareplus base) +
2211 # (nowawareplus offset - nowaware offset) =
2212 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002213 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002214 self.assertEqual(got, expected)
2215
2216 # Try max possible difference.
2217 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2218 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2219 tzinfo=FixedOffset(-1439, "max"))
2220 maxdiff = max - min
2221 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2222 timedelta(minutes=2*1439))
2223
2224 def test_tzinfo_now(self):
2225 meth = self.theclass.now
2226 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2227 base = meth()
2228 # Try with and without naming the keyword.
2229 off42 = FixedOffset(42, "42")
2230 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002231 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002232 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002233 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002234 # Bad argument with and w/o naming the keyword.
2235 self.assertRaises(TypeError, meth, 16)
2236 self.assertRaises(TypeError, meth, tzinfo=16)
2237 # Bad keyword name.
2238 self.assertRaises(TypeError, meth, tinfo=off42)
2239 # Too many args.
2240 self.assertRaises(TypeError, meth, off42, off42)
2241
Tim Peters10cadce2003-01-23 19:58:02 +00002242 # We don't know which time zone we're in, and don't have a tzinfo
2243 # class to represent it, so seeing whether a tz argument actually
2244 # does a conversion is tricky.
2245 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2246 utc = FixedOffset(0, "utc", 0)
2247 for dummy in range(3):
2248 now = datetime.now(weirdtz)
2249 self.failUnless(now.tzinfo is weirdtz)
2250 utcnow = datetime.utcnow().replace(tzinfo=utc)
2251 now2 = utcnow.astimezone(weirdtz)
2252 if abs(now - now2) < timedelta(seconds=30):
2253 break
2254 # Else the code is broken, or more than 30 seconds passed between
2255 # calls; assuming the latter, just try again.
2256 else:
2257 # Three strikes and we're out.
2258 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2259
Tim Peters2a799bf2002-12-16 20:18:38 +00002260 def test_tzinfo_fromtimestamp(self):
2261 import time
2262 meth = self.theclass.fromtimestamp
2263 ts = time.time()
2264 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2265 base = meth(ts)
2266 # Try with and without naming the keyword.
2267 off42 = FixedOffset(42, "42")
2268 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002269 again = meth(ts, 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, ts, 16)
2274 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2275 # Bad keyword name.
2276 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2277 # Too many args.
2278 self.assertRaises(TypeError, meth, ts, off42, off42)
2279 # Too few args.
2280 self.assertRaises(TypeError, meth)
2281
Tim Peters2a44a8d2003-01-23 20:53:10 +00002282 # Try to make sure tz= actually does some conversion.
2283 timestamp = 1000000000 # 2001-09-09 01:46:40 UTC, give or take
2284 utc = FixedOffset(0, "utc", 0)
2285 expected = datetime(2001, 9, 9, 1, 46, 40)
2286 got = datetime.utcfromtimestamp(timestamp)
2287 # We don't support leap seconds, but maybe the platfrom insists
2288 # on using them, so don't demand exact equality).
2289 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2290
2291 est = FixedOffset(-5*60, "est", 0)
2292 expected -= timedelta(hours=5)
2293 got = datetime.fromtimestamp(timestamp, est).replace(tzinfo=None)
2294 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2295
Tim Peters2a799bf2002-12-16 20:18:38 +00002296 def test_tzinfo_utcnow(self):
2297 meth = self.theclass.utcnow
2298 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2299 base = meth()
2300 # Try with and without naming the keyword; for whatever reason,
2301 # utcnow() doesn't accept a tzinfo argument.
2302 off42 = FixedOffset(42, "42")
2303 self.assertRaises(TypeError, meth, off42)
2304 self.assertRaises(TypeError, meth, tzinfo=off42)
2305
2306 def test_tzinfo_utcfromtimestamp(self):
2307 import time
2308 meth = self.theclass.utcfromtimestamp
2309 ts = time.time()
2310 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2311 base = meth(ts)
2312 # Try with and without naming the keyword; for whatever reason,
2313 # utcfromtimestamp() doesn't accept a tzinfo argument.
2314 off42 = FixedOffset(42, "42")
2315 self.assertRaises(TypeError, meth, ts, off42)
2316 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2317
2318 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002319 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002320 # DST flag.
2321 class DST(tzinfo):
2322 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002323 if isinstance(dstvalue, int):
2324 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002325 self.dstvalue = dstvalue
2326 def dst(self, dt):
2327 return self.dstvalue
2328
2329 cls = self.theclass
2330 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2331 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2332 t = d.timetuple()
2333 self.assertEqual(1, t.tm_year)
2334 self.assertEqual(1, t.tm_mon)
2335 self.assertEqual(1, t.tm_mday)
2336 self.assertEqual(10, t.tm_hour)
2337 self.assertEqual(20, t.tm_min)
2338 self.assertEqual(30, t.tm_sec)
2339 self.assertEqual(0, t.tm_wday)
2340 self.assertEqual(1, t.tm_yday)
2341 self.assertEqual(flag, t.tm_isdst)
2342
2343 # dst() returns wrong type.
2344 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2345
2346 # dst() at the edge.
2347 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2348 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2349
2350 # dst() out of range.
2351 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2352 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2353
2354 def test_utctimetuple(self):
2355 class DST(tzinfo):
2356 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002357 if isinstance(dstvalue, int):
2358 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002359 self.dstvalue = dstvalue
2360 def dst(self, dt):
2361 return self.dstvalue
2362
2363 cls = self.theclass
2364 # This can't work: DST didn't implement utcoffset.
2365 self.assertRaises(NotImplementedError,
2366 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2367
2368 class UOFS(DST):
2369 def __init__(self, uofs, dofs=None):
2370 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002371 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002372 def utcoffset(self, dt):
2373 return self.uofs
2374
2375 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2376 # in effect for a UTC time.
2377 for dstvalue in -33, 33, 0, None:
2378 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2379 t = d.utctimetuple()
2380 self.assertEqual(d.year, t.tm_year)
2381 self.assertEqual(d.month, t.tm_mon)
2382 self.assertEqual(d.day, t.tm_mday)
2383 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2384 self.assertEqual(13, t.tm_min)
2385 self.assertEqual(d.second, t.tm_sec)
2386 self.assertEqual(d.weekday(), t.tm_wday)
2387 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2388 t.tm_yday)
2389 self.assertEqual(0, t.tm_isdst)
2390
2391 # At the edges, UTC adjustment can normalize into years out-of-range
2392 # for a datetime object. Ensure that a correct timetuple is
2393 # created anyway.
2394 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2395 # That goes back 1 minute less than a full day.
2396 t = tiny.utctimetuple()
2397 self.assertEqual(t.tm_year, MINYEAR-1)
2398 self.assertEqual(t.tm_mon, 12)
2399 self.assertEqual(t.tm_mday, 31)
2400 self.assertEqual(t.tm_hour, 0)
2401 self.assertEqual(t.tm_min, 1)
2402 self.assertEqual(t.tm_sec, 37)
2403 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2404 self.assertEqual(t.tm_isdst, 0)
2405
2406 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2407 # That goes forward 1 minute less than a full day.
2408 t = huge.utctimetuple()
2409 self.assertEqual(t.tm_year, MAXYEAR+1)
2410 self.assertEqual(t.tm_mon, 1)
2411 self.assertEqual(t.tm_mday, 1)
2412 self.assertEqual(t.tm_hour, 23)
2413 self.assertEqual(t.tm_min, 58)
2414 self.assertEqual(t.tm_sec, 37)
2415 self.assertEqual(t.tm_yday, 1)
2416 self.assertEqual(t.tm_isdst, 0)
2417
2418 def test_tzinfo_isoformat(self):
2419 zero = FixedOffset(0, "+00:00")
2420 plus = FixedOffset(220, "+03:40")
2421 minus = FixedOffset(-231, "-03:51")
2422 unknown = FixedOffset(None, "")
2423
2424 cls = self.theclass
2425 datestr = '0001-02-03'
2426 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002427 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002428 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2429 timestr = '04:05:59' + (us and '.987001' or '')
2430 ofsstr = ofs is not None and d.tzname() or ''
2431 tailstr = timestr + ofsstr
2432 iso = d.isoformat()
2433 self.assertEqual(iso, datestr + 'T' + tailstr)
2434 self.assertEqual(iso, d.isoformat('T'))
2435 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2436 self.assertEqual(str(d), datestr + ' ' + tailstr)
2437
Tim Peters12bf3392002-12-24 05:41:27 +00002438 def test_replace(self):
2439 cls = self.theclass
2440 z100 = FixedOffset(100, "+100")
2441 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2442 args = [1, 2, 3, 4, 5, 6, 7, z100]
2443 base = cls(*args)
2444 self.assertEqual(base, base.replace())
2445
2446 i = 0
2447 for name, newval in (("year", 2),
2448 ("month", 3),
2449 ("day", 4),
2450 ("hour", 5),
2451 ("minute", 6),
2452 ("second", 7),
2453 ("microsecond", 8),
2454 ("tzinfo", zm200)):
2455 newargs = args[:]
2456 newargs[i] = newval
2457 expected = cls(*newargs)
2458 got = base.replace(**{name: newval})
2459 self.assertEqual(expected, got)
2460 i += 1
2461
2462 # Ensure we can get rid of a tzinfo.
2463 self.assertEqual(base.tzname(), "+100")
2464 base2 = base.replace(tzinfo=None)
2465 self.failUnless(base2.tzinfo is None)
2466 self.failUnless(base2.tzname() is None)
2467
2468 # Ensure we can add one.
2469 base3 = base2.replace(tzinfo=z100)
2470 self.assertEqual(base, base3)
2471 self.failUnless(base.tzinfo is base3.tzinfo)
2472
2473 # Out of bounds.
2474 base = cls(2000, 2, 29)
2475 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002476
Tim Peters80475bb2002-12-25 07:40:55 +00002477 def test_more_astimezone(self):
2478 # The inherited test_astimezone covered some trivial and error cases.
2479 fnone = FixedOffset(None, "None")
2480 f44m = FixedOffset(44, "44")
2481 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2482
Tim Peters10cadce2003-01-23 19:58:02 +00002483 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002484 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002485 # Replacing with degenerate tzinfo raises an exception.
2486 self.assertRaises(ValueError, dt.astimezone, fnone)
2487 # Ditto with None tz.
2488 self.assertRaises(TypeError, dt.astimezone, None)
2489 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002490 x = dt.astimezone(dt.tzinfo)
2491 self.failUnless(x.tzinfo is f44m)
2492 self.assertEqual(x.date(), dt.date())
2493 self.assertEqual(x.time(), dt.time())
2494
2495 # Replacing with different tzinfo does adjust.
2496 got = dt.astimezone(fm5h)
2497 self.failUnless(got.tzinfo is fm5h)
2498 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2499 expected = dt - dt.utcoffset() # in effect, convert to UTC
2500 expected += fm5h.utcoffset(dt) # and from there to local time
2501 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2502 self.assertEqual(got.date(), expected.date())
2503 self.assertEqual(got.time(), expected.time())
2504 self.assertEqual(got.timetz(), expected.timetz())
2505 self.failUnless(got.tzinfo is expected.tzinfo)
2506 self.assertEqual(got, expected)
2507
Tim Peters4c0db782002-12-26 05:01:19 +00002508 def test_aware_subtract(self):
2509 cls = self.theclass
2510
Tim Peters60c76e42002-12-27 00:41:11 +00002511 # Ensure that utcoffset() is ignored when the operands have the
2512 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002513 class OperandDependentOffset(tzinfo):
2514 def utcoffset(self, t):
2515 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002516 # d0 and d1 equal after adjustment
2517 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002518 else:
Tim Peters397301e2003-01-02 21:28:08 +00002519 # d2 off in the weeds
2520 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002521
2522 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2523 d0 = base.replace(minute=3)
2524 d1 = base.replace(minute=9)
2525 d2 = base.replace(minute=11)
2526 for x in d0, d1, d2:
2527 for y in d0, d1, d2:
2528 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002529 expected = timedelta(minutes=x.minute - y.minute)
2530 self.assertEqual(got, expected)
2531
2532 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2533 # ignored.
2534 base = cls(8, 9, 10, 11, 12, 13, 14)
2535 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2536 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2537 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2538 for x in d0, d1, d2:
2539 for y in d0, d1, d2:
2540 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002541 if (x is d0 or x is d1) and (y is d0 or y is d1):
2542 expected = timedelta(0)
2543 elif x is y is d2:
2544 expected = timedelta(0)
2545 elif x is d2:
2546 expected = timedelta(minutes=(11-59)-0)
2547 else:
2548 assert y is d2
2549 expected = timedelta(minutes=0-(11-59))
2550 self.assertEqual(got, expected)
2551
Tim Peters60c76e42002-12-27 00:41:11 +00002552 def test_mixed_compare(self):
2553 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002554 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002555 self.assertEqual(t1, t2)
2556 t2 = t2.replace(tzinfo=None)
2557 self.assertEqual(t1, t2)
2558 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2559 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002560 if CMP_BUG_FIXED:
2561 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2562 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002563
Tim Peters0bf60bd2003-01-08 20:40:01 +00002564 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002565 class Varies(tzinfo):
2566 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002567 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002568 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002569 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002570 return self.offset
2571
2572 v = Varies()
2573 t1 = t2.replace(tzinfo=v)
2574 t2 = t2.replace(tzinfo=v)
2575 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2576 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2577 self.assertEqual(t1, t2)
2578
2579 # But if they're not identical, it isn't ignored.
2580 t2 = t2.replace(tzinfo=Varies())
2581 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002582
Tim Peters621818b2002-12-29 23:44:49 +00002583# Pain to set up DST-aware tzinfo classes.
2584
2585def first_sunday_on_or_after(dt):
2586 days_to_go = 6 - dt.weekday()
2587 if days_to_go:
2588 dt += timedelta(days_to_go)
2589 return dt
2590
2591ZERO = timedelta(0)
2592HOUR = timedelta(hours=1)
2593DAY = timedelta(days=1)
2594# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2595DSTSTART = datetime(1, 4, 1, 2)
2596# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002597# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2598# being standard time on that day, there is no spelling in local time of
2599# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2600DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002601
2602class USTimeZone(tzinfo):
2603
2604 def __init__(self, hours, reprname, stdname, dstname):
2605 self.stdoffset = timedelta(hours=hours)
2606 self.reprname = reprname
2607 self.stdname = stdname
2608 self.dstname = dstname
2609
2610 def __repr__(self):
2611 return self.reprname
2612
2613 def tzname(self, dt):
2614 if self.dst(dt):
2615 return self.dstname
2616 else:
2617 return self.stdname
2618
2619 def utcoffset(self, dt):
2620 return self.stdoffset + self.dst(dt)
2621
2622 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002623 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002624 # An exception instead may be sensible here, in one or more of
2625 # the cases.
2626 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002627 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002628
2629 # Find first Sunday in April.
2630 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2631 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2632
2633 # Find last Sunday in October.
2634 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2635 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2636
Tim Peters621818b2002-12-29 23:44:49 +00002637 # Can't compare naive to aware objects, so strip the timezone from
2638 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002639 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002640 return HOUR
2641 else:
2642 return ZERO
2643
Tim Peters521fc152002-12-31 17:36:56 +00002644Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2645Central = USTimeZone(-6, "Central", "CST", "CDT")
2646Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2647Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002648utc_real = FixedOffset(0, "UTC", 0)
2649# For better test coverage, we want another flavor of UTC that's west of
2650# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002651utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002652
2653class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002654 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002655 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002656 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002657
Tim Peters0bf60bd2003-01-08 20:40:01 +00002658 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002659
Tim Peters521fc152002-12-31 17:36:56 +00002660 # Check a time that's inside DST.
2661 def checkinside(self, dt, tz, utc, dston, dstoff):
2662 self.assertEqual(dt.dst(), HOUR)
2663
2664 # Conversion to our own timezone is always an identity.
2665 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002666
2667 asutc = dt.astimezone(utc)
2668 there_and_back = asutc.astimezone(tz)
2669
2670 # Conversion to UTC and back isn't always an identity here,
2671 # because there are redundant spellings (in local time) of
2672 # UTC time when DST begins: the clock jumps from 1:59:59
2673 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2674 # make sense then. The classes above treat 2:MM:SS as
2675 # daylight time then (it's "after 2am"), really an alias
2676 # for 1:MM:SS standard time. The latter form is what
2677 # conversion back from UTC produces.
2678 if dt.date() == dston.date() and dt.hour == 2:
2679 # We're in the redundant hour, and coming back from
2680 # UTC gives the 1:MM:SS standard-time spelling.
2681 self.assertEqual(there_and_back + HOUR, dt)
2682 # Although during was considered to be in daylight
2683 # time, there_and_back is not.
2684 self.assertEqual(there_and_back.dst(), ZERO)
2685 # They're the same times in UTC.
2686 self.assertEqual(there_and_back.astimezone(utc),
2687 dt.astimezone(utc))
2688 else:
2689 # We're not in the redundant hour.
2690 self.assertEqual(dt, there_and_back)
2691
Tim Peters327098a2003-01-20 22:54:38 +00002692 # Because we have a redundant spelling when DST begins, there is
2693 # (unforunately) an hour when DST ends that can't be spelled at all in
2694 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2695 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2696 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2697 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2698 # expressed in local time. Nevertheless, we want conversion back
2699 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002700 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002701 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002702 if dt.date() == dstoff.date() and dt.hour == 0:
2703 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002704 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002705 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2706 nexthour_utc += HOUR
2707 nexthour_tz = nexthour_utc.astimezone(tz)
2708 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002709 else:
Tim Peters327098a2003-01-20 22:54:38 +00002710 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002711
2712 # Check a time that's outside DST.
2713 def checkoutside(self, dt, tz, utc):
2714 self.assertEqual(dt.dst(), ZERO)
2715
2716 # Conversion to our own timezone is always an identity.
2717 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002718
2719 # Converting to UTC and back is an identity too.
2720 asutc = dt.astimezone(utc)
2721 there_and_back = asutc.astimezone(tz)
2722 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002723
Tim Peters1024bf82002-12-30 17:09:40 +00002724 def convert_between_tz_and_utc(self, tz, utc):
2725 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002726 # Because 1:MM on the day DST ends is taken as being standard time,
2727 # there is no spelling in tz for the last hour of daylight time.
2728 # For purposes of the test, the last hour of DST is 0:MM, which is
2729 # taken as being daylight time (and 1:MM is taken as being standard
2730 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002731 dstoff = self.dstoff.replace(tzinfo=tz)
2732 for delta in (timedelta(weeks=13),
2733 DAY,
2734 HOUR,
2735 timedelta(minutes=1),
2736 timedelta(microseconds=1)):
2737
Tim Peters521fc152002-12-31 17:36:56 +00002738 self.checkinside(dston, tz, utc, dston, dstoff)
2739 for during in dston + delta, dstoff - delta:
2740 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002741
Tim Peters521fc152002-12-31 17:36:56 +00002742 self.checkoutside(dstoff, tz, utc)
2743 for outside in dston - delta, dstoff + delta:
2744 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002745
Tim Peters621818b2002-12-29 23:44:49 +00002746 def test_easy(self):
2747 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002748 self.convert_between_tz_and_utc(Eastern, utc_real)
2749 self.convert_between_tz_and_utc(Pacific, utc_real)
2750 self.convert_between_tz_and_utc(Eastern, utc_fake)
2751 self.convert_between_tz_and_utc(Pacific, utc_fake)
2752 # The next is really dancing near the edge. It works because
2753 # Pacific and Eastern are far enough apart that their "problem
2754 # hours" don't overlap.
2755 self.convert_between_tz_and_utc(Eastern, Pacific)
2756 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002757 # OTOH, these fail! Don't enable them. The difficulty is that
2758 # the edge case tests assume that every hour is representable in
2759 # the "utc" class. This is always true for a fixed-offset tzinfo
2760 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2761 # For these adjacent DST-aware time zones, the range of time offsets
2762 # tested ends up creating hours in the one that aren't representable
2763 # in the other. For the same reason, we would see failures in the
2764 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2765 # offset deltas in convert_between_tz_and_utc().
2766 #
2767 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2768 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002769
Tim Petersf3615152003-01-01 21:51:37 +00002770 def test_tricky(self):
2771 # 22:00 on day before daylight starts.
2772 fourback = self.dston - timedelta(hours=4)
2773 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002774 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002775 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2776 # 2", we should get the 3 spelling.
2777 # If we plug 22:00 the day before into Eastern, it "looks like std
2778 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2779 # to 22:00 lands on 2:00, which makes no sense in local time (the
2780 # local clock jumps from 1 to 3). The point here is to make sure we
2781 # get the 3 spelling.
2782 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002783 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002784 self.assertEqual(expected, got)
2785
2786 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2787 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002788 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002789 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2790 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2791 # spelling.
2792 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002793 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002794 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002795
Tim Petersadf64202003-01-04 06:03:15 +00002796 # Now on the day DST ends, we want "repeat an hour" behavior.
2797 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2798 # EST 23:MM 0:MM 1:MM 2:MM
2799 # EDT 0:MM 1:MM 2:MM 3:MM
2800 # wall 0:MM 1:MM 1:MM 2:MM against these
2801 for utc in utc_real, utc_fake:
2802 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002803 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002804 # Convert that to UTC.
2805 first_std_hour -= tz.utcoffset(None)
2806 # Adjust for possibly fake UTC.
2807 asutc = first_std_hour + utc.utcoffset(None)
2808 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2809 # tz=Eastern.
2810 asutcbase = asutc.replace(tzinfo=utc)
2811 for tzhour in (0, 1, 1, 2):
2812 expectedbase = self.dstoff.replace(hour=tzhour)
2813 for minute in 0, 30, 59:
2814 expected = expectedbase.replace(minute=minute)
2815 asutc = asutcbase.replace(minute=minute)
2816 astz = asutc.astimezone(tz)
2817 self.assertEqual(astz.replace(tzinfo=None), expected)
2818 asutcbase += HOUR
2819
2820
Tim Peters710fb152003-01-02 19:35:54 +00002821 def test_bogus_dst(self):
2822 class ok(tzinfo):
2823 def utcoffset(self, dt): return HOUR
2824 def dst(self, dt): return HOUR
2825
2826 now = self.theclass.now().replace(tzinfo=utc_real)
2827 # Doesn't blow up.
2828 now.astimezone(ok())
2829
2830 # Does blow up.
2831 class notok(ok):
2832 def dst(self, dt): return None
2833 self.assertRaises(ValueError, now.astimezone, notok())
2834
Tim Peters52dcce22003-01-23 16:36:11 +00002835 def test_fromutc(self):
2836 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2837 now = datetime.utcnow().replace(tzinfo=utc_real)
2838 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2839 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2840 enow = Eastern.fromutc(now) # doesn't blow up
2841 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2842 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2843 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2844
2845 # Always converts UTC to standard time.
2846 class FauxUSTimeZone(USTimeZone):
2847 def fromutc(self, dt):
2848 return dt + self.stdoffset
2849 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2850
2851 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2852 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2853 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2854
2855 # Check around DST start.
2856 start = self.dston.replace(hour=4, tzinfo=Eastern)
2857 fstart = start.replace(tzinfo=FEastern)
2858 for wall in 23, 0, 1, 3, 4, 5:
2859 expected = start.replace(hour=wall)
2860 if wall == 23:
2861 expected -= timedelta(days=1)
2862 got = Eastern.fromutc(start)
2863 self.assertEqual(expected, got)
2864
2865 expected = fstart + FEastern.stdoffset
2866 got = FEastern.fromutc(fstart)
2867 self.assertEqual(expected, got)
2868
2869 # Ensure astimezone() calls fromutc() too.
2870 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2871 self.assertEqual(expected, got)
2872
2873 start += HOUR
2874 fstart += HOUR
2875
2876 # Check around DST end.
2877 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2878 fstart = start.replace(tzinfo=FEastern)
2879 for wall in 0, 1, 1, 2, 3, 4:
2880 expected = start.replace(hour=wall)
2881 got = Eastern.fromutc(start)
2882 self.assertEqual(expected, got)
2883
2884 expected = fstart + FEastern.stdoffset
2885 got = FEastern.fromutc(fstart)
2886 self.assertEqual(expected, got)
2887
2888 # Ensure astimezone() calls fromutc() too.
2889 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2890 self.assertEqual(expected, got)
2891
2892 start += HOUR
2893 fstart += HOUR
2894
Tim Peters710fb152003-01-02 19:35:54 +00002895
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002896def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002897 allsuites = [unittest.makeSuite(klass, 'test')
2898 for klass in (TestModule,
2899 TestTZInfo,
2900 TestTimeDelta,
2901 TestDateOnly,
2902 TestDate,
2903 TestDateTime,
2904 TestTime,
2905 TestTimeTZ,
2906 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002907 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002908 )
2909 ]
2910 return unittest.TestSuite(allsuites)
2911
2912def test_main():
2913 import gc
2914 import sys
2915
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002916 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002917 lastrc = None
2918 while True:
2919 test_support.run_suite(thesuite)
2920 if 1: # change to 0, under a debug build, for some leak detection
2921 break
2922 gc.collect()
2923 if gc.garbage:
2924 raise SystemError("gc.garbage not empty after test run: %r" %
2925 gc.garbage)
2926 if hasattr(sys, 'gettotalrefcount'):
2927 thisrc = sys.gettotalrefcount()
2928 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2929 if lastrc:
2930 print >> sys.stderr, 'delta:', thisrc - lastrc
2931 else:
2932 print >> sys.stderr
2933 lastrc = thisrc
2934
2935if __name__ == "__main__":
2936 test_main()