blob: 32a277ed010b8a21c084695a7f3b4b2ee2491b70 [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)
283 derived = timedelta()
284 derived.__setstate__(state)
285 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000286 for pickler, unpickler, proto in pickle_choices:
287 green = pickler.dumps(orig, proto)
288 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000289 self.assertEqual(orig, derived)
290
291 def test_compare(self):
292 t1 = timedelta(2, 3, 4)
293 t2 = timedelta(2, 3, 4)
294 self.failUnless(t1 == t2)
295 self.failUnless(t1 <= t2)
296 self.failUnless(t1 >= t2)
297 self.failUnless(not t1 != t2)
298 self.failUnless(not t1 < t2)
299 self.failUnless(not t1 > t2)
300 self.assertEqual(cmp(t1, t2), 0)
301 self.assertEqual(cmp(t2, t1), 0)
302
303 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
304 t2 = timedelta(*args) # this is larger than t1
305 self.failUnless(t1 < t2)
306 self.failUnless(t2 > t1)
307 self.failUnless(t1 <= t2)
308 self.failUnless(t2 >= t1)
309 self.failUnless(t1 != t2)
310 self.failUnless(t2 != t1)
311 self.failUnless(not t1 == t2)
312 self.failUnless(not t2 == t1)
313 self.failUnless(not t1 > t2)
314 self.failUnless(not t2 < t1)
315 self.failUnless(not t1 >= t2)
316 self.failUnless(not t2 <= t1)
317 self.assertEqual(cmp(t1, t2), -1)
318 self.assertEqual(cmp(t2, t1), 1)
319
320 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
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: t1 < badarg)
325 self.assertRaises(TypeError, lambda: t1 > badarg)
326 self.assertRaises(TypeError, lambda: t1 >= badarg)
327 self.assertRaises(TypeError, lambda: badarg == t1)
328 self.assertRaises(TypeError, lambda: badarg != t1)
329 self.assertRaises(TypeError, lambda: badarg <= t1)
330 self.assertRaises(TypeError, lambda: badarg < t1)
331 self.assertRaises(TypeError, lambda: badarg > t1)
332 self.assertRaises(TypeError, lambda: badarg >= t1)
333
334 def test_str(self):
335 td = timedelta
336 eq = self.assertEqual
337
338 eq(str(td(1)), "1 day, 0:00:00")
339 eq(str(td(-1)), "-1 day, 0:00:00")
340 eq(str(td(2)), "2 days, 0:00:00")
341 eq(str(td(-2)), "-2 days, 0:00:00")
342
343 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
344 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
345 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
346 "-210 days, 23:12:34")
347
348 eq(str(td(milliseconds=1)), "0:00:00.001000")
349 eq(str(td(microseconds=3)), "0:00:00.000003")
350
351 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
352 microseconds=999999)),
353 "999999999 days, 23:59:59.999999")
354
355 def test_roundtrip(self):
356 for td in (timedelta(days=999999999, hours=23, minutes=59,
357 seconds=59, microseconds=999999),
358 timedelta(days=-999999999),
359 timedelta(days=1, seconds=2, microseconds=3)):
360
361 # Verify td -> string -> td identity.
362 s = repr(td)
363 self.failUnless(s.startswith('datetime.'))
364 s = s[9:]
365 td2 = eval(s)
366 self.assertEqual(td, td2)
367
368 # Verify identity via reconstructing from pieces.
369 td2 = timedelta(td.days, td.seconds, td.microseconds)
370 self.assertEqual(td, td2)
371
372 def test_resolution_info(self):
373 self.assert_(isinstance(timedelta.min, timedelta))
374 self.assert_(isinstance(timedelta.max, timedelta))
375 self.assert_(isinstance(timedelta.resolution, timedelta))
376 self.assert_(timedelta.max > timedelta.min)
377 self.assertEqual(timedelta.min, timedelta(-999999999))
378 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
379 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
380
381 def test_overflow(self):
382 tiny = timedelta.resolution
383
384 td = timedelta.min + tiny
385 td -= tiny # no problem
386 self.assertRaises(OverflowError, td.__sub__, tiny)
387 self.assertRaises(OverflowError, td.__add__, -tiny)
388
389 td = timedelta.max - tiny
390 td += tiny # no problem
391 self.assertRaises(OverflowError, td.__add__, tiny)
392 self.assertRaises(OverflowError, td.__sub__, -tiny)
393
394 self.assertRaises(OverflowError, lambda: -timedelta.max)
395
396 def test_microsecond_rounding(self):
397 td = timedelta
398 eq = self.assertEqual
399
400 # Single-field rounding.
401 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
402 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
403 eq(td(milliseconds=0.6/1000), td(microseconds=1))
404 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
405
406 # Rounding due to contributions from more than one field.
407 us_per_hour = 3600e6
408 us_per_day = us_per_hour * 24
409 eq(td(days=.4/us_per_day), td(0))
410 eq(td(hours=.2/us_per_hour), td(0))
411 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
412
413 eq(td(days=-.4/us_per_day), td(0))
414 eq(td(hours=-.2/us_per_hour), td(0))
415 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
416
417 def test_massive_normalization(self):
418 td = timedelta(microseconds=-1)
419 self.assertEqual((td.days, td.seconds, td.microseconds),
420 (-1, 24*3600-1, 999999))
421
422 def test_bool(self):
423 self.failUnless(timedelta(1))
424 self.failUnless(timedelta(0, 1))
425 self.failUnless(timedelta(0, 0, 1))
426 self.failUnless(timedelta(microseconds=1))
427 self.failUnless(not timedelta(0))
428
429#############################################################################
430# date tests
431
432class TestDateOnly(unittest.TestCase):
433 # Tests here won't pass if also run on datetime objects, so don't
434 # subclass this to test datetimes too.
435
436 def test_delta_non_days_ignored(self):
437 dt = date(2000, 1, 2)
438 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
439 microseconds=5)
440 days = timedelta(delta.days)
441 self.assertEqual(days, timedelta(1))
442
443 dt2 = dt + delta
444 self.assertEqual(dt2, dt + days)
445
446 dt2 = delta + dt
447 self.assertEqual(dt2, dt + days)
448
449 dt2 = dt - delta
450 self.assertEqual(dt2, dt - days)
451
452 delta = -delta
453 days = timedelta(delta.days)
454 self.assertEqual(days, timedelta(-2))
455
456 dt2 = dt + delta
457 self.assertEqual(dt2, dt + days)
458
459 dt2 = delta + dt
460 self.assertEqual(dt2, dt + days)
461
462 dt2 = dt - delta
463 self.assertEqual(dt2, dt - days)
464
465class TestDate(unittest.TestCase):
466 # Tests here should pass for both dates and datetimes, except for a
467 # few tests that TestDateTime overrides.
468
469 theclass = date
470
471 def test_basic_attributes(self):
472 dt = self.theclass(2002, 3, 1)
473 self.assertEqual(dt.year, 2002)
474 self.assertEqual(dt.month, 3)
475 self.assertEqual(dt.day, 1)
476
477 def test_roundtrip(self):
478 for dt in (self.theclass(1, 2, 3),
479 self.theclass.today()):
480 # Verify dt -> string -> date identity.
481 s = repr(dt)
482 self.failUnless(s.startswith('datetime.'))
483 s = s[9:]
484 dt2 = eval(s)
485 self.assertEqual(dt, dt2)
486
487 # Verify identity via reconstructing from pieces.
488 dt2 = self.theclass(dt.year, dt.month, dt.day)
489 self.assertEqual(dt, dt2)
490
491 def test_ordinal_conversions(self):
492 # Check some fixed values.
493 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
494 (1, 12, 31, 365),
495 (2, 1, 1, 366),
496 # first example from "Calendrical Calculations"
497 (1945, 11, 12, 710347)]:
498 d = self.theclass(y, m, d)
499 self.assertEqual(n, d.toordinal())
500 fromord = self.theclass.fromordinal(n)
501 self.assertEqual(d, fromord)
502 if hasattr(fromord, "hour"):
503 # if we're checking something fancier than a date, verify
504 # the extra fields have been zeroed out
505 self.assertEqual(fromord.hour, 0)
506 self.assertEqual(fromord.minute, 0)
507 self.assertEqual(fromord.second, 0)
508 self.assertEqual(fromord.microsecond, 0)
509
Tim Peters0bf60bd2003-01-08 20:40:01 +0000510 # Check first and last days of year spottily across the whole
511 # range of years supported.
512 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000513 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
514 d = self.theclass(year, 1, 1)
515 n = d.toordinal()
516 d2 = self.theclass.fromordinal(n)
517 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000518 # Verify that moving back a day gets to the end of year-1.
519 if year > 1:
520 d = self.theclass.fromordinal(n-1)
521 d2 = self.theclass(year-1, 12, 31)
522 self.assertEqual(d, d2)
523 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000524
525 # Test every day in a leap-year and a non-leap year.
526 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
527 for year, isleap in (2000, True), (2002, False):
528 n = self.theclass(year, 1, 1).toordinal()
529 for month, maxday in zip(range(1, 13), dim):
530 if month == 2 and isleap:
531 maxday += 1
532 for day in range(1, maxday+1):
533 d = self.theclass(year, month, day)
534 self.assertEqual(d.toordinal(), n)
535 self.assertEqual(d, self.theclass.fromordinal(n))
536 n += 1
537
538 def test_extreme_ordinals(self):
539 a = self.theclass.min
540 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
541 aord = a.toordinal()
542 b = a.fromordinal(aord)
543 self.assertEqual(a, b)
544
545 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
546
547 b = a + timedelta(days=1)
548 self.assertEqual(b.toordinal(), aord + 1)
549 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
550
551 a = self.theclass.max
552 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
553 aord = a.toordinal()
554 b = a.fromordinal(aord)
555 self.assertEqual(a, b)
556
557 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
558
559 b = a - timedelta(days=1)
560 self.assertEqual(b.toordinal(), aord - 1)
561 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
562
563 def test_bad_constructor_arguments(self):
564 # bad years
565 self.theclass(MINYEAR, 1, 1) # no exception
566 self.theclass(MAXYEAR, 1, 1) # no exception
567 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
568 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
569 # bad months
570 self.theclass(2000, 1, 1) # no exception
571 self.theclass(2000, 12, 1) # no exception
572 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
573 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
574 # bad days
575 self.theclass(2000, 2, 29) # no exception
576 self.theclass(2004, 2, 29) # no exception
577 self.theclass(2400, 2, 29) # no exception
578 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
579 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
580 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
581 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
582 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
583 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
584
585 def test_hash_equality(self):
586 d = self.theclass(2000, 12, 31)
587 # same thing
588 e = self.theclass(2000, 12, 31)
589 self.assertEqual(d, e)
590 self.assertEqual(hash(d), hash(e))
591
592 dic = {d: 1}
593 dic[e] = 2
594 self.assertEqual(len(dic), 1)
595 self.assertEqual(dic[d], 2)
596 self.assertEqual(dic[e], 2)
597
598 d = self.theclass(2001, 1, 1)
599 # same thing
600 e = self.theclass(2001, 1, 1)
601 self.assertEqual(d, e)
602 self.assertEqual(hash(d), hash(e))
603
604 dic = {d: 1}
605 dic[e] = 2
606 self.assertEqual(len(dic), 1)
607 self.assertEqual(dic[d], 2)
608 self.assertEqual(dic[e], 2)
609
610 def test_computations(self):
611 a = self.theclass(2002, 1, 31)
612 b = self.theclass(1956, 1, 31)
613
614 diff = a-b
615 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
616 self.assertEqual(diff.seconds, 0)
617 self.assertEqual(diff.microseconds, 0)
618
619 day = timedelta(1)
620 week = timedelta(7)
621 a = self.theclass(2002, 3, 2)
622 self.assertEqual(a + day, self.theclass(2002, 3, 3))
623 self.assertEqual(day + a, self.theclass(2002, 3, 3))
624 self.assertEqual(a - day, self.theclass(2002, 3, 1))
625 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
626 self.assertEqual(a + week, self.theclass(2002, 3, 9))
627 self.assertEqual(a - week, self.theclass(2002, 2, 23))
628 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
629 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
630 self.assertEqual((a + week) - a, week)
631 self.assertEqual((a + day) - a, day)
632 self.assertEqual((a - week) - a, -week)
633 self.assertEqual((a - day) - a, -day)
634 self.assertEqual(a - (a + week), -week)
635 self.assertEqual(a - (a + day), -day)
636 self.assertEqual(a - (a - week), week)
637 self.assertEqual(a - (a - day), day)
638
639 # Add/sub ints, longs, floats should be illegal
640 for i in 1, 1L, 1.0:
641 self.assertRaises(TypeError, lambda: a+i)
642 self.assertRaises(TypeError, lambda: a-i)
643 self.assertRaises(TypeError, lambda: i+a)
644 self.assertRaises(TypeError, lambda: i-a)
645
646 # delta - date is senseless.
647 self.assertRaises(TypeError, lambda: day - a)
648 # mixing date and (delta or date) via * or // is senseless
649 self.assertRaises(TypeError, lambda: day * a)
650 self.assertRaises(TypeError, lambda: a * day)
651 self.assertRaises(TypeError, lambda: day // a)
652 self.assertRaises(TypeError, lambda: a // day)
653 self.assertRaises(TypeError, lambda: a * a)
654 self.assertRaises(TypeError, lambda: a // a)
655 # date + date is senseless
656 self.assertRaises(TypeError, lambda: a + a)
657
658 def test_overflow(self):
659 tiny = self.theclass.resolution
660
661 dt = self.theclass.min + tiny
662 dt -= tiny # no problem
663 self.assertRaises(OverflowError, dt.__sub__, tiny)
664 self.assertRaises(OverflowError, dt.__add__, -tiny)
665
666 dt = self.theclass.max - tiny
667 dt += tiny # no problem
668 self.assertRaises(OverflowError, dt.__add__, tiny)
669 self.assertRaises(OverflowError, dt.__sub__, -tiny)
670
671 def test_fromtimestamp(self):
672 import time
673
674 # Try an arbitrary fixed value.
675 year, month, day = 1999, 9, 19
676 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
677 d = self.theclass.fromtimestamp(ts)
678 self.assertEqual(d.year, year)
679 self.assertEqual(d.month, month)
680 self.assertEqual(d.day, day)
681
682 def test_today(self):
683 import time
684
685 # We claim that today() is like fromtimestamp(time.time()), so
686 # prove it.
687 for dummy in range(3):
688 today = self.theclass.today()
689 ts = time.time()
690 todayagain = self.theclass.fromtimestamp(ts)
691 if today == todayagain:
692 break
693 # There are several legit reasons that could fail:
694 # 1. It recently became midnight, between the today() and the
695 # time() calls.
696 # 2. The platform time() has such fine resolution that we'll
697 # never get the same value twice.
698 # 3. The platform time() has poor resolution, and we just
699 # happened to call today() right before a resolution quantum
700 # boundary.
701 # 4. The system clock got fiddled between calls.
702 # In any case, wait a little while and try again.
703 time.sleep(0.1)
704
705 # It worked or it didn't. If it didn't, assume it's reason #2, and
706 # let the test pass if they're within half a second of each other.
707 self.failUnless(today == todayagain or
708 abs(todayagain - today) < timedelta(seconds=0.5))
709
710 def test_weekday(self):
711 for i in range(7):
712 # March 4, 2002 is a Monday
713 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
714 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
715 # January 2, 1956 is a Monday
716 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
717 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
718
719 def test_isocalendar(self):
720 # Check examples from
721 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
722 for i in range(7):
723 d = self.theclass(2003, 12, 22+i)
724 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
725 d = self.theclass(2003, 12, 29) + timedelta(i)
726 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
727 d = self.theclass(2004, 1, 5+i)
728 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
729 d = self.theclass(2009, 12, 21+i)
730 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
731 d = self.theclass(2009, 12, 28) + timedelta(i)
732 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
733 d = self.theclass(2010, 1, 4+i)
734 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
735
736 def test_iso_long_years(self):
737 # Calculate long ISO years and compare to table from
738 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
739 ISO_LONG_YEARS_TABLE = """
740 4 32 60 88
741 9 37 65 93
742 15 43 71 99
743 20 48 76
744 26 54 82
745
746 105 133 161 189
747 111 139 167 195
748 116 144 172
749 122 150 178
750 128 156 184
751
752 201 229 257 285
753 207 235 263 291
754 212 240 268 296
755 218 246 274
756 224 252 280
757
758 303 331 359 387
759 308 336 364 392
760 314 342 370 398
761 320 348 376
762 325 353 381
763 """
764 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
765 iso_long_years.sort()
766 L = []
767 for i in range(400):
768 d = self.theclass(2000+i, 12, 31)
769 d1 = self.theclass(1600+i, 12, 31)
770 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
771 if d.isocalendar()[1] == 53:
772 L.append(i)
773 self.assertEqual(L, iso_long_years)
774
775 def test_isoformat(self):
776 t = self.theclass(2, 3, 2)
777 self.assertEqual(t.isoformat(), "0002-03-02")
778
779 def test_ctime(self):
780 t = self.theclass(2002, 3, 2)
781 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
782
783 def test_strftime(self):
784 t = self.theclass(2005, 3, 2)
785 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
786
787 self.assertRaises(TypeError, t.strftime) # needs an arg
788 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
789 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
790
791 # A naive object replaces %z and %Z w/ empty strings.
792 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
793
794 def test_resolution_info(self):
795 self.assert_(isinstance(self.theclass.min, self.theclass))
796 self.assert_(isinstance(self.theclass.max, self.theclass))
797 self.assert_(isinstance(self.theclass.resolution, timedelta))
798 self.assert_(self.theclass.max > self.theclass.min)
799
800 def test_extreme_timedelta(self):
801 big = self.theclass.max - self.theclass.min
802 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
803 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
804 # n == 315537897599999999 ~= 2**58.13
805 justasbig = timedelta(0, 0, n)
806 self.assertEqual(big, justasbig)
807 self.assertEqual(self.theclass.min + big, self.theclass.max)
808 self.assertEqual(self.theclass.max - big, self.theclass.min)
809
810 def test_timetuple(self):
811 for i in range(7):
812 # January 2, 1956 is a Monday (0)
813 d = self.theclass(1956, 1, 2+i)
814 t = d.timetuple()
815 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
816 # February 1, 1956 is a Wednesday (2)
817 d = self.theclass(1956, 2, 1+i)
818 t = d.timetuple()
819 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
820 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
821 # of the year.
822 d = self.theclass(1956, 3, 1+i)
823 t = d.timetuple()
824 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
825 self.assertEqual(t.tm_year, 1956)
826 self.assertEqual(t.tm_mon, 3)
827 self.assertEqual(t.tm_mday, 1+i)
828 self.assertEqual(t.tm_hour, 0)
829 self.assertEqual(t.tm_min, 0)
830 self.assertEqual(t.tm_sec, 0)
831 self.assertEqual(t.tm_wday, (3+i)%7)
832 self.assertEqual(t.tm_yday, 61+i)
833 self.assertEqual(t.tm_isdst, -1)
834
835 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000836 args = 6, 7, 23
837 orig = self.theclass(*args)
838 state = orig.__getstate__()
Guido van Rossum177e41a2003-01-30 22:06:23 +0000839 self.assertEqual(state, ('\x00\x06\x07\x17',), self.theclass)
Tim Peters2a799bf2002-12-16 20:18:38 +0000840 derived = self.theclass(1, 1, 1)
841 derived.__setstate__(state)
842 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000843 for pickler, unpickler, proto in pickle_choices:
844 green = pickler.dumps(orig, proto)
845 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000846 self.assertEqual(orig, derived)
847
848 def test_compare(self):
849 t1 = self.theclass(2, 3, 4)
850 t2 = self.theclass(2, 3, 4)
851 self.failUnless(t1 == t2)
852 self.failUnless(t1 <= t2)
853 self.failUnless(t1 >= t2)
854 self.failUnless(not t1 != t2)
855 self.failUnless(not t1 < t2)
856 self.failUnless(not t1 > t2)
857 self.assertEqual(cmp(t1, t2), 0)
858 self.assertEqual(cmp(t2, t1), 0)
859
860 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
861 t2 = self.theclass(*args) # this is larger than t1
862 self.failUnless(t1 < t2)
863 self.failUnless(t2 > t1)
864 self.failUnless(t1 <= t2)
865 self.failUnless(t2 >= t1)
866 self.failUnless(t1 != t2)
867 self.failUnless(t2 != t1)
868 self.failUnless(not t1 == t2)
869 self.failUnless(not t2 == t1)
870 self.failUnless(not t1 > t2)
871 self.failUnless(not t2 < t1)
872 self.failUnless(not t1 >= t2)
873 self.failUnless(not t2 <= t1)
874 self.assertEqual(cmp(t1, t2), -1)
875 self.assertEqual(cmp(t2, t1), 1)
876
877 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
878 self.assertRaises(TypeError, lambda: t1 == badarg)
879 self.assertRaises(TypeError, lambda: t1 != badarg)
880 self.assertRaises(TypeError, lambda: t1 <= badarg)
881 self.assertRaises(TypeError, lambda: t1 < badarg)
882 self.assertRaises(TypeError, lambda: t1 > badarg)
883 self.assertRaises(TypeError, lambda: t1 >= badarg)
884 self.assertRaises(TypeError, lambda: badarg == t1)
885 self.assertRaises(TypeError, lambda: badarg != t1)
886 self.assertRaises(TypeError, lambda: badarg <= t1)
887 self.assertRaises(TypeError, lambda: badarg < t1)
888 self.assertRaises(TypeError, lambda: badarg > t1)
889 self.assertRaises(TypeError, lambda: badarg >= t1)
890
Tim Peters8d81a012003-01-24 22:36:34 +0000891 def test_mixed_compare(self):
892 our = self.theclass(2000, 4, 5)
893 self.assertRaises(TypeError, cmp, our, 1)
894 self.assertRaises(TypeError, cmp, 1, our)
895
896 class AnotherDateTimeClass(object):
897 def __cmp__(self, other):
898 # Return "equal" so calling this can't be confused with
899 # compare-by-address (which never says "equal" for distinct
900 # objects).
901 return 0
902
903 # This still errors, because date and datetime comparison raise
904 # TypeError instead of NotImplemented when they don't know what to
905 # do, in order to stop comparison from falling back to the default
906 # compare-by-address.
907 their = AnotherDateTimeClass()
908 self.assertRaises(TypeError, cmp, our, their)
909 # Oops: The next stab raises TypeError in the C implementation,
910 # but not in the Python implementation of datetime. The difference
911 # is due to that the Python implementation defines __cmp__ but
912 # the C implementation defines tp_richcompare. This is more pain
913 # to fix than it's worth, so commenting out the test.
914 # self.assertEqual(cmp(their, our), 0)
915
916 # But date and datetime comparison return NotImplemented instead if the
917 # other object has a timetuple attr. This gives the other object a
918 # chance to do the comparison.
919 class Comparable(AnotherDateTimeClass):
920 def timetuple(self):
921 return ()
922
923 their = Comparable()
924 self.assertEqual(cmp(our, their), 0)
925 self.assertEqual(cmp(their, our), 0)
926 self.failUnless(our == their)
927 self.failUnless(their == our)
928
Tim Peters2a799bf2002-12-16 20:18:38 +0000929 def test_bool(self):
930 # All dates are considered true.
931 self.failUnless(self.theclass.min)
932 self.failUnless(self.theclass.max)
933
Tim Petersd6844152002-12-22 20:58:42 +0000934 def test_srftime_out_of_range(self):
935 # For nasty technical reasons, we can't handle years before 1900.
936 cls = self.theclass
937 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
938 for y in 1, 49, 51, 99, 100, 1000, 1899:
939 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000940
941 def test_replace(self):
942 cls = self.theclass
943 args = [1, 2, 3]
944 base = cls(*args)
945 self.assertEqual(base, base.replace())
946
947 i = 0
948 for name, newval in (("year", 2),
949 ("month", 3),
950 ("day", 4)):
951 newargs = args[:]
952 newargs[i] = newval
953 expected = cls(*newargs)
954 got = base.replace(**{name: newval})
955 self.assertEqual(expected, got)
956 i += 1
957
958 # Out of bounds.
959 base = cls(2000, 2, 29)
960 self.assertRaises(ValueError, base.replace, year=2001)
961
Tim Peters2a799bf2002-12-16 20:18:38 +0000962#############################################################################
963# datetime tests
964
965class TestDateTime(TestDate):
966
967 theclass = datetime
968
969 def test_basic_attributes(self):
970 dt = self.theclass(2002, 3, 1, 12, 0)
971 self.assertEqual(dt.year, 2002)
972 self.assertEqual(dt.month, 3)
973 self.assertEqual(dt.day, 1)
974 self.assertEqual(dt.hour, 12)
975 self.assertEqual(dt.minute, 0)
976 self.assertEqual(dt.second, 0)
977 self.assertEqual(dt.microsecond, 0)
978
979 def test_basic_attributes_nonzero(self):
980 # Make sure all attributes are non-zero so bugs in
981 # bit-shifting access show up.
982 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
983 self.assertEqual(dt.year, 2002)
984 self.assertEqual(dt.month, 3)
985 self.assertEqual(dt.day, 1)
986 self.assertEqual(dt.hour, 12)
987 self.assertEqual(dt.minute, 59)
988 self.assertEqual(dt.second, 59)
989 self.assertEqual(dt.microsecond, 8000)
990
991 def test_roundtrip(self):
992 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
993 self.theclass.now()):
994 # Verify dt -> string -> datetime identity.
995 s = repr(dt)
996 self.failUnless(s.startswith('datetime.'))
997 s = s[9:]
998 dt2 = eval(s)
999 self.assertEqual(dt, dt2)
1000
1001 # Verify identity via reconstructing from pieces.
1002 dt2 = self.theclass(dt.year, dt.month, dt.day,
1003 dt.hour, dt.minute, dt.second,
1004 dt.microsecond)
1005 self.assertEqual(dt, dt2)
1006
1007 def test_isoformat(self):
1008 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1009 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1010 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1011 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1012 # str is ISO format with the separator forced to a blank.
1013 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1014
1015 t = self.theclass(2, 3, 2)
1016 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1017 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1018 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1019 # str is ISO format with the separator forced to a blank.
1020 self.assertEqual(str(t), "0002-03-02 00:00:00")
1021
1022 def test_more_ctime(self):
1023 # Test fields that TestDate doesn't touch.
1024 import time
1025
1026 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1027 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1028 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1029 # out. The difference is that t.ctime() produces " 2" for the day,
1030 # but platform ctime() produces "02" for the day. According to
1031 # C99, t.ctime() is correct here.
1032 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1033
1034 # So test a case where that difference doesn't matter.
1035 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1036 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1037
1038 def test_tz_independent_comparing(self):
1039 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1040 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1041 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1042 self.assertEqual(dt1, dt3)
1043 self.assert_(dt2 > dt3)
1044
1045 # Make sure comparison doesn't forget microseconds, and isn't done
1046 # via comparing a float timestamp (an IEEE double doesn't have enough
1047 # precision to span microsecond resolution across years 1 thru 9999,
1048 # so comparing via timestamp necessarily calls some distinct values
1049 # equal).
1050 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1051 us = timedelta(microseconds=1)
1052 dt2 = dt1 + us
1053 self.assertEqual(dt2 - dt1, us)
1054 self.assert_(dt1 < dt2)
1055
1056 def test_bad_constructor_arguments(self):
1057 # bad years
1058 self.theclass(MINYEAR, 1, 1) # no exception
1059 self.theclass(MAXYEAR, 1, 1) # no exception
1060 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1061 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1062 # bad months
1063 self.theclass(2000, 1, 1) # no exception
1064 self.theclass(2000, 12, 1) # no exception
1065 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1066 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1067 # bad days
1068 self.theclass(2000, 2, 29) # no exception
1069 self.theclass(2004, 2, 29) # no exception
1070 self.theclass(2400, 2, 29) # no exception
1071 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1072 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1073 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1074 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1075 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1076 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1077 # bad hours
1078 self.theclass(2000, 1, 31, 0) # no exception
1079 self.theclass(2000, 1, 31, 23) # no exception
1080 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1081 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1082 # bad minutes
1083 self.theclass(2000, 1, 31, 23, 0) # no exception
1084 self.theclass(2000, 1, 31, 23, 59) # no exception
1085 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1086 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1087 # bad seconds
1088 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1089 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1090 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1091 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1092 # bad microseconds
1093 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1094 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1095 self.assertRaises(ValueError, self.theclass,
1096 2000, 1, 31, 23, 59, 59, -1)
1097 self.assertRaises(ValueError, self.theclass,
1098 2000, 1, 31, 23, 59, 59,
1099 1000000)
1100
1101 def test_hash_equality(self):
1102 d = self.theclass(2000, 12, 31, 23, 30, 17)
1103 e = self.theclass(2000, 12, 31, 23, 30, 17)
1104 self.assertEqual(d, e)
1105 self.assertEqual(hash(d), hash(e))
1106
1107 dic = {d: 1}
1108 dic[e] = 2
1109 self.assertEqual(len(dic), 1)
1110 self.assertEqual(dic[d], 2)
1111 self.assertEqual(dic[e], 2)
1112
1113 d = self.theclass(2001, 1, 1, 0, 5, 17)
1114 e = self.theclass(2001, 1, 1, 0, 5, 17)
1115 self.assertEqual(d, e)
1116 self.assertEqual(hash(d), hash(e))
1117
1118 dic = {d: 1}
1119 dic[e] = 2
1120 self.assertEqual(len(dic), 1)
1121 self.assertEqual(dic[d], 2)
1122 self.assertEqual(dic[e], 2)
1123
1124 def test_computations(self):
1125 a = self.theclass(2002, 1, 31)
1126 b = self.theclass(1956, 1, 31)
1127 diff = a-b
1128 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1129 self.assertEqual(diff.seconds, 0)
1130 self.assertEqual(diff.microseconds, 0)
1131 a = self.theclass(2002, 3, 2, 17, 6)
1132 millisec = timedelta(0, 0, 1000)
1133 hour = timedelta(0, 3600)
1134 day = timedelta(1)
1135 week = timedelta(7)
1136 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1137 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1138 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1139 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1140 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1141 self.assertEqual(a - hour, a + -hour)
1142 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1143 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1144 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1145 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1146 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1147 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1148 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1149 self.assertEqual((a + week) - a, week)
1150 self.assertEqual((a + day) - a, day)
1151 self.assertEqual((a + hour) - a, hour)
1152 self.assertEqual((a + millisec) - a, millisec)
1153 self.assertEqual((a - week) - a, -week)
1154 self.assertEqual((a - day) - a, -day)
1155 self.assertEqual((a - hour) - a, -hour)
1156 self.assertEqual((a - millisec) - a, -millisec)
1157 self.assertEqual(a - (a + week), -week)
1158 self.assertEqual(a - (a + day), -day)
1159 self.assertEqual(a - (a + hour), -hour)
1160 self.assertEqual(a - (a + millisec), -millisec)
1161 self.assertEqual(a - (a - week), week)
1162 self.assertEqual(a - (a - day), day)
1163 self.assertEqual(a - (a - hour), hour)
1164 self.assertEqual(a - (a - millisec), millisec)
1165 self.assertEqual(a + (week + day + hour + millisec),
1166 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1167 self.assertEqual(a + (week + day + hour + millisec),
1168 (((a + week) + day) + hour) + millisec)
1169 self.assertEqual(a - (week + day + hour + millisec),
1170 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1171 self.assertEqual(a - (week + day + hour + millisec),
1172 (((a - week) - day) - hour) - millisec)
1173 # Add/sub ints, longs, floats should be illegal
1174 for i in 1, 1L, 1.0:
1175 self.assertRaises(TypeError, lambda: a+i)
1176 self.assertRaises(TypeError, lambda: a-i)
1177 self.assertRaises(TypeError, lambda: i+a)
1178 self.assertRaises(TypeError, lambda: i-a)
1179
1180 # delta - datetime is senseless.
1181 self.assertRaises(TypeError, lambda: day - a)
1182 # mixing datetime and (delta or datetime) via * or // is senseless
1183 self.assertRaises(TypeError, lambda: day * a)
1184 self.assertRaises(TypeError, lambda: a * day)
1185 self.assertRaises(TypeError, lambda: day // a)
1186 self.assertRaises(TypeError, lambda: a // day)
1187 self.assertRaises(TypeError, lambda: a * a)
1188 self.assertRaises(TypeError, lambda: a // a)
1189 # datetime + datetime is senseless
1190 self.assertRaises(TypeError, lambda: a + a)
1191
1192 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001193 args = 6, 7, 23, 20, 59, 1, 64**2
1194 orig = self.theclass(*args)
1195 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001196 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001197 derived = self.theclass(1, 1, 1)
1198 derived.__setstate__(state)
1199 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001200 for pickler, unpickler, proto in pickle_choices:
1201 green = pickler.dumps(orig, proto)
1202 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00001203 self.assertEqual(orig, derived)
1204
1205 def test_more_compare(self):
1206 # The test_compare() inherited from TestDate covers the error cases.
1207 # We just want to test lexicographic ordering on the members datetime
1208 # has that date lacks.
1209 args = [2000, 11, 29, 20, 58, 16, 999998]
1210 t1 = self.theclass(*args)
1211 t2 = self.theclass(*args)
1212 self.failUnless(t1 == t2)
1213 self.failUnless(t1 <= t2)
1214 self.failUnless(t1 >= t2)
1215 self.failUnless(not t1 != t2)
1216 self.failUnless(not t1 < t2)
1217 self.failUnless(not t1 > t2)
1218 self.assertEqual(cmp(t1, t2), 0)
1219 self.assertEqual(cmp(t2, t1), 0)
1220
1221 for i in range(len(args)):
1222 newargs = args[:]
1223 newargs[i] = args[i] + 1
1224 t2 = self.theclass(*newargs) # this is larger than t1
1225 self.failUnless(t1 < t2)
1226 self.failUnless(t2 > t1)
1227 self.failUnless(t1 <= t2)
1228 self.failUnless(t2 >= t1)
1229 self.failUnless(t1 != t2)
1230 self.failUnless(t2 != t1)
1231 self.failUnless(not t1 == t2)
1232 self.failUnless(not t2 == t1)
1233 self.failUnless(not t1 > t2)
1234 self.failUnless(not t2 < t1)
1235 self.failUnless(not t1 >= t2)
1236 self.failUnless(not t2 <= t1)
1237 self.assertEqual(cmp(t1, t2), -1)
1238 self.assertEqual(cmp(t2, t1), 1)
1239
1240
1241 # A helper for timestamp constructor tests.
1242 def verify_field_equality(self, expected, got):
1243 self.assertEqual(expected.tm_year, got.year)
1244 self.assertEqual(expected.tm_mon, got.month)
1245 self.assertEqual(expected.tm_mday, got.day)
1246 self.assertEqual(expected.tm_hour, got.hour)
1247 self.assertEqual(expected.tm_min, got.minute)
1248 self.assertEqual(expected.tm_sec, got.second)
1249
1250 def test_fromtimestamp(self):
1251 import time
1252
1253 ts = time.time()
1254 expected = time.localtime(ts)
1255 got = self.theclass.fromtimestamp(ts)
1256 self.verify_field_equality(expected, got)
1257
1258 def test_utcfromtimestamp(self):
1259 import time
1260
1261 ts = time.time()
1262 expected = time.gmtime(ts)
1263 got = self.theclass.utcfromtimestamp(ts)
1264 self.verify_field_equality(expected, got)
1265
1266 def test_utcnow(self):
1267 import time
1268
1269 # Call it a success if utcnow() and utcfromtimestamp() are within
1270 # a second of each other.
1271 tolerance = timedelta(seconds=1)
1272 for dummy in range(3):
1273 from_now = self.theclass.utcnow()
1274 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1275 if abs(from_timestamp - from_now) <= tolerance:
1276 break
1277 # Else try again a few times.
1278 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1279
1280 def test_more_timetuple(self):
1281 # This tests fields beyond those tested by the TestDate.test_timetuple.
1282 t = self.theclass(2004, 12, 31, 6, 22, 33)
1283 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1284 self.assertEqual(t.timetuple(),
1285 (t.year, t.month, t.day,
1286 t.hour, t.minute, t.second,
1287 t.weekday(),
1288 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1289 -1))
1290 tt = t.timetuple()
1291 self.assertEqual(tt.tm_year, t.year)
1292 self.assertEqual(tt.tm_mon, t.month)
1293 self.assertEqual(tt.tm_mday, t.day)
1294 self.assertEqual(tt.tm_hour, t.hour)
1295 self.assertEqual(tt.tm_min, t.minute)
1296 self.assertEqual(tt.tm_sec, t.second)
1297 self.assertEqual(tt.tm_wday, t.weekday())
1298 self.assertEqual(tt.tm_yday, t.toordinal() -
1299 date(t.year, 1, 1).toordinal() + 1)
1300 self.assertEqual(tt.tm_isdst, -1)
1301
1302 def test_more_strftime(self):
1303 # This tests fields beyond those tested by the TestDate.test_strftime.
1304 t = self.theclass(2004, 12, 31, 6, 22, 33)
1305 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1306 "12 31 04 33 22 06 366")
1307
1308 def test_extract(self):
1309 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1310 self.assertEqual(dt.date(), date(2002, 3, 4))
1311 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1312
1313 def test_combine(self):
1314 d = date(2002, 3, 4)
1315 t = time(18, 45, 3, 1234)
1316 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1317 combine = self.theclass.combine
1318 dt = combine(d, t)
1319 self.assertEqual(dt, expected)
1320
1321 dt = combine(time=t, date=d)
1322 self.assertEqual(dt, expected)
1323
1324 self.assertEqual(d, dt.date())
1325 self.assertEqual(t, dt.time())
1326 self.assertEqual(dt, combine(dt.date(), dt.time()))
1327
1328 self.assertRaises(TypeError, combine) # need an arg
1329 self.assertRaises(TypeError, combine, d) # need two args
1330 self.assertRaises(TypeError, combine, t, d) # args reversed
1331 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1332 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1333
Tim Peters12bf3392002-12-24 05:41:27 +00001334 def test_replace(self):
1335 cls = self.theclass
1336 args = [1, 2, 3, 4, 5, 6, 7]
1337 base = cls(*args)
1338 self.assertEqual(base, base.replace())
1339
1340 i = 0
1341 for name, newval in (("year", 2),
1342 ("month", 3),
1343 ("day", 4),
1344 ("hour", 5),
1345 ("minute", 6),
1346 ("second", 7),
1347 ("microsecond", 8)):
1348 newargs = args[:]
1349 newargs[i] = newval
1350 expected = cls(*newargs)
1351 got = base.replace(**{name: newval})
1352 self.assertEqual(expected, got)
1353 i += 1
1354
1355 # Out of bounds.
1356 base = cls(2000, 2, 29)
1357 self.assertRaises(ValueError, base.replace, year=2001)
1358
Tim Peters80475bb2002-12-25 07:40:55 +00001359 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001360 # Pretty boring! The TZ test is more interesting here. astimezone()
1361 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001362 dt = self.theclass.now()
1363 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001364 self.assertRaises(TypeError, dt.astimezone) # not enough args
1365 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1366 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001367 self.assertRaises(ValueError, dt.astimezone, f) # naive
1368 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001369
Tim Peters52dcce22003-01-23 16:36:11 +00001370 class Bogus(tzinfo):
1371 def utcoffset(self, dt): return None
1372 def dst(self, dt): return timedelta(0)
1373 bog = Bogus()
1374 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1375
1376 class AlsoBogus(tzinfo):
1377 def utcoffset(self, dt): return timedelta(0)
1378 def dst(self, dt): return None
1379 alsobog = AlsoBogus()
1380 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001381
Tim Peters2a799bf2002-12-16 20:18:38 +00001382class TestTime(unittest.TestCase):
1383
1384 theclass = time
1385
1386 def test_basic_attributes(self):
1387 t = self.theclass(12, 0)
1388 self.assertEqual(t.hour, 12)
1389 self.assertEqual(t.minute, 0)
1390 self.assertEqual(t.second, 0)
1391 self.assertEqual(t.microsecond, 0)
1392
1393 def test_basic_attributes_nonzero(self):
1394 # Make sure all attributes are non-zero so bugs in
1395 # bit-shifting access show up.
1396 t = self.theclass(12, 59, 59, 8000)
1397 self.assertEqual(t.hour, 12)
1398 self.assertEqual(t.minute, 59)
1399 self.assertEqual(t.second, 59)
1400 self.assertEqual(t.microsecond, 8000)
1401
1402 def test_roundtrip(self):
1403 t = self.theclass(1, 2, 3, 4)
1404
1405 # Verify t -> string -> time identity.
1406 s = repr(t)
1407 self.failUnless(s.startswith('datetime.'))
1408 s = s[9:]
1409 t2 = eval(s)
1410 self.assertEqual(t, t2)
1411
1412 # Verify identity via reconstructing from pieces.
1413 t2 = self.theclass(t.hour, t.minute, t.second,
1414 t.microsecond)
1415 self.assertEqual(t, t2)
1416
1417 def test_comparing(self):
1418 args = [1, 2, 3, 4]
1419 t1 = self.theclass(*args)
1420 t2 = self.theclass(*args)
1421 self.failUnless(t1 == t2)
1422 self.failUnless(t1 <= t2)
1423 self.failUnless(t1 >= t2)
1424 self.failUnless(not t1 != t2)
1425 self.failUnless(not t1 < t2)
1426 self.failUnless(not t1 > t2)
1427 self.assertEqual(cmp(t1, t2), 0)
1428 self.assertEqual(cmp(t2, t1), 0)
1429
1430 for i in range(len(args)):
1431 newargs = args[:]
1432 newargs[i] = args[i] + 1
1433 t2 = self.theclass(*newargs) # this is larger than t1
1434 self.failUnless(t1 < t2)
1435 self.failUnless(t2 > t1)
1436 self.failUnless(t1 <= t2)
1437 self.failUnless(t2 >= t1)
1438 self.failUnless(t1 != t2)
1439 self.failUnless(t2 != t1)
1440 self.failUnless(not t1 == t2)
1441 self.failUnless(not t2 == t1)
1442 self.failUnless(not t1 > t2)
1443 self.failUnless(not t2 < t1)
1444 self.failUnless(not t1 >= t2)
1445 self.failUnless(not t2 <= t1)
1446 self.assertEqual(cmp(t1, t2), -1)
1447 self.assertEqual(cmp(t2, t1), 1)
1448
Tim Peters0bf60bd2003-01-08 20:40:01 +00001449 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1450 if CMP_BUG_FIXED:
1451 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1452 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001453 self.assertRaises(TypeError, lambda: t1 == badarg)
1454 self.assertRaises(TypeError, lambda: t1 != badarg)
1455 self.assertRaises(TypeError, lambda: t1 <= badarg)
1456 self.assertRaises(TypeError, lambda: t1 < badarg)
1457 self.assertRaises(TypeError, lambda: t1 > badarg)
1458 self.assertRaises(TypeError, lambda: t1 >= badarg)
1459 self.assertRaises(TypeError, lambda: badarg == t1)
1460 self.assertRaises(TypeError, lambda: badarg != t1)
1461 self.assertRaises(TypeError, lambda: badarg <= t1)
1462 self.assertRaises(TypeError, lambda: badarg < t1)
1463 self.assertRaises(TypeError, lambda: badarg > t1)
1464 self.assertRaises(TypeError, lambda: badarg >= t1)
1465
1466 def test_bad_constructor_arguments(self):
1467 # bad hours
1468 self.theclass(0, 0) # no exception
1469 self.theclass(23, 0) # no exception
1470 self.assertRaises(ValueError, self.theclass, -1, 0)
1471 self.assertRaises(ValueError, self.theclass, 24, 0)
1472 # bad minutes
1473 self.theclass(23, 0) # no exception
1474 self.theclass(23, 59) # no exception
1475 self.assertRaises(ValueError, self.theclass, 23, -1)
1476 self.assertRaises(ValueError, self.theclass, 23, 60)
1477 # bad seconds
1478 self.theclass(23, 59, 0) # no exception
1479 self.theclass(23, 59, 59) # no exception
1480 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1481 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1482 # bad microseconds
1483 self.theclass(23, 59, 59, 0) # no exception
1484 self.theclass(23, 59, 59, 999999) # no exception
1485 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1486 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1487
1488 def test_hash_equality(self):
1489 d = self.theclass(23, 30, 17)
1490 e = self.theclass(23, 30, 17)
1491 self.assertEqual(d, e)
1492 self.assertEqual(hash(d), hash(e))
1493
1494 dic = {d: 1}
1495 dic[e] = 2
1496 self.assertEqual(len(dic), 1)
1497 self.assertEqual(dic[d], 2)
1498 self.assertEqual(dic[e], 2)
1499
1500 d = self.theclass(0, 5, 17)
1501 e = self.theclass(0, 5, 17)
1502 self.assertEqual(d, e)
1503 self.assertEqual(hash(d), hash(e))
1504
1505 dic = {d: 1}
1506 dic[e] = 2
1507 self.assertEqual(len(dic), 1)
1508 self.assertEqual(dic[d], 2)
1509 self.assertEqual(dic[e], 2)
1510
1511 def test_isoformat(self):
1512 t = self.theclass(4, 5, 1, 123)
1513 self.assertEqual(t.isoformat(), "04:05:01.000123")
1514 self.assertEqual(t.isoformat(), str(t))
1515
1516 t = self.theclass()
1517 self.assertEqual(t.isoformat(), "00:00:00")
1518 self.assertEqual(t.isoformat(), str(t))
1519
1520 t = self.theclass(microsecond=1)
1521 self.assertEqual(t.isoformat(), "00:00:00.000001")
1522 self.assertEqual(t.isoformat(), str(t))
1523
1524 t = self.theclass(microsecond=10)
1525 self.assertEqual(t.isoformat(), "00:00:00.000010")
1526 self.assertEqual(t.isoformat(), str(t))
1527
1528 t = self.theclass(microsecond=100)
1529 self.assertEqual(t.isoformat(), "00:00:00.000100")
1530 self.assertEqual(t.isoformat(), str(t))
1531
1532 t = self.theclass(microsecond=1000)
1533 self.assertEqual(t.isoformat(), "00:00:00.001000")
1534 self.assertEqual(t.isoformat(), str(t))
1535
1536 t = self.theclass(microsecond=10000)
1537 self.assertEqual(t.isoformat(), "00:00:00.010000")
1538 self.assertEqual(t.isoformat(), str(t))
1539
1540 t = self.theclass(microsecond=100000)
1541 self.assertEqual(t.isoformat(), "00:00:00.100000")
1542 self.assertEqual(t.isoformat(), str(t))
1543
1544 def test_strftime(self):
1545 t = self.theclass(1, 2, 3, 4)
1546 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1547 # A naive object replaces %z and %Z with empty strings.
1548 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1549
1550 def test_str(self):
1551 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1552 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1553 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1554 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1555 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1556
1557 def test_repr(self):
1558 name = 'datetime.' + self.theclass.__name__
1559 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1560 "%s(1, 2, 3, 4)" % name)
1561 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1562 "%s(10, 2, 3, 4000)" % name)
1563 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1564 "%s(0, 2, 3, 400000)" % name)
1565 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1566 "%s(12, 2, 3)" % name)
1567 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1568 "%s(23, 15)" % name)
1569
1570 def test_resolution_info(self):
1571 self.assert_(isinstance(self.theclass.min, self.theclass))
1572 self.assert_(isinstance(self.theclass.max, self.theclass))
1573 self.assert_(isinstance(self.theclass.resolution, timedelta))
1574 self.assert_(self.theclass.max > self.theclass.min)
1575
1576 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001577 args = 20, 59, 16, 64**2
1578 orig = self.theclass(*args)
1579 state = orig.__getstate__()
Tim Peters0bf60bd2003-01-08 20:40:01 +00001580 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
Tim Peters2a799bf2002-12-16 20:18:38 +00001581 derived = self.theclass()
1582 derived.__setstate__(state)
1583 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001584 for pickler, unpickler, proto in pickle_choices:
1585 green = pickler.dumps(orig, proto)
1586 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00001587 self.assertEqual(orig, derived)
1588
1589 def test_bool(self):
1590 cls = self.theclass
1591 self.failUnless(cls(1))
1592 self.failUnless(cls(0, 1))
1593 self.failUnless(cls(0, 0, 1))
1594 self.failUnless(cls(0, 0, 0, 1))
1595 self.failUnless(not cls(0))
1596 self.failUnless(not cls())
1597
Tim Peters12bf3392002-12-24 05:41:27 +00001598 def test_replace(self):
1599 cls = self.theclass
1600 args = [1, 2, 3, 4]
1601 base = cls(*args)
1602 self.assertEqual(base, base.replace())
1603
1604 i = 0
1605 for name, newval in (("hour", 5),
1606 ("minute", 6),
1607 ("second", 7),
1608 ("microsecond", 8)):
1609 newargs = args[:]
1610 newargs[i] = newval
1611 expected = cls(*newargs)
1612 got = base.replace(**{name: newval})
1613 self.assertEqual(expected, got)
1614 i += 1
1615
1616 # Out of bounds.
1617 base = cls(1)
1618 self.assertRaises(ValueError, base.replace, hour=24)
1619 self.assertRaises(ValueError, base.replace, minute=-1)
1620 self.assertRaises(ValueError, base.replace, second=100)
1621 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1622
Tim Peters855fe882002-12-22 03:43:39 +00001623# A mixin for classes with a tzinfo= argument. Subclasses must define
1624# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001625# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001626class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001627
Tim Petersbad8ff02002-12-30 20:52:32 +00001628 def test_argument_passing(self):
1629 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001630 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001631 class introspective(tzinfo):
1632 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001633 def utcoffset(self, dt):
1634 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001635 dst = utcoffset
1636
1637 obj = cls(1, 2, 3, tzinfo=introspective())
1638
Tim Peters0bf60bd2003-01-08 20:40:01 +00001639 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001640 self.assertEqual(obj.tzname(), expected)
1641
Tim Peters0bf60bd2003-01-08 20:40:01 +00001642 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001643 self.assertEqual(obj.utcoffset(), expected)
1644 self.assertEqual(obj.dst(), expected)
1645
Tim Peters855fe882002-12-22 03:43:39 +00001646 def test_bad_tzinfo_classes(self):
1647 cls = self.theclass
1648 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001649
Tim Peters855fe882002-12-22 03:43:39 +00001650 class NiceTry(object):
1651 def __init__(self): pass
1652 def utcoffset(self, dt): pass
1653 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1654
1655 class BetterTry(tzinfo):
1656 def __init__(self): pass
1657 def utcoffset(self, dt): pass
1658 b = BetterTry()
1659 t = cls(1, 1, 1, tzinfo=b)
1660 self.failUnless(t.tzinfo is b)
1661
1662 def test_utc_offset_out_of_bounds(self):
1663 class Edgy(tzinfo):
1664 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001665 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001666 def utcoffset(self, dt):
1667 return self.offset
1668
1669 cls = self.theclass
1670 for offset, legit in ((-1440, False),
1671 (-1439, True),
1672 (1439, True),
1673 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001674 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001675 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001676 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001677 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001678 else:
1679 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001680 if legit:
1681 aofs = abs(offset)
1682 h, m = divmod(aofs, 60)
1683 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001684 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001685 t = t.timetz()
1686 self.assertEqual(str(t), "01:02:03" + tag)
1687 else:
1688 self.assertRaises(ValueError, str, t)
1689
1690 def test_tzinfo_classes(self):
1691 cls = self.theclass
1692 class C1(tzinfo):
1693 def utcoffset(self, dt): return None
1694 def dst(self, dt): return None
1695 def tzname(self, dt): return None
1696 for t in (cls(1, 1, 1),
1697 cls(1, 1, 1, tzinfo=None),
1698 cls(1, 1, 1, tzinfo=C1())):
1699 self.failUnless(t.utcoffset() is None)
1700 self.failUnless(t.dst() is None)
1701 self.failUnless(t.tzname() is None)
1702
Tim Peters855fe882002-12-22 03:43:39 +00001703 class C3(tzinfo):
1704 def utcoffset(self, dt): return timedelta(minutes=-1439)
1705 def dst(self, dt): return timedelta(minutes=1439)
1706 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001707 t = cls(1, 1, 1, tzinfo=C3())
1708 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1709 self.assertEqual(t.dst(), timedelta(minutes=1439))
1710 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001711
1712 # Wrong types.
1713 class C4(tzinfo):
1714 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001715 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001716 def tzname(self, dt): return 0
1717 t = cls(1, 1, 1, tzinfo=C4())
1718 self.assertRaises(TypeError, t.utcoffset)
1719 self.assertRaises(TypeError, t.dst)
1720 self.assertRaises(TypeError, t.tzname)
1721
1722 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001723 class C6(tzinfo):
1724 def utcoffset(self, dt): return timedelta(hours=-24)
1725 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001726 t = cls(1, 1, 1, tzinfo=C6())
1727 self.assertRaises(ValueError, t.utcoffset)
1728 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001729
1730 # Not a whole number of minutes.
1731 class C7(tzinfo):
1732 def utcoffset(self, dt): return timedelta(seconds=61)
1733 def dst(self, dt): return timedelta(microseconds=-81)
1734 t = cls(1, 1, 1, tzinfo=C7())
1735 self.assertRaises(ValueError, t.utcoffset)
1736 self.assertRaises(ValueError, t.dst)
1737
Tim Peters4c0db782002-12-26 05:01:19 +00001738 def test_aware_compare(self):
1739 cls = self.theclass
1740
Tim Peters60c76e42002-12-27 00:41:11 +00001741 # Ensure that utcoffset() gets ignored if the comparands have
1742 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001743 class OperandDependentOffset(tzinfo):
1744 def utcoffset(self, t):
1745 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001746 # d0 and d1 equal after adjustment
1747 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001748 else:
Tim Peters397301e2003-01-02 21:28:08 +00001749 # d2 off in the weeds
1750 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001751
1752 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1753 d0 = base.replace(minute=3)
1754 d1 = base.replace(minute=9)
1755 d2 = base.replace(minute=11)
1756 for x in d0, d1, d2:
1757 for y in d0, d1, d2:
1758 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001759 expected = cmp(x.minute, y.minute)
1760 self.assertEqual(got, expected)
1761
1762 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001763 # Note that a time can't actually have an operand-depedent offset,
1764 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1765 # so skip this test for time.
1766 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001767 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1768 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1769 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1770 for x in d0, d1, d2:
1771 for y in d0, d1, d2:
1772 got = cmp(x, y)
1773 if (x is d0 or x is d1) and (y is d0 or y is d1):
1774 expected = 0
1775 elif x is y is d2:
1776 expected = 0
1777 elif x is d2:
1778 expected = -1
1779 else:
1780 assert y is d2
1781 expected = 1
1782 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001783
Tim Peters855fe882002-12-22 03:43:39 +00001784
Tim Peters0bf60bd2003-01-08 20:40:01 +00001785# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001786class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001787 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001788
1789 def test_empty(self):
1790 t = self.theclass()
1791 self.assertEqual(t.hour, 0)
1792 self.assertEqual(t.minute, 0)
1793 self.assertEqual(t.second, 0)
1794 self.assertEqual(t.microsecond, 0)
1795 self.failUnless(t.tzinfo is None)
1796
Tim Peters2a799bf2002-12-16 20:18:38 +00001797 def test_zones(self):
1798 est = FixedOffset(-300, "EST", 1)
1799 utc = FixedOffset(0, "UTC", -2)
1800 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001801 t1 = time( 7, 47, tzinfo=est)
1802 t2 = time(12, 47, tzinfo=utc)
1803 t3 = time(13, 47, tzinfo=met)
1804 t4 = time(microsecond=40)
1805 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001806
1807 self.assertEqual(t1.tzinfo, est)
1808 self.assertEqual(t2.tzinfo, utc)
1809 self.assertEqual(t3.tzinfo, met)
1810 self.failUnless(t4.tzinfo is None)
1811 self.assertEqual(t5.tzinfo, utc)
1812
Tim Peters855fe882002-12-22 03:43:39 +00001813 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1814 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1815 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001816 self.failUnless(t4.utcoffset() is None)
1817 self.assertRaises(TypeError, t1.utcoffset, "no args")
1818
1819 self.assertEqual(t1.tzname(), "EST")
1820 self.assertEqual(t2.tzname(), "UTC")
1821 self.assertEqual(t3.tzname(), "MET")
1822 self.failUnless(t4.tzname() is None)
1823 self.assertRaises(TypeError, t1.tzname, "no args")
1824
Tim Peters855fe882002-12-22 03:43:39 +00001825 self.assertEqual(t1.dst(), timedelta(minutes=1))
1826 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1827 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001828 self.failUnless(t4.dst() is None)
1829 self.assertRaises(TypeError, t1.dst, "no args")
1830
1831 self.assertEqual(hash(t1), hash(t2))
1832 self.assertEqual(hash(t1), hash(t3))
1833 self.assertEqual(hash(t2), hash(t3))
1834
1835 self.assertEqual(t1, t2)
1836 self.assertEqual(t1, t3)
1837 self.assertEqual(t2, t3)
1838 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1839 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1840 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1841
1842 self.assertEqual(str(t1), "07:47:00-05:00")
1843 self.assertEqual(str(t2), "12:47:00+00:00")
1844 self.assertEqual(str(t3), "13:47:00+01:00")
1845 self.assertEqual(str(t4), "00:00:00.000040")
1846 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1847
1848 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1849 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1850 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1851 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1852 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1853
Tim Peters0bf60bd2003-01-08 20:40:01 +00001854 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001855 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1856 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1857 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1858 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1859 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1860
1861 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1862 "07:47:00 %Z=EST %z=-0500")
1863 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1864 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1865
1866 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001867 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001868 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1869 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1870
Tim Petersb92bb712002-12-21 17:44:07 +00001871 # Check that an invalid tzname result raises an exception.
1872 class Badtzname(tzinfo):
1873 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001874 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001875 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1876 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001877
1878 def test_hash_edge_cases(self):
1879 # Offsets that overflow a basic time.
1880 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1881 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1882 self.assertEqual(hash(t1), hash(t2))
1883
1884 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1885 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1886 self.assertEqual(hash(t1), hash(t2))
1887
Tim Peters2a799bf2002-12-16 20:18:38 +00001888 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001889 # Try one without a tzinfo.
1890 args = 20, 59, 16, 64**2
1891 orig = self.theclass(*args)
1892 state = orig.__getstate__()
1893 self.assertEqual(state, ('\x14\x3b\x10\x00\x10\x00',))
1894 derived = self.theclass()
1895 derived.__setstate__(state)
1896 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001897 for pickler, unpickler, proto in pickle_choices:
1898 green = pickler.dumps(orig, proto)
1899 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00001900 self.assertEqual(orig, derived)
1901
1902 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001903 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001904 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
1905 state = orig.__getstate__()
Tim Peters37f39822003-01-10 03:49:02 +00001906 derived = self.theclass(tzinfo=FixedOffset(0, "UTC", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00001907 derived.__setstate__(state)
1908 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001909 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001910 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001911 self.assertEqual(derived.tzname(), 'cookie')
1912
Guido van Rossum177e41a2003-01-30 22:06:23 +00001913 for pickler, unpickler, proto in pickle_choices:
1914 green = pickler.dumps(orig, proto)
1915 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00001916 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00001917 self.failUnless(isinstance(derived.tzinfo,
1918 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00001919 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00001920 self.assertEqual(derived.tzname(), 'cookie')
1921
1922 def test_more_bool(self):
1923 # Test cases with non-None tzinfo.
1924 cls = self.theclass
1925
1926 t = cls(0, tzinfo=FixedOffset(-300, ""))
1927 self.failUnless(t)
1928
1929 t = cls(5, tzinfo=FixedOffset(-300, ""))
1930 self.failUnless(t)
1931
1932 t = cls(5, tzinfo=FixedOffset(300, ""))
1933 self.failUnless(not t)
1934
1935 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1936 self.failUnless(not t)
1937
1938 # Mostly ensuring this doesn't overflow internally.
1939 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1940 self.failUnless(t)
1941
1942 # But this should yield a value error -- the utcoffset is bogus.
1943 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1944 self.assertRaises(ValueError, lambda: bool(t))
1945
1946 # Likewise.
1947 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1948 self.assertRaises(ValueError, lambda: bool(t))
1949
Tim Peters12bf3392002-12-24 05:41:27 +00001950 def test_replace(self):
1951 cls = self.theclass
1952 z100 = FixedOffset(100, "+100")
1953 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1954 args = [1, 2, 3, 4, z100]
1955 base = cls(*args)
1956 self.assertEqual(base, base.replace())
1957
1958 i = 0
1959 for name, newval in (("hour", 5),
1960 ("minute", 6),
1961 ("second", 7),
1962 ("microsecond", 8),
1963 ("tzinfo", zm200)):
1964 newargs = args[:]
1965 newargs[i] = newval
1966 expected = cls(*newargs)
1967 got = base.replace(**{name: newval})
1968 self.assertEqual(expected, got)
1969 i += 1
1970
1971 # Ensure we can get rid of a tzinfo.
1972 self.assertEqual(base.tzname(), "+100")
1973 base2 = base.replace(tzinfo=None)
1974 self.failUnless(base2.tzinfo is None)
1975 self.failUnless(base2.tzname() is None)
1976
1977 # Ensure we can add one.
1978 base3 = base2.replace(tzinfo=z100)
1979 self.assertEqual(base, base3)
1980 self.failUnless(base.tzinfo is base3.tzinfo)
1981
1982 # Out of bounds.
1983 base = cls(1)
1984 self.assertRaises(ValueError, base.replace, hour=24)
1985 self.assertRaises(ValueError, base.replace, minute=-1)
1986 self.assertRaises(ValueError, base.replace, second=100)
1987 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1988
Tim Peters60c76e42002-12-27 00:41:11 +00001989 def test_mixed_compare(self):
1990 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001991 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001992 self.assertEqual(t1, t2)
1993 t2 = t2.replace(tzinfo=None)
1994 self.assertEqual(t1, t2)
1995 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1996 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001997 if CMP_BUG_FIXED:
1998 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1999 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002000
Tim Peters0bf60bd2003-01-08 20:40:01 +00002001 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002002 class Varies(tzinfo):
2003 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002004 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002005 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002006 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002007 return self.offset
2008
2009 v = Varies()
2010 t1 = t2.replace(tzinfo=v)
2011 t2 = t2.replace(tzinfo=v)
2012 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2013 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2014 self.assertEqual(t1, t2)
2015
2016 # But if they're not identical, it isn't ignored.
2017 t2 = t2.replace(tzinfo=Varies())
2018 self.failUnless(t1 < t2) # t1's offset counter still going up
2019
Tim Peters4c0db782002-12-26 05:01:19 +00002020
Tim Peters0bf60bd2003-01-08 20:40:01 +00002021# Testing datetime objects with a non-None tzinfo.
2022
Tim Peters855fe882002-12-22 03:43:39 +00002023class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002024 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002025
2026 def test_trivial(self):
2027 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2028 self.assertEqual(dt.year, 1)
2029 self.assertEqual(dt.month, 2)
2030 self.assertEqual(dt.day, 3)
2031 self.assertEqual(dt.hour, 4)
2032 self.assertEqual(dt.minute, 5)
2033 self.assertEqual(dt.second, 6)
2034 self.assertEqual(dt.microsecond, 7)
2035 self.assertEqual(dt.tzinfo, None)
2036
2037 def test_even_more_compare(self):
2038 # The test_compare() and test_more_compare() inherited from TestDate
2039 # and TestDateTime covered non-tzinfo cases.
2040
2041 # Smallest possible after UTC adjustment.
2042 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2043 # Largest possible after UTC adjustment.
2044 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2045 tzinfo=FixedOffset(-1439, ""))
2046
2047 # Make sure those compare correctly, and w/o overflow.
2048 self.failUnless(t1 < t2)
2049 self.failUnless(t1 != t2)
2050 self.failUnless(t2 > t1)
2051
2052 self.failUnless(t1 == t1)
2053 self.failUnless(t2 == t2)
2054
2055 # Equal afer adjustment.
2056 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2057 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2058 self.assertEqual(t1, t2)
2059
2060 # Change t1 not to subtract a minute, and t1 should be larger.
2061 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2062 self.failUnless(t1 > t2)
2063
2064 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2065 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2066 self.failUnless(t1 < t2)
2067
2068 # Back to the original t1, but make seconds resolve it.
2069 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2070 second=1)
2071 self.failUnless(t1 > t2)
2072
2073 # Likewise, but make microseconds resolve it.
2074 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2075 microsecond=1)
2076 self.failUnless(t1 > t2)
2077
2078 # Make t2 naive and it should fail.
2079 t2 = self.theclass.min
2080 self.assertRaises(TypeError, lambda: t1 == t2)
2081 self.assertEqual(t2, t2)
2082
2083 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2084 class Naive(tzinfo):
2085 def utcoffset(self, dt): return None
2086 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2087 self.assertRaises(TypeError, lambda: t1 == t2)
2088 self.assertEqual(t2, t2)
2089
2090 # OTOH, it's OK to compare two of these mixing the two ways of being
2091 # naive.
2092 t1 = self.theclass(5, 6, 7)
2093 self.assertEqual(t1, t2)
2094
2095 # Try a bogus uctoffset.
2096 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002097 def utcoffset(self, dt):
2098 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002099 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2100 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002101 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002102
Tim Peters2a799bf2002-12-16 20:18:38 +00002103 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002104 # Try one without a tzinfo.
2105 args = 6, 7, 23, 20, 59, 1, 64**2
2106 orig = self.theclass(*args)
2107 state = orig.__getstate__()
2108 self.assertEqual(state, ('\x00\x06\x07\x17\x14\x3b\x01\x00\x10\x00',))
2109 derived = self.theclass(1, 1, 1)
2110 derived.__setstate__(state)
2111 self.assertEqual(orig, derived)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002112 for pickler, unpickler, proto in pickle_choices:
2113 green = pickler.dumps(orig, proto)
2114 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00002115 self.assertEqual(orig, derived)
2116
2117 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002118 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002119 orig = self.theclass(*args, **{'tzinfo': tinfo})
2120 state = orig.__getstate__()
Tim Petersa9bc1682003-01-11 03:39:11 +00002121 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Tim Peters2a799bf2002-12-16 20:18:38 +00002122 derived.__setstate__(state)
2123 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002124 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002125 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002126 self.assertEqual(derived.tzname(), 'cookie')
2127
Guido van Rossum177e41a2003-01-30 22:06:23 +00002128 for pickler, unpickler, proto in pickle_choices:
2129 green = pickler.dumps(orig, proto)
2130 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +00002131 self.assertEqual(orig, derived)
Tim Petersfb8472c2002-12-21 05:04:42 +00002132 self.failUnless(isinstance(derived.tzinfo,
2133 PicklableFixedOffset))
Tim Peters855fe882002-12-22 03:43:39 +00002134 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
Tim Peters2a799bf2002-12-16 20:18:38 +00002135 self.assertEqual(derived.tzname(), 'cookie')
2136
2137 def test_extreme_hashes(self):
2138 # If an attempt is made to hash these via subtracting the offset
2139 # then hashing a datetime object, OverflowError results. The
2140 # Python implementation used to blow up here.
2141 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2142 hash(t)
2143 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2144 tzinfo=FixedOffset(-1439, ""))
2145 hash(t)
2146
2147 # OTOH, an OOB offset should blow up.
2148 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2149 self.assertRaises(ValueError, hash, t)
2150
2151 def test_zones(self):
2152 est = FixedOffset(-300, "EST")
2153 utc = FixedOffset(0, "UTC")
2154 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002155 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2156 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2157 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002158 self.assertEqual(t1.tzinfo, est)
2159 self.assertEqual(t2.tzinfo, utc)
2160 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002161 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2162 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2163 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002164 self.assertEqual(t1.tzname(), "EST")
2165 self.assertEqual(t2.tzname(), "UTC")
2166 self.assertEqual(t3.tzname(), "MET")
2167 self.assertEqual(hash(t1), hash(t2))
2168 self.assertEqual(hash(t1), hash(t3))
2169 self.assertEqual(hash(t2), hash(t3))
2170 self.assertEqual(t1, t2)
2171 self.assertEqual(t1, t3)
2172 self.assertEqual(t2, t3)
2173 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2174 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2175 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002176 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002177 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2178 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2179 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2180
2181 def test_combine(self):
2182 met = FixedOffset(60, "MET")
2183 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002184 tz = time(18, 45, 3, 1234, tzinfo=met)
2185 dt = datetime.combine(d, tz)
2186 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002187 tzinfo=met))
2188
2189 def test_extract(self):
2190 met = FixedOffset(60, "MET")
2191 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2192 self.assertEqual(dt.date(), date(2002, 3, 4))
2193 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002194 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002195
2196 def test_tz_aware_arithmetic(self):
2197 import random
2198
2199 now = self.theclass.now()
2200 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002201 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002202 nowaware = self.theclass.combine(now.date(), timeaware)
2203 self.failUnless(nowaware.tzinfo is tz55)
2204 self.assertEqual(nowaware.timetz(), timeaware)
2205
2206 # Can't mix aware and non-aware.
2207 self.assertRaises(TypeError, lambda: now - nowaware)
2208 self.assertRaises(TypeError, lambda: nowaware - now)
2209
Tim Peters0bf60bd2003-01-08 20:40:01 +00002210 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002211 self.assertRaises(TypeError, lambda: now + nowaware)
2212 self.assertRaises(TypeError, lambda: nowaware + now)
2213 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2214
2215 # Subtracting should yield 0.
2216 self.assertEqual(now - now, timedelta(0))
2217 self.assertEqual(nowaware - nowaware, timedelta(0))
2218
2219 # Adding a delta should preserve tzinfo.
2220 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2221 nowawareplus = nowaware + delta
2222 self.failUnless(nowaware.tzinfo is tz55)
2223 nowawareplus2 = delta + nowaware
2224 self.failUnless(nowawareplus2.tzinfo is tz55)
2225 self.assertEqual(nowawareplus, nowawareplus2)
2226
2227 # that - delta should be what we started with, and that - what we
2228 # started with should be delta.
2229 diff = nowawareplus - delta
2230 self.failUnless(diff.tzinfo is tz55)
2231 self.assertEqual(nowaware, diff)
2232 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2233 self.assertEqual(nowawareplus - nowaware, delta)
2234
2235 # Make up a random timezone.
2236 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002237 # Attach it to nowawareplus.
2238 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002239 self.failUnless(nowawareplus.tzinfo is tzr)
2240 # Make sure the difference takes the timezone adjustments into account.
2241 got = nowaware - nowawareplus
2242 # Expected: (nowaware base - nowaware offset) -
2243 # (nowawareplus base - nowawareplus offset) =
2244 # (nowaware base - nowawareplus base) +
2245 # (nowawareplus offset - nowaware offset) =
2246 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002247 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002248 self.assertEqual(got, expected)
2249
2250 # Try max possible difference.
2251 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2252 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2253 tzinfo=FixedOffset(-1439, "max"))
2254 maxdiff = max - min
2255 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2256 timedelta(minutes=2*1439))
2257
2258 def test_tzinfo_now(self):
2259 meth = self.theclass.now
2260 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2261 base = meth()
2262 # Try with and without naming the keyword.
2263 off42 = FixedOffset(42, "42")
2264 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002265 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002266 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002267 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002268 # Bad argument with and w/o naming the keyword.
2269 self.assertRaises(TypeError, meth, 16)
2270 self.assertRaises(TypeError, meth, tzinfo=16)
2271 # Bad keyword name.
2272 self.assertRaises(TypeError, meth, tinfo=off42)
2273 # Too many args.
2274 self.assertRaises(TypeError, meth, off42, off42)
2275
Tim Peters10cadce2003-01-23 19:58:02 +00002276 # We don't know which time zone we're in, and don't have a tzinfo
2277 # class to represent it, so seeing whether a tz argument actually
2278 # does a conversion is tricky.
2279 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2280 utc = FixedOffset(0, "utc", 0)
2281 for dummy in range(3):
2282 now = datetime.now(weirdtz)
2283 self.failUnless(now.tzinfo is weirdtz)
2284 utcnow = datetime.utcnow().replace(tzinfo=utc)
2285 now2 = utcnow.astimezone(weirdtz)
2286 if abs(now - now2) < timedelta(seconds=30):
2287 break
2288 # Else the code is broken, or more than 30 seconds passed between
2289 # calls; assuming the latter, just try again.
2290 else:
2291 # Three strikes and we're out.
2292 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2293
Tim Peters2a799bf2002-12-16 20:18:38 +00002294 def test_tzinfo_fromtimestamp(self):
2295 import time
2296 meth = self.theclass.fromtimestamp
2297 ts = time.time()
2298 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2299 base = meth(ts)
2300 # Try with and without naming the keyword.
2301 off42 = FixedOffset(42, "42")
2302 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002303 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002304 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002305 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002306 # Bad argument with and w/o naming the keyword.
2307 self.assertRaises(TypeError, meth, ts, 16)
2308 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2309 # Bad keyword name.
2310 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2311 # Too many args.
2312 self.assertRaises(TypeError, meth, ts, off42, off42)
2313 # Too few args.
2314 self.assertRaises(TypeError, meth)
2315
Tim Peters2a44a8d2003-01-23 20:53:10 +00002316 # Try to make sure tz= actually does some conversion.
2317 timestamp = 1000000000 # 2001-09-09 01:46:40 UTC, give or take
2318 utc = FixedOffset(0, "utc", 0)
2319 expected = datetime(2001, 9, 9, 1, 46, 40)
2320 got = datetime.utcfromtimestamp(timestamp)
2321 # We don't support leap seconds, but maybe the platfrom insists
2322 # on using them, so don't demand exact equality).
2323 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2324
2325 est = FixedOffset(-5*60, "est", 0)
2326 expected -= timedelta(hours=5)
2327 got = datetime.fromtimestamp(timestamp, est).replace(tzinfo=None)
2328 self.failUnless(abs(got - expected) < timedelta(minutes=1))
2329
Tim Peters2a799bf2002-12-16 20:18:38 +00002330 def test_tzinfo_utcnow(self):
2331 meth = self.theclass.utcnow
2332 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2333 base = meth()
2334 # Try with and without naming the keyword; for whatever reason,
2335 # utcnow() doesn't accept a tzinfo argument.
2336 off42 = FixedOffset(42, "42")
2337 self.assertRaises(TypeError, meth, off42)
2338 self.assertRaises(TypeError, meth, tzinfo=off42)
2339
2340 def test_tzinfo_utcfromtimestamp(self):
2341 import time
2342 meth = self.theclass.utcfromtimestamp
2343 ts = time.time()
2344 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2345 base = meth(ts)
2346 # Try with and without naming the keyword; for whatever reason,
2347 # utcfromtimestamp() doesn't accept a tzinfo argument.
2348 off42 = FixedOffset(42, "42")
2349 self.assertRaises(TypeError, meth, ts, off42)
2350 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2351
2352 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002353 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002354 # DST flag.
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 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2365 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2366 t = d.timetuple()
2367 self.assertEqual(1, t.tm_year)
2368 self.assertEqual(1, t.tm_mon)
2369 self.assertEqual(1, t.tm_mday)
2370 self.assertEqual(10, t.tm_hour)
2371 self.assertEqual(20, t.tm_min)
2372 self.assertEqual(30, t.tm_sec)
2373 self.assertEqual(0, t.tm_wday)
2374 self.assertEqual(1, t.tm_yday)
2375 self.assertEqual(flag, t.tm_isdst)
2376
2377 # dst() returns wrong type.
2378 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2379
2380 # dst() at the edge.
2381 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2382 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2383
2384 # dst() out of range.
2385 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2386 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2387
2388 def test_utctimetuple(self):
2389 class DST(tzinfo):
2390 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002391 if isinstance(dstvalue, int):
2392 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002393 self.dstvalue = dstvalue
2394 def dst(self, dt):
2395 return self.dstvalue
2396
2397 cls = self.theclass
2398 # This can't work: DST didn't implement utcoffset.
2399 self.assertRaises(NotImplementedError,
2400 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2401
2402 class UOFS(DST):
2403 def __init__(self, uofs, dofs=None):
2404 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002405 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002406 def utcoffset(self, dt):
2407 return self.uofs
2408
2409 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2410 # in effect for a UTC time.
2411 for dstvalue in -33, 33, 0, None:
2412 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2413 t = d.utctimetuple()
2414 self.assertEqual(d.year, t.tm_year)
2415 self.assertEqual(d.month, t.tm_mon)
2416 self.assertEqual(d.day, t.tm_mday)
2417 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2418 self.assertEqual(13, t.tm_min)
2419 self.assertEqual(d.second, t.tm_sec)
2420 self.assertEqual(d.weekday(), t.tm_wday)
2421 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2422 t.tm_yday)
2423 self.assertEqual(0, t.tm_isdst)
2424
2425 # At the edges, UTC adjustment can normalize into years out-of-range
2426 # for a datetime object. Ensure that a correct timetuple is
2427 # created anyway.
2428 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2429 # That goes back 1 minute less than a full day.
2430 t = tiny.utctimetuple()
2431 self.assertEqual(t.tm_year, MINYEAR-1)
2432 self.assertEqual(t.tm_mon, 12)
2433 self.assertEqual(t.tm_mday, 31)
2434 self.assertEqual(t.tm_hour, 0)
2435 self.assertEqual(t.tm_min, 1)
2436 self.assertEqual(t.tm_sec, 37)
2437 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2438 self.assertEqual(t.tm_isdst, 0)
2439
2440 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2441 # That goes forward 1 minute less than a full day.
2442 t = huge.utctimetuple()
2443 self.assertEqual(t.tm_year, MAXYEAR+1)
2444 self.assertEqual(t.tm_mon, 1)
2445 self.assertEqual(t.tm_mday, 1)
2446 self.assertEqual(t.tm_hour, 23)
2447 self.assertEqual(t.tm_min, 58)
2448 self.assertEqual(t.tm_sec, 37)
2449 self.assertEqual(t.tm_yday, 1)
2450 self.assertEqual(t.tm_isdst, 0)
2451
2452 def test_tzinfo_isoformat(self):
2453 zero = FixedOffset(0, "+00:00")
2454 plus = FixedOffset(220, "+03:40")
2455 minus = FixedOffset(-231, "-03:51")
2456 unknown = FixedOffset(None, "")
2457
2458 cls = self.theclass
2459 datestr = '0001-02-03'
2460 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002461 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002462 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2463 timestr = '04:05:59' + (us and '.987001' or '')
2464 ofsstr = ofs is not None and d.tzname() or ''
2465 tailstr = timestr + ofsstr
2466 iso = d.isoformat()
2467 self.assertEqual(iso, datestr + 'T' + tailstr)
2468 self.assertEqual(iso, d.isoformat('T'))
2469 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2470 self.assertEqual(str(d), datestr + ' ' + tailstr)
2471
Tim Peters12bf3392002-12-24 05:41:27 +00002472 def test_replace(self):
2473 cls = self.theclass
2474 z100 = FixedOffset(100, "+100")
2475 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2476 args = [1, 2, 3, 4, 5, 6, 7, z100]
2477 base = cls(*args)
2478 self.assertEqual(base, base.replace())
2479
2480 i = 0
2481 for name, newval in (("year", 2),
2482 ("month", 3),
2483 ("day", 4),
2484 ("hour", 5),
2485 ("minute", 6),
2486 ("second", 7),
2487 ("microsecond", 8),
2488 ("tzinfo", zm200)):
2489 newargs = args[:]
2490 newargs[i] = newval
2491 expected = cls(*newargs)
2492 got = base.replace(**{name: newval})
2493 self.assertEqual(expected, got)
2494 i += 1
2495
2496 # Ensure we can get rid of a tzinfo.
2497 self.assertEqual(base.tzname(), "+100")
2498 base2 = base.replace(tzinfo=None)
2499 self.failUnless(base2.tzinfo is None)
2500 self.failUnless(base2.tzname() is None)
2501
2502 # Ensure we can add one.
2503 base3 = base2.replace(tzinfo=z100)
2504 self.assertEqual(base, base3)
2505 self.failUnless(base.tzinfo is base3.tzinfo)
2506
2507 # Out of bounds.
2508 base = cls(2000, 2, 29)
2509 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002510
Tim Peters80475bb2002-12-25 07:40:55 +00002511 def test_more_astimezone(self):
2512 # The inherited test_astimezone covered some trivial and error cases.
2513 fnone = FixedOffset(None, "None")
2514 f44m = FixedOffset(44, "44")
2515 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2516
Tim Peters10cadce2003-01-23 19:58:02 +00002517 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002518 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002519 # Replacing with degenerate tzinfo raises an exception.
2520 self.assertRaises(ValueError, dt.astimezone, fnone)
2521 # Ditto with None tz.
2522 self.assertRaises(TypeError, dt.astimezone, None)
2523 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002524 x = dt.astimezone(dt.tzinfo)
2525 self.failUnless(x.tzinfo is f44m)
2526 self.assertEqual(x.date(), dt.date())
2527 self.assertEqual(x.time(), dt.time())
2528
2529 # Replacing with different tzinfo does adjust.
2530 got = dt.astimezone(fm5h)
2531 self.failUnless(got.tzinfo is fm5h)
2532 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2533 expected = dt - dt.utcoffset() # in effect, convert to UTC
2534 expected += fm5h.utcoffset(dt) # and from there to local time
2535 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2536 self.assertEqual(got.date(), expected.date())
2537 self.assertEqual(got.time(), expected.time())
2538 self.assertEqual(got.timetz(), expected.timetz())
2539 self.failUnless(got.tzinfo is expected.tzinfo)
2540 self.assertEqual(got, expected)
2541
Tim Peters4c0db782002-12-26 05:01:19 +00002542 def test_aware_subtract(self):
2543 cls = self.theclass
2544
Tim Peters60c76e42002-12-27 00:41:11 +00002545 # Ensure that utcoffset() is ignored when the operands have the
2546 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002547 class OperandDependentOffset(tzinfo):
2548 def utcoffset(self, t):
2549 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002550 # d0 and d1 equal after adjustment
2551 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002552 else:
Tim Peters397301e2003-01-02 21:28:08 +00002553 # d2 off in the weeds
2554 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002555
2556 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2557 d0 = base.replace(minute=3)
2558 d1 = base.replace(minute=9)
2559 d2 = base.replace(minute=11)
2560 for x in d0, d1, d2:
2561 for y in d0, d1, d2:
2562 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002563 expected = timedelta(minutes=x.minute - y.minute)
2564 self.assertEqual(got, expected)
2565
2566 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2567 # ignored.
2568 base = cls(8, 9, 10, 11, 12, 13, 14)
2569 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2570 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2571 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2572 for x in d0, d1, d2:
2573 for y in d0, d1, d2:
2574 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002575 if (x is d0 or x is d1) and (y is d0 or y is d1):
2576 expected = timedelta(0)
2577 elif x is y is d2:
2578 expected = timedelta(0)
2579 elif x is d2:
2580 expected = timedelta(minutes=(11-59)-0)
2581 else:
2582 assert y is d2
2583 expected = timedelta(minutes=0-(11-59))
2584 self.assertEqual(got, expected)
2585
Tim Peters60c76e42002-12-27 00:41:11 +00002586 def test_mixed_compare(self):
2587 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002588 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002589 self.assertEqual(t1, t2)
2590 t2 = t2.replace(tzinfo=None)
2591 self.assertEqual(t1, t2)
2592 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2593 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002594 if CMP_BUG_FIXED:
2595 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2596 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002597
Tim Peters0bf60bd2003-01-08 20:40:01 +00002598 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002599 class Varies(tzinfo):
2600 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002601 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002602 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002603 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002604 return self.offset
2605
2606 v = Varies()
2607 t1 = t2.replace(tzinfo=v)
2608 t2 = t2.replace(tzinfo=v)
2609 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2610 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2611 self.assertEqual(t1, t2)
2612
2613 # But if they're not identical, it isn't ignored.
2614 t2 = t2.replace(tzinfo=Varies())
2615 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002616
Tim Peters621818b2002-12-29 23:44:49 +00002617# Pain to set up DST-aware tzinfo classes.
2618
2619def first_sunday_on_or_after(dt):
2620 days_to_go = 6 - dt.weekday()
2621 if days_to_go:
2622 dt += timedelta(days_to_go)
2623 return dt
2624
2625ZERO = timedelta(0)
2626HOUR = timedelta(hours=1)
2627DAY = timedelta(days=1)
2628# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2629DSTSTART = datetime(1, 4, 1, 2)
2630# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002631# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2632# being standard time on that day, there is no spelling in local time of
2633# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2634DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002635
2636class USTimeZone(tzinfo):
2637
2638 def __init__(self, hours, reprname, stdname, dstname):
2639 self.stdoffset = timedelta(hours=hours)
2640 self.reprname = reprname
2641 self.stdname = stdname
2642 self.dstname = dstname
2643
2644 def __repr__(self):
2645 return self.reprname
2646
2647 def tzname(self, dt):
2648 if self.dst(dt):
2649 return self.dstname
2650 else:
2651 return self.stdname
2652
2653 def utcoffset(self, dt):
2654 return self.stdoffset + self.dst(dt)
2655
2656 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002657 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002658 # An exception instead may be sensible here, in one or more of
2659 # the cases.
2660 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002661 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002662
2663 # Find first Sunday in April.
2664 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2665 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2666
2667 # Find last Sunday in October.
2668 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2669 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2670
Tim Peters621818b2002-12-29 23:44:49 +00002671 # Can't compare naive to aware objects, so strip the timezone from
2672 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002673 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002674 return HOUR
2675 else:
2676 return ZERO
2677
Tim Peters521fc152002-12-31 17:36:56 +00002678Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2679Central = USTimeZone(-6, "Central", "CST", "CDT")
2680Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2681Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002682utc_real = FixedOffset(0, "UTC", 0)
2683# For better test coverage, we want another flavor of UTC that's west of
2684# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002685utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002686
2687class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002688 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002689 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002690 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002691
Tim Peters0bf60bd2003-01-08 20:40:01 +00002692 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002693
Tim Peters521fc152002-12-31 17:36:56 +00002694 # Check a time that's inside DST.
2695 def checkinside(self, dt, tz, utc, dston, dstoff):
2696 self.assertEqual(dt.dst(), HOUR)
2697
2698 # Conversion to our own timezone is always an identity.
2699 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002700
2701 asutc = dt.astimezone(utc)
2702 there_and_back = asutc.astimezone(tz)
2703
2704 # Conversion to UTC and back isn't always an identity here,
2705 # because there are redundant spellings (in local time) of
2706 # UTC time when DST begins: the clock jumps from 1:59:59
2707 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2708 # make sense then. The classes above treat 2:MM:SS as
2709 # daylight time then (it's "after 2am"), really an alias
2710 # for 1:MM:SS standard time. The latter form is what
2711 # conversion back from UTC produces.
2712 if dt.date() == dston.date() and dt.hour == 2:
2713 # We're in the redundant hour, and coming back from
2714 # UTC gives the 1:MM:SS standard-time spelling.
2715 self.assertEqual(there_and_back + HOUR, dt)
2716 # Although during was considered to be in daylight
2717 # time, there_and_back is not.
2718 self.assertEqual(there_and_back.dst(), ZERO)
2719 # They're the same times in UTC.
2720 self.assertEqual(there_and_back.astimezone(utc),
2721 dt.astimezone(utc))
2722 else:
2723 # We're not in the redundant hour.
2724 self.assertEqual(dt, there_and_back)
2725
Tim Peters327098a2003-01-20 22:54:38 +00002726 # Because we have a redundant spelling when DST begins, there is
2727 # (unforunately) an hour when DST ends that can't be spelled at all in
2728 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2729 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2730 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2731 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2732 # expressed in local time. Nevertheless, we want conversion back
2733 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002734 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002735 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002736 if dt.date() == dstoff.date() and dt.hour == 0:
2737 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002738 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002739 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2740 nexthour_utc += HOUR
2741 nexthour_tz = nexthour_utc.astimezone(tz)
2742 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002743 else:
Tim Peters327098a2003-01-20 22:54:38 +00002744 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002745
2746 # Check a time that's outside DST.
2747 def checkoutside(self, dt, tz, utc):
2748 self.assertEqual(dt.dst(), ZERO)
2749
2750 # Conversion to our own timezone is always an identity.
2751 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002752
2753 # Converting to UTC and back is an identity too.
2754 asutc = dt.astimezone(utc)
2755 there_and_back = asutc.astimezone(tz)
2756 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002757
Tim Peters1024bf82002-12-30 17:09:40 +00002758 def convert_between_tz_and_utc(self, tz, utc):
2759 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002760 # Because 1:MM on the day DST ends is taken as being standard time,
2761 # there is no spelling in tz for the last hour of daylight time.
2762 # For purposes of the test, the last hour of DST is 0:MM, which is
2763 # taken as being daylight time (and 1:MM is taken as being standard
2764 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002765 dstoff = self.dstoff.replace(tzinfo=tz)
2766 for delta in (timedelta(weeks=13),
2767 DAY,
2768 HOUR,
2769 timedelta(minutes=1),
2770 timedelta(microseconds=1)):
2771
Tim Peters521fc152002-12-31 17:36:56 +00002772 self.checkinside(dston, tz, utc, dston, dstoff)
2773 for during in dston + delta, dstoff - delta:
2774 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002775
Tim Peters521fc152002-12-31 17:36:56 +00002776 self.checkoutside(dstoff, tz, utc)
2777 for outside in dston - delta, dstoff + delta:
2778 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002779
Tim Peters621818b2002-12-29 23:44:49 +00002780 def test_easy(self):
2781 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002782 self.convert_between_tz_and_utc(Eastern, utc_real)
2783 self.convert_between_tz_and_utc(Pacific, utc_real)
2784 self.convert_between_tz_and_utc(Eastern, utc_fake)
2785 self.convert_between_tz_and_utc(Pacific, utc_fake)
2786 # The next is really dancing near the edge. It works because
2787 # Pacific and Eastern are far enough apart that their "problem
2788 # hours" don't overlap.
2789 self.convert_between_tz_and_utc(Eastern, Pacific)
2790 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002791 # OTOH, these fail! Don't enable them. The difficulty is that
2792 # the edge case tests assume that every hour is representable in
2793 # the "utc" class. This is always true for a fixed-offset tzinfo
2794 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2795 # For these adjacent DST-aware time zones, the range of time offsets
2796 # tested ends up creating hours in the one that aren't representable
2797 # in the other. For the same reason, we would see failures in the
2798 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2799 # offset deltas in convert_between_tz_and_utc().
2800 #
2801 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2802 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002803
Tim Petersf3615152003-01-01 21:51:37 +00002804 def test_tricky(self):
2805 # 22:00 on day before daylight starts.
2806 fourback = self.dston - timedelta(hours=4)
2807 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002808 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002809 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2810 # 2", we should get the 3 spelling.
2811 # If we plug 22:00 the day before into Eastern, it "looks like std
2812 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2813 # to 22:00 lands on 2:00, which makes no sense in local time (the
2814 # local clock jumps from 1 to 3). The point here is to make sure we
2815 # get the 3 spelling.
2816 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002817 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002818 self.assertEqual(expected, got)
2819
2820 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2821 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002822 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002823 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2824 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2825 # spelling.
2826 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002827 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002828 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002829
Tim Petersadf64202003-01-04 06:03:15 +00002830 # Now on the day DST ends, we want "repeat an hour" behavior.
2831 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2832 # EST 23:MM 0:MM 1:MM 2:MM
2833 # EDT 0:MM 1:MM 2:MM 3:MM
2834 # wall 0:MM 1:MM 1:MM 2:MM against these
2835 for utc in utc_real, utc_fake:
2836 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002837 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002838 # Convert that to UTC.
2839 first_std_hour -= tz.utcoffset(None)
2840 # Adjust for possibly fake UTC.
2841 asutc = first_std_hour + utc.utcoffset(None)
2842 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2843 # tz=Eastern.
2844 asutcbase = asutc.replace(tzinfo=utc)
2845 for tzhour in (0, 1, 1, 2):
2846 expectedbase = self.dstoff.replace(hour=tzhour)
2847 for minute in 0, 30, 59:
2848 expected = expectedbase.replace(minute=minute)
2849 asutc = asutcbase.replace(minute=minute)
2850 astz = asutc.astimezone(tz)
2851 self.assertEqual(astz.replace(tzinfo=None), expected)
2852 asutcbase += HOUR
2853
2854
Tim Peters710fb152003-01-02 19:35:54 +00002855 def test_bogus_dst(self):
2856 class ok(tzinfo):
2857 def utcoffset(self, dt): return HOUR
2858 def dst(self, dt): return HOUR
2859
2860 now = self.theclass.now().replace(tzinfo=utc_real)
2861 # Doesn't blow up.
2862 now.astimezone(ok())
2863
2864 # Does blow up.
2865 class notok(ok):
2866 def dst(self, dt): return None
2867 self.assertRaises(ValueError, now.astimezone, notok())
2868
Tim Peters52dcce22003-01-23 16:36:11 +00002869 def test_fromutc(self):
2870 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2871 now = datetime.utcnow().replace(tzinfo=utc_real)
2872 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2873 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2874 enow = Eastern.fromutc(now) # doesn't blow up
2875 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2876 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2877 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2878
2879 # Always converts UTC to standard time.
2880 class FauxUSTimeZone(USTimeZone):
2881 def fromutc(self, dt):
2882 return dt + self.stdoffset
2883 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2884
2885 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2886 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2887 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2888
2889 # Check around DST start.
2890 start = self.dston.replace(hour=4, tzinfo=Eastern)
2891 fstart = start.replace(tzinfo=FEastern)
2892 for wall in 23, 0, 1, 3, 4, 5:
2893 expected = start.replace(hour=wall)
2894 if wall == 23:
2895 expected -= timedelta(days=1)
2896 got = Eastern.fromutc(start)
2897 self.assertEqual(expected, got)
2898
2899 expected = fstart + FEastern.stdoffset
2900 got = FEastern.fromutc(fstart)
2901 self.assertEqual(expected, got)
2902
2903 # Ensure astimezone() calls fromutc() too.
2904 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2905 self.assertEqual(expected, got)
2906
2907 start += HOUR
2908 fstart += HOUR
2909
2910 # Check around DST end.
2911 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2912 fstart = start.replace(tzinfo=FEastern)
2913 for wall in 0, 1, 1, 2, 3, 4:
2914 expected = start.replace(hour=wall)
2915 got = Eastern.fromutc(start)
2916 self.assertEqual(expected, got)
2917
2918 expected = fstart + FEastern.stdoffset
2919 got = FEastern.fromutc(fstart)
2920 self.assertEqual(expected, got)
2921
2922 # Ensure astimezone() calls fromutc() too.
2923 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2924 self.assertEqual(expected, got)
2925
2926 start += HOUR
2927 fstart += HOUR
2928
Tim Peters710fb152003-01-02 19:35:54 +00002929
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002930def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002931 allsuites = [unittest.makeSuite(klass, 'test')
2932 for klass in (TestModule,
2933 TestTZInfo,
2934 TestTimeDelta,
2935 TestDateOnly,
2936 TestDate,
2937 TestDateTime,
2938 TestTime,
2939 TestTimeTZ,
2940 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002941 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002942 )
2943 ]
2944 return unittest.TestSuite(allsuites)
2945
2946def test_main():
2947 import gc
2948 import sys
2949
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002950 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002951 lastrc = None
2952 while True:
2953 test_support.run_suite(thesuite)
2954 if 1: # change to 0, under a debug build, for some leak detection
2955 break
2956 gc.collect()
2957 if gc.garbage:
2958 raise SystemError("gc.garbage not empty after test run: %r" %
2959 gc.garbage)
2960 if hasattr(sys, 'gettotalrefcount'):
2961 thisrc = sys.gettotalrefcount()
2962 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2963 if lastrc:
2964 print >> sys.stderr, 'delta:', thisrc - lastrc
2965 else:
2966 print >> sys.stderr
2967 lastrc = thisrc
2968
2969if __name__ == "__main__":
2970 test_main()