blob: 6acf0731bf8e02b2ba566a66454762063ae7b462 [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
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters0bf60bd2003-01-08 20:40:01 +000025# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are
26# XXX instances of new-style classes (like date and time) that both
27# XXX define __cmp__, and x is compared to y, and one of the __cmp__
28# XXX implementations raises an exception, the exception can get dropped
29# XXX on the floor when it occurs, and pop up again at some "random" time
30# XXX later (it depends on when the next opcode gets executed that
31# XXX bothers to check). There isn't a workaround for this, so instead
32# XXX we disable the parts of the tests that trigger it unless
33# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid
34# XXX provoking it here.
35# XXX Guido checked into a fix that will go into 2.2.3. The bug was
36# XXX already fixed in 2.3 CVS via a different means.
37CMP_BUG_FIXED = sys.version_info >= (2, 2, 3)
38
Tim Peters2a799bf2002-12-16 20:18:38 +000039
40#############################################################################
41# module tests
42
43class TestModule(unittest.TestCase):
44
45 def test_constants(self):
46 import datetime
47 self.assertEqual(datetime.MINYEAR, 1)
48 self.assertEqual(datetime.MAXYEAR, 9999)
49
50#############################################################################
51# tzinfo tests
52
53class FixedOffset(tzinfo):
54 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000055 if isinstance(offset, int):
56 offset = timedelta(minutes=offset)
57 if isinstance(dstoffset, int):
58 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000059 self.__offset = offset
60 self.__name = name
61 self.__dstoffset = dstoffset
62 def __repr__(self):
63 return self.__name.lower()
64 def utcoffset(self, dt):
65 return self.__offset
66 def tzname(self, dt):
67 return self.__name
68 def dst(self, dt):
69 return self.__dstoffset
70
Tim Petersfb8472c2002-12-21 05:04:42 +000071class PicklableFixedOffset(FixedOffset):
72 def __init__(self, offset=None, name=None, dstoffset=None):
73 FixedOffset.__init__(self, offset, name, dstoffset)
74
Tim Peters2a799bf2002-12-16 20:18:38 +000075class TestTZInfo(unittest.TestCase):
76
77 def test_non_abstractness(self):
78 # In order to allow subclasses to get pickled, the C implementation
79 # wasn't able to get away with having __init__ raise
80 # NotImplementedError.
81 useless = tzinfo()
82 dt = datetime.max
83 self.assertRaises(NotImplementedError, useless.tzname, dt)
84 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
85 self.assertRaises(NotImplementedError, useless.dst, dt)
86
87 def test_subclass_must_override(self):
88 class NotEnough(tzinfo):
89 def __init__(self, offset, name):
90 self.__offset = offset
91 self.__name = name
92 self.failUnless(issubclass(NotEnough, tzinfo))
93 ne = NotEnough(3, "NotByALongShot")
94 self.failUnless(isinstance(ne, tzinfo))
95
96 dt = datetime.now()
97 self.assertRaises(NotImplementedError, ne.tzname, dt)
98 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
99 self.assertRaises(NotImplementedError, ne.dst, dt)
100
101 def test_normal(self):
102 fo = FixedOffset(3, "Three")
103 self.failUnless(isinstance(fo, tzinfo))
104 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +0000105 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +0000106 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000107 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000108
109 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000110 # There's no point to pickling tzinfo objects on their own (they
111 # carry no data), but they need to be picklable anyway else
112 # concrete subclasses can't be pickled.
113 orig = tzinfo.__new__(tzinfo)
114 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000115 for pickler, unpickler, proto in pickle_choices:
116 green = pickler.dumps(orig, proto)
117 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000118 self.failUnless(type(derived) is tzinfo)
119
120 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000121 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000122 offset = timedelta(minutes=-300)
123 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000124 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000125 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000126 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000127 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000128 for pickler, unpickler, proto in pickle_choices:
129 green = pickler.dumps(orig, proto)
130 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000131 self.failUnless(isinstance(derived, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000132 self.failUnless(type(derived) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000133 self.assertEqual(derived.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000134 self.assertEqual(derived.tzname(None), 'cookie')
135
136#############################################################################
137# timedelta tests
138
139class TestTimeDelta(unittest.TestCase):
140
141 def test_constructor(self):
142 eq = self.assertEqual
143 td = timedelta
144
145 # Check keyword args to constructor
146 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
147 milliseconds=0, microseconds=0))
148 eq(td(1), td(days=1))
149 eq(td(0, 1), td(seconds=1))
150 eq(td(0, 0, 1), td(microseconds=1))
151 eq(td(weeks=1), td(days=7))
152 eq(td(days=1), td(hours=24))
153 eq(td(hours=1), td(minutes=60))
154 eq(td(minutes=1), td(seconds=60))
155 eq(td(seconds=1), td(milliseconds=1000))
156 eq(td(milliseconds=1), td(microseconds=1000))
157
158 # Check float args to constructor
159 eq(td(weeks=1.0/7), td(days=1))
160 eq(td(days=1.0/24), td(hours=1))
161 eq(td(hours=1.0/60), td(minutes=1))
162 eq(td(minutes=1.0/60), td(seconds=1))
163 eq(td(seconds=0.001), td(milliseconds=1))
164 eq(td(milliseconds=0.001), td(microseconds=1))
165
166 def test_computations(self):
167 eq = self.assertEqual
168 td = timedelta
169
170 a = td(7) # One week
171 b = td(0, 60) # One minute
172 c = td(0, 0, 1000) # One millisecond
173 eq(a+b+c, td(7, 60, 1000))
174 eq(a-b, td(6, 24*3600 - 60))
175 eq(-a, td(-7))
176 eq(+a, td(7))
177 eq(-b, td(-1, 24*3600 - 60))
178 eq(-c, td(-1, 24*3600 - 1, 999000))
179 eq(abs(a), a)
180 eq(abs(-a), a)
181 eq(td(6, 24*3600), a)
182 eq(td(0, 0, 60*1000000), b)
183 eq(a*10, td(70))
184 eq(a*10, 10*a)
185 eq(a*10L, 10*a)
186 eq(b*10, td(0, 600))
187 eq(10*b, td(0, 600))
188 eq(b*10L, td(0, 600))
189 eq(c*10, td(0, 0, 10000))
190 eq(10*c, td(0, 0, 10000))
191 eq(c*10L, td(0, 0, 10000))
192 eq(a*-1, -a)
193 eq(b*-2, -b-b)
194 eq(c*-2, -c+-c)
195 eq(b*(60*24), (b*60)*24)
196 eq(b*(60*24), (60*b)*24)
197 eq(c*1000, td(0, 1))
198 eq(1000*c, td(0, 1))
199 eq(a//7, td(1))
200 eq(b//10, td(0, 6))
201 eq(c//1000, td(0, 0, 1))
202 eq(a//10, td(0, 7*24*360))
203 eq(a//3600000, td(0, 0, 7*24*1000))
204
205 def test_disallowed_computations(self):
206 a = timedelta(42)
207
208 # Add/sub ints, longs, floats should be illegal
209 for i in 1, 1L, 1.0:
210 self.assertRaises(TypeError, lambda: a+i)
211 self.assertRaises(TypeError, lambda: a-i)
212 self.assertRaises(TypeError, lambda: i+a)
213 self.assertRaises(TypeError, lambda: i-a)
214
215 # Mul/div by float isn't supported.
216 x = 2.3
217 self.assertRaises(TypeError, lambda: a*x)
218 self.assertRaises(TypeError, lambda: x*a)
219 self.assertRaises(TypeError, lambda: a/x)
220 self.assertRaises(TypeError, lambda: x/a)
221 self.assertRaises(TypeError, lambda: a // x)
222 self.assertRaises(TypeError, lambda: x // a)
223
224 # Divison of int by timedelta doesn't make sense.
225 # Division by zero doesn't make sense.
226 for zero in 0, 0L:
227 self.assertRaises(TypeError, lambda: zero // a)
228 self.assertRaises(ZeroDivisionError, lambda: a // zero)
229
230 def test_basic_attributes(self):
231 days, seconds, us = 1, 7, 31
232 td = timedelta(days, seconds, us)
233 self.assertEqual(td.days, days)
234 self.assertEqual(td.seconds, seconds)
235 self.assertEqual(td.microseconds, us)
236
237 def test_carries(self):
238 t1 = timedelta(days=100,
239 weeks=-7,
240 hours=-24*(100-49),
241 minutes=-3,
242 seconds=12,
243 microseconds=(3*60 - 12) * 1e6 + 1)
244 t2 = timedelta(microseconds=1)
245 self.assertEqual(t1, t2)
246
247 def test_hash_equality(self):
248 t1 = timedelta(days=100,
249 weeks=-7,
250 hours=-24*(100-49),
251 minutes=-3,
252 seconds=12,
253 microseconds=(3*60 - 12) * 1000000)
254 t2 = timedelta()
255 self.assertEqual(hash(t1), hash(t2))
256
257 t1 += timedelta(weeks=7)
258 t2 += timedelta(days=7*7)
259 self.assertEqual(t1, t2)
260 self.assertEqual(hash(t1), hash(t2))
261
262 d = {t1: 1}
263 d[t2] = 2
264 self.assertEqual(len(d), 1)
265 self.assertEqual(d[t1], 2)
266
267 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000268 args = 12, 34, 56
269 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000270 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000271 green = pickler.dumps(orig, proto)
272 derived = unpickler.loads(green)
273 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000274
275 def test_compare(self):
276 t1 = timedelta(2, 3, 4)
277 t2 = timedelta(2, 3, 4)
278 self.failUnless(t1 == t2)
279 self.failUnless(t1 <= t2)
280 self.failUnless(t1 >= t2)
281 self.failUnless(not t1 != t2)
282 self.failUnless(not t1 < t2)
283 self.failUnless(not t1 > t2)
284 self.assertEqual(cmp(t1, t2), 0)
285 self.assertEqual(cmp(t2, t1), 0)
286
287 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
288 t2 = timedelta(*args) # this is larger than t1
289 self.failUnless(t1 < t2)
290 self.failUnless(t2 > t1)
291 self.failUnless(t1 <= t2)
292 self.failUnless(t2 >= t1)
293 self.failUnless(t1 != t2)
294 self.failUnless(t2 != t1)
295 self.failUnless(not t1 == t2)
296 self.failUnless(not t2 == t1)
297 self.failUnless(not t1 > t2)
298 self.failUnless(not t2 < t1)
299 self.failUnless(not t1 >= t2)
300 self.failUnless(not t2 <= t1)
301 self.assertEqual(cmp(t1, t2), -1)
302 self.assertEqual(cmp(t2, t1), 1)
303
304 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
305 self.assertRaises(TypeError, lambda: t1 == badarg)
306 self.assertRaises(TypeError, lambda: t1 != badarg)
307 self.assertRaises(TypeError, lambda: t1 <= badarg)
308 self.assertRaises(TypeError, lambda: t1 < badarg)
309 self.assertRaises(TypeError, lambda: t1 > badarg)
310 self.assertRaises(TypeError, lambda: t1 >= badarg)
311 self.assertRaises(TypeError, lambda: badarg == t1)
312 self.assertRaises(TypeError, lambda: badarg != t1)
313 self.assertRaises(TypeError, lambda: badarg <= t1)
314 self.assertRaises(TypeError, lambda: badarg < t1)
315 self.assertRaises(TypeError, lambda: badarg > t1)
316 self.assertRaises(TypeError, lambda: badarg >= t1)
317
318 def test_str(self):
319 td = timedelta
320 eq = self.assertEqual
321
322 eq(str(td(1)), "1 day, 0:00:00")
323 eq(str(td(-1)), "-1 day, 0:00:00")
324 eq(str(td(2)), "2 days, 0:00:00")
325 eq(str(td(-2)), "-2 days, 0:00:00")
326
327 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
328 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
329 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
330 "-210 days, 23:12:34")
331
332 eq(str(td(milliseconds=1)), "0:00:00.001000")
333 eq(str(td(microseconds=3)), "0:00:00.000003")
334
335 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
336 microseconds=999999)),
337 "999999999 days, 23:59:59.999999")
338
339 def test_roundtrip(self):
340 for td in (timedelta(days=999999999, hours=23, minutes=59,
341 seconds=59, microseconds=999999),
342 timedelta(days=-999999999),
343 timedelta(days=1, seconds=2, microseconds=3)):
344
345 # Verify td -> string -> td identity.
346 s = repr(td)
347 self.failUnless(s.startswith('datetime.'))
348 s = s[9:]
349 td2 = eval(s)
350 self.assertEqual(td, td2)
351
352 # Verify identity via reconstructing from pieces.
353 td2 = timedelta(td.days, td.seconds, td.microseconds)
354 self.assertEqual(td, td2)
355
356 def test_resolution_info(self):
357 self.assert_(isinstance(timedelta.min, timedelta))
358 self.assert_(isinstance(timedelta.max, timedelta))
359 self.assert_(isinstance(timedelta.resolution, timedelta))
360 self.assert_(timedelta.max > timedelta.min)
361 self.assertEqual(timedelta.min, timedelta(-999999999))
362 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
363 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
364
365 def test_overflow(self):
366 tiny = timedelta.resolution
367
368 td = timedelta.min + tiny
369 td -= tiny # no problem
370 self.assertRaises(OverflowError, td.__sub__, tiny)
371 self.assertRaises(OverflowError, td.__add__, -tiny)
372
373 td = timedelta.max - tiny
374 td += tiny # no problem
375 self.assertRaises(OverflowError, td.__add__, tiny)
376 self.assertRaises(OverflowError, td.__sub__, -tiny)
377
378 self.assertRaises(OverflowError, lambda: -timedelta.max)
379
380 def test_microsecond_rounding(self):
381 td = timedelta
382 eq = self.assertEqual
383
384 # Single-field rounding.
385 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
386 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
387 eq(td(milliseconds=0.6/1000), td(microseconds=1))
388 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
389
390 # Rounding due to contributions from more than one field.
391 us_per_hour = 3600e6
392 us_per_day = us_per_hour * 24
393 eq(td(days=.4/us_per_day), td(0))
394 eq(td(hours=.2/us_per_hour), td(0))
395 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
396
397 eq(td(days=-.4/us_per_day), td(0))
398 eq(td(hours=-.2/us_per_hour), td(0))
399 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
400
401 def test_massive_normalization(self):
402 td = timedelta(microseconds=-1)
403 self.assertEqual((td.days, td.seconds, td.microseconds),
404 (-1, 24*3600-1, 999999))
405
406 def test_bool(self):
407 self.failUnless(timedelta(1))
408 self.failUnless(timedelta(0, 1))
409 self.failUnless(timedelta(0, 0, 1))
410 self.failUnless(timedelta(microseconds=1))
411 self.failUnless(not timedelta(0))
412
413#############################################################################
414# date tests
415
416class TestDateOnly(unittest.TestCase):
417 # Tests here won't pass if also run on datetime objects, so don't
418 # subclass this to test datetimes too.
419
420 def test_delta_non_days_ignored(self):
421 dt = date(2000, 1, 2)
422 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
423 microseconds=5)
424 days = timedelta(delta.days)
425 self.assertEqual(days, timedelta(1))
426
427 dt2 = dt + delta
428 self.assertEqual(dt2, dt + days)
429
430 dt2 = delta + dt
431 self.assertEqual(dt2, dt + days)
432
433 dt2 = dt - delta
434 self.assertEqual(dt2, dt - days)
435
436 delta = -delta
437 days = timedelta(delta.days)
438 self.assertEqual(days, timedelta(-2))
439
440 dt2 = dt + delta
441 self.assertEqual(dt2, dt + days)
442
443 dt2 = delta + dt
444 self.assertEqual(dt2, dt + days)
445
446 dt2 = dt - delta
447 self.assertEqual(dt2, dt - days)
448
449class TestDate(unittest.TestCase):
450 # Tests here should pass for both dates and datetimes, except for a
451 # few tests that TestDateTime overrides.
452
453 theclass = date
454
455 def test_basic_attributes(self):
456 dt = self.theclass(2002, 3, 1)
457 self.assertEqual(dt.year, 2002)
458 self.assertEqual(dt.month, 3)
459 self.assertEqual(dt.day, 1)
460
461 def test_roundtrip(self):
462 for dt in (self.theclass(1, 2, 3),
463 self.theclass.today()):
464 # Verify dt -> string -> date identity.
465 s = repr(dt)
466 self.failUnless(s.startswith('datetime.'))
467 s = s[9:]
468 dt2 = eval(s)
469 self.assertEqual(dt, dt2)
470
471 # Verify identity via reconstructing from pieces.
472 dt2 = self.theclass(dt.year, dt.month, dt.day)
473 self.assertEqual(dt, dt2)
474
475 def test_ordinal_conversions(self):
476 # Check some fixed values.
477 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
478 (1, 12, 31, 365),
479 (2, 1, 1, 366),
480 # first example from "Calendrical Calculations"
481 (1945, 11, 12, 710347)]:
482 d = self.theclass(y, m, d)
483 self.assertEqual(n, d.toordinal())
484 fromord = self.theclass.fromordinal(n)
485 self.assertEqual(d, fromord)
486 if hasattr(fromord, "hour"):
487 # if we're checking something fancier than a date, verify
488 # the extra fields have been zeroed out
489 self.assertEqual(fromord.hour, 0)
490 self.assertEqual(fromord.minute, 0)
491 self.assertEqual(fromord.second, 0)
492 self.assertEqual(fromord.microsecond, 0)
493
Tim Peters0bf60bd2003-01-08 20:40:01 +0000494 # Check first and last days of year spottily across the whole
495 # range of years supported.
496 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000497 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
498 d = self.theclass(year, 1, 1)
499 n = d.toordinal()
500 d2 = self.theclass.fromordinal(n)
501 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000502 # Verify that moving back a day gets to the end of year-1.
503 if year > 1:
504 d = self.theclass.fromordinal(n-1)
505 d2 = self.theclass(year-1, 12, 31)
506 self.assertEqual(d, d2)
507 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000508
509 # Test every day in a leap-year and a non-leap year.
510 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
511 for year, isleap in (2000, True), (2002, False):
512 n = self.theclass(year, 1, 1).toordinal()
513 for month, maxday in zip(range(1, 13), dim):
514 if month == 2 and isleap:
515 maxday += 1
516 for day in range(1, maxday+1):
517 d = self.theclass(year, month, day)
518 self.assertEqual(d.toordinal(), n)
519 self.assertEqual(d, self.theclass.fromordinal(n))
520 n += 1
521
522 def test_extreme_ordinals(self):
523 a = self.theclass.min
524 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
525 aord = a.toordinal()
526 b = a.fromordinal(aord)
527 self.assertEqual(a, b)
528
529 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
530
531 b = a + timedelta(days=1)
532 self.assertEqual(b.toordinal(), aord + 1)
533 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
534
535 a = self.theclass.max
536 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
537 aord = a.toordinal()
538 b = a.fromordinal(aord)
539 self.assertEqual(a, b)
540
541 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
542
543 b = a - timedelta(days=1)
544 self.assertEqual(b.toordinal(), aord - 1)
545 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
546
547 def test_bad_constructor_arguments(self):
548 # bad years
549 self.theclass(MINYEAR, 1, 1) # no exception
550 self.theclass(MAXYEAR, 1, 1) # no exception
551 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
552 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
553 # bad months
554 self.theclass(2000, 1, 1) # no exception
555 self.theclass(2000, 12, 1) # no exception
556 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
557 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
558 # bad days
559 self.theclass(2000, 2, 29) # no exception
560 self.theclass(2004, 2, 29) # no exception
561 self.theclass(2400, 2, 29) # no exception
562 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
563 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
564 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
565 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
566 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
567 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
568
569 def test_hash_equality(self):
570 d = self.theclass(2000, 12, 31)
571 # same thing
572 e = self.theclass(2000, 12, 31)
573 self.assertEqual(d, e)
574 self.assertEqual(hash(d), hash(e))
575
576 dic = {d: 1}
577 dic[e] = 2
578 self.assertEqual(len(dic), 1)
579 self.assertEqual(dic[d], 2)
580 self.assertEqual(dic[e], 2)
581
582 d = self.theclass(2001, 1, 1)
583 # same thing
584 e = self.theclass(2001, 1, 1)
585 self.assertEqual(d, e)
586 self.assertEqual(hash(d), hash(e))
587
588 dic = {d: 1}
589 dic[e] = 2
590 self.assertEqual(len(dic), 1)
591 self.assertEqual(dic[d], 2)
592 self.assertEqual(dic[e], 2)
593
594 def test_computations(self):
595 a = self.theclass(2002, 1, 31)
596 b = self.theclass(1956, 1, 31)
597
598 diff = a-b
599 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
600 self.assertEqual(diff.seconds, 0)
601 self.assertEqual(diff.microseconds, 0)
602
603 day = timedelta(1)
604 week = timedelta(7)
605 a = self.theclass(2002, 3, 2)
606 self.assertEqual(a + day, self.theclass(2002, 3, 3))
607 self.assertEqual(day + a, self.theclass(2002, 3, 3))
608 self.assertEqual(a - day, self.theclass(2002, 3, 1))
609 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
610 self.assertEqual(a + week, self.theclass(2002, 3, 9))
611 self.assertEqual(a - week, self.theclass(2002, 2, 23))
612 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
613 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
614 self.assertEqual((a + week) - a, week)
615 self.assertEqual((a + day) - a, day)
616 self.assertEqual((a - week) - a, -week)
617 self.assertEqual((a - day) - a, -day)
618 self.assertEqual(a - (a + week), -week)
619 self.assertEqual(a - (a + day), -day)
620 self.assertEqual(a - (a - week), week)
621 self.assertEqual(a - (a - day), day)
622
623 # Add/sub ints, longs, floats should be illegal
624 for i in 1, 1L, 1.0:
625 self.assertRaises(TypeError, lambda: a+i)
626 self.assertRaises(TypeError, lambda: a-i)
627 self.assertRaises(TypeError, lambda: i+a)
628 self.assertRaises(TypeError, lambda: i-a)
629
630 # delta - date is senseless.
631 self.assertRaises(TypeError, lambda: day - a)
632 # mixing date and (delta or date) via * or // is senseless
633 self.assertRaises(TypeError, lambda: day * a)
634 self.assertRaises(TypeError, lambda: a * day)
635 self.assertRaises(TypeError, lambda: day // a)
636 self.assertRaises(TypeError, lambda: a // day)
637 self.assertRaises(TypeError, lambda: a * a)
638 self.assertRaises(TypeError, lambda: a // a)
639 # date + date is senseless
640 self.assertRaises(TypeError, lambda: a + a)
641
642 def test_overflow(self):
643 tiny = self.theclass.resolution
644
645 dt = self.theclass.min + tiny
646 dt -= tiny # no problem
647 self.assertRaises(OverflowError, dt.__sub__, tiny)
648 self.assertRaises(OverflowError, dt.__add__, -tiny)
649
650 dt = self.theclass.max - tiny
651 dt += tiny # no problem
652 self.assertRaises(OverflowError, dt.__add__, tiny)
653 self.assertRaises(OverflowError, dt.__sub__, -tiny)
654
655 def test_fromtimestamp(self):
656 import time
657
658 # Try an arbitrary fixed value.
659 year, month, day = 1999, 9, 19
660 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
661 d = self.theclass.fromtimestamp(ts)
662 self.assertEqual(d.year, year)
663 self.assertEqual(d.month, month)
664 self.assertEqual(d.day, day)
665
666 def test_today(self):
667 import time
668
669 # We claim that today() is like fromtimestamp(time.time()), so
670 # prove it.
671 for dummy in range(3):
672 today = self.theclass.today()
673 ts = time.time()
674 todayagain = self.theclass.fromtimestamp(ts)
675 if today == todayagain:
676 break
677 # There are several legit reasons that could fail:
678 # 1. It recently became midnight, between the today() and the
679 # time() calls.
680 # 2. The platform time() has such fine resolution that we'll
681 # never get the same value twice.
682 # 3. The platform time() has poor resolution, and we just
683 # happened to call today() right before a resolution quantum
684 # boundary.
685 # 4. The system clock got fiddled between calls.
686 # In any case, wait a little while and try again.
687 time.sleep(0.1)
688
689 # It worked or it didn't. If it didn't, assume it's reason #2, and
690 # let the test pass if they're within half a second of each other.
691 self.failUnless(today == todayagain or
692 abs(todayagain - today) < timedelta(seconds=0.5))
693
694 def test_weekday(self):
695 for i in range(7):
696 # March 4, 2002 is a Monday
697 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
698 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
699 # January 2, 1956 is a Monday
700 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
701 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
702
703 def test_isocalendar(self):
704 # Check examples from
705 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
706 for i in range(7):
707 d = self.theclass(2003, 12, 22+i)
708 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
709 d = self.theclass(2003, 12, 29) + timedelta(i)
710 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
711 d = self.theclass(2004, 1, 5+i)
712 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
713 d = self.theclass(2009, 12, 21+i)
714 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
715 d = self.theclass(2009, 12, 28) + timedelta(i)
716 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
717 d = self.theclass(2010, 1, 4+i)
718 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
719
720 def test_iso_long_years(self):
721 # Calculate long ISO years and compare to table from
722 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
723 ISO_LONG_YEARS_TABLE = """
724 4 32 60 88
725 9 37 65 93
726 15 43 71 99
727 20 48 76
728 26 54 82
729
730 105 133 161 189
731 111 139 167 195
732 116 144 172
733 122 150 178
734 128 156 184
735
736 201 229 257 285
737 207 235 263 291
738 212 240 268 296
739 218 246 274
740 224 252 280
741
742 303 331 359 387
743 308 336 364 392
744 314 342 370 398
745 320 348 376
746 325 353 381
747 """
748 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
749 iso_long_years.sort()
750 L = []
751 for i in range(400):
752 d = self.theclass(2000+i, 12, 31)
753 d1 = self.theclass(1600+i, 12, 31)
754 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
755 if d.isocalendar()[1] == 53:
756 L.append(i)
757 self.assertEqual(L, iso_long_years)
758
759 def test_isoformat(self):
760 t = self.theclass(2, 3, 2)
761 self.assertEqual(t.isoformat(), "0002-03-02")
762
763 def test_ctime(self):
764 t = self.theclass(2002, 3, 2)
765 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
766
767 def test_strftime(self):
768 t = self.theclass(2005, 3, 2)
769 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
770
771 self.assertRaises(TypeError, t.strftime) # needs an arg
772 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
773 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
774
775 # A naive object replaces %z and %Z w/ empty strings.
776 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
777
778 def test_resolution_info(self):
779 self.assert_(isinstance(self.theclass.min, self.theclass))
780 self.assert_(isinstance(self.theclass.max, self.theclass))
781 self.assert_(isinstance(self.theclass.resolution, timedelta))
782 self.assert_(self.theclass.max > self.theclass.min)
783
784 def test_extreme_timedelta(self):
785 big = self.theclass.max - self.theclass.min
786 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
787 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
788 # n == 315537897599999999 ~= 2**58.13
789 justasbig = timedelta(0, 0, n)
790 self.assertEqual(big, justasbig)
791 self.assertEqual(self.theclass.min + big, self.theclass.max)
792 self.assertEqual(self.theclass.max - big, self.theclass.min)
793
794 def test_timetuple(self):
795 for i in range(7):
796 # January 2, 1956 is a Monday (0)
797 d = self.theclass(1956, 1, 2+i)
798 t = d.timetuple()
799 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
800 # February 1, 1956 is a Wednesday (2)
801 d = self.theclass(1956, 2, 1+i)
802 t = d.timetuple()
803 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
804 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
805 # of the year.
806 d = self.theclass(1956, 3, 1+i)
807 t = d.timetuple()
808 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
809 self.assertEqual(t.tm_year, 1956)
810 self.assertEqual(t.tm_mon, 3)
811 self.assertEqual(t.tm_mday, 1+i)
812 self.assertEqual(t.tm_hour, 0)
813 self.assertEqual(t.tm_min, 0)
814 self.assertEqual(t.tm_sec, 0)
815 self.assertEqual(t.tm_wday, (3+i)%7)
816 self.assertEqual(t.tm_yday, 61+i)
817 self.assertEqual(t.tm_isdst, -1)
818
819 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000820 args = 6, 7, 23
821 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000822 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000823 green = pickler.dumps(orig, proto)
824 derived = unpickler.loads(green)
825 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000826
827 def test_compare(self):
828 t1 = self.theclass(2, 3, 4)
829 t2 = self.theclass(2, 3, 4)
830 self.failUnless(t1 == t2)
831 self.failUnless(t1 <= t2)
832 self.failUnless(t1 >= t2)
833 self.failUnless(not t1 != t2)
834 self.failUnless(not t1 < t2)
835 self.failUnless(not t1 > t2)
836 self.assertEqual(cmp(t1, t2), 0)
837 self.assertEqual(cmp(t2, t1), 0)
838
839 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
840 t2 = self.theclass(*args) # this is larger than t1
841 self.failUnless(t1 < t2)
842 self.failUnless(t2 > t1)
843 self.failUnless(t1 <= t2)
844 self.failUnless(t2 >= t1)
845 self.failUnless(t1 != t2)
846 self.failUnless(t2 != t1)
847 self.failUnless(not t1 == t2)
848 self.failUnless(not t2 == t1)
849 self.failUnless(not t1 > t2)
850 self.failUnless(not t2 < t1)
851 self.failUnless(not t1 >= t2)
852 self.failUnless(not t2 <= t1)
853 self.assertEqual(cmp(t1, t2), -1)
854 self.assertEqual(cmp(t2, t1), 1)
855
856 for badarg in 10, 10L, 34.5, "abc", {}, [], ():
857 self.assertRaises(TypeError, lambda: t1 == badarg)
858 self.assertRaises(TypeError, lambda: t1 != badarg)
859 self.assertRaises(TypeError, lambda: t1 <= badarg)
860 self.assertRaises(TypeError, lambda: t1 < badarg)
861 self.assertRaises(TypeError, lambda: t1 > badarg)
862 self.assertRaises(TypeError, lambda: t1 >= badarg)
863 self.assertRaises(TypeError, lambda: badarg == t1)
864 self.assertRaises(TypeError, lambda: badarg != t1)
865 self.assertRaises(TypeError, lambda: badarg <= t1)
866 self.assertRaises(TypeError, lambda: badarg < t1)
867 self.assertRaises(TypeError, lambda: badarg > t1)
868 self.assertRaises(TypeError, lambda: badarg >= t1)
869
Tim Peters8d81a012003-01-24 22:36:34 +0000870 def test_mixed_compare(self):
871 our = self.theclass(2000, 4, 5)
872 self.assertRaises(TypeError, cmp, our, 1)
873 self.assertRaises(TypeError, cmp, 1, our)
874
875 class AnotherDateTimeClass(object):
876 def __cmp__(self, other):
877 # Return "equal" so calling this can't be confused with
878 # compare-by-address (which never says "equal" for distinct
879 # objects).
880 return 0
881
882 # This still errors, because date and datetime comparison raise
883 # TypeError instead of NotImplemented when they don't know what to
884 # do, in order to stop comparison from falling back to the default
885 # compare-by-address.
886 their = AnotherDateTimeClass()
887 self.assertRaises(TypeError, cmp, our, their)
888 # Oops: The next stab raises TypeError in the C implementation,
889 # but not in the Python implementation of datetime. The difference
890 # is due to that the Python implementation defines __cmp__ but
891 # the C implementation defines tp_richcompare. This is more pain
892 # to fix than it's worth, so commenting out the test.
893 # self.assertEqual(cmp(their, our), 0)
894
895 # But date and datetime comparison return NotImplemented instead if the
896 # other object has a timetuple attr. This gives the other object a
897 # chance to do the comparison.
898 class Comparable(AnotherDateTimeClass):
899 def timetuple(self):
900 return ()
901
902 their = Comparable()
903 self.assertEqual(cmp(our, their), 0)
904 self.assertEqual(cmp(their, our), 0)
905 self.failUnless(our == their)
906 self.failUnless(their == our)
907
Tim Peters2a799bf2002-12-16 20:18:38 +0000908 def test_bool(self):
909 # All dates are considered true.
910 self.failUnless(self.theclass.min)
911 self.failUnless(self.theclass.max)
912
Tim Petersd6844152002-12-22 20:58:42 +0000913 def test_srftime_out_of_range(self):
914 # For nasty technical reasons, we can't handle years before 1900.
915 cls = self.theclass
916 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
917 for y in 1, 49, 51, 99, 100, 1000, 1899:
918 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000919
920 def test_replace(self):
921 cls = self.theclass
922 args = [1, 2, 3]
923 base = cls(*args)
924 self.assertEqual(base, base.replace())
925
926 i = 0
927 for name, newval in (("year", 2),
928 ("month", 3),
929 ("day", 4)):
930 newargs = args[:]
931 newargs[i] = newval
932 expected = cls(*newargs)
933 got = base.replace(**{name: newval})
934 self.assertEqual(expected, got)
935 i += 1
936
937 # Out of bounds.
938 base = cls(2000, 2, 29)
939 self.assertRaises(ValueError, base.replace, year=2001)
940
Tim Peters2a799bf2002-12-16 20:18:38 +0000941#############################################################################
942# datetime tests
943
944class TestDateTime(TestDate):
945
946 theclass = datetime
947
948 def test_basic_attributes(self):
949 dt = self.theclass(2002, 3, 1, 12, 0)
950 self.assertEqual(dt.year, 2002)
951 self.assertEqual(dt.month, 3)
952 self.assertEqual(dt.day, 1)
953 self.assertEqual(dt.hour, 12)
954 self.assertEqual(dt.minute, 0)
955 self.assertEqual(dt.second, 0)
956 self.assertEqual(dt.microsecond, 0)
957
958 def test_basic_attributes_nonzero(self):
959 # Make sure all attributes are non-zero so bugs in
960 # bit-shifting access show up.
961 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
962 self.assertEqual(dt.year, 2002)
963 self.assertEqual(dt.month, 3)
964 self.assertEqual(dt.day, 1)
965 self.assertEqual(dt.hour, 12)
966 self.assertEqual(dt.minute, 59)
967 self.assertEqual(dt.second, 59)
968 self.assertEqual(dt.microsecond, 8000)
969
970 def test_roundtrip(self):
971 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
972 self.theclass.now()):
973 # Verify dt -> string -> datetime identity.
974 s = repr(dt)
975 self.failUnless(s.startswith('datetime.'))
976 s = s[9:]
977 dt2 = eval(s)
978 self.assertEqual(dt, dt2)
979
980 # Verify identity via reconstructing from pieces.
981 dt2 = self.theclass(dt.year, dt.month, dt.day,
982 dt.hour, dt.minute, dt.second,
983 dt.microsecond)
984 self.assertEqual(dt, dt2)
985
986 def test_isoformat(self):
987 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
988 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
989 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
990 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
991 # str is ISO format with the separator forced to a blank.
992 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
993
994 t = self.theclass(2, 3, 2)
995 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
996 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
997 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
998 # str is ISO format with the separator forced to a blank.
999 self.assertEqual(str(t), "0002-03-02 00:00:00")
1000
1001 def test_more_ctime(self):
1002 # Test fields that TestDate doesn't touch.
1003 import time
1004
1005 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1006 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1007 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1008 # out. The difference is that t.ctime() produces " 2" for the day,
1009 # but platform ctime() produces "02" for the day. According to
1010 # C99, t.ctime() is correct here.
1011 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1012
1013 # So test a case where that difference doesn't matter.
1014 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1015 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1016
1017 def test_tz_independent_comparing(self):
1018 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1019 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1020 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1021 self.assertEqual(dt1, dt3)
1022 self.assert_(dt2 > dt3)
1023
1024 # Make sure comparison doesn't forget microseconds, and isn't done
1025 # via comparing a float timestamp (an IEEE double doesn't have enough
1026 # precision to span microsecond resolution across years 1 thru 9999,
1027 # so comparing via timestamp necessarily calls some distinct values
1028 # equal).
1029 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1030 us = timedelta(microseconds=1)
1031 dt2 = dt1 + us
1032 self.assertEqual(dt2 - dt1, us)
1033 self.assert_(dt1 < dt2)
1034
1035 def test_bad_constructor_arguments(self):
1036 # bad years
1037 self.theclass(MINYEAR, 1, 1) # no exception
1038 self.theclass(MAXYEAR, 1, 1) # no exception
1039 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1040 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1041 # bad months
1042 self.theclass(2000, 1, 1) # no exception
1043 self.theclass(2000, 12, 1) # no exception
1044 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1045 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1046 # bad days
1047 self.theclass(2000, 2, 29) # no exception
1048 self.theclass(2004, 2, 29) # no exception
1049 self.theclass(2400, 2, 29) # no exception
1050 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1051 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1052 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1053 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1054 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1055 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1056 # bad hours
1057 self.theclass(2000, 1, 31, 0) # no exception
1058 self.theclass(2000, 1, 31, 23) # no exception
1059 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1060 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1061 # bad minutes
1062 self.theclass(2000, 1, 31, 23, 0) # no exception
1063 self.theclass(2000, 1, 31, 23, 59) # no exception
1064 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1065 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1066 # bad seconds
1067 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1068 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1069 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1070 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1071 # bad microseconds
1072 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1073 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1074 self.assertRaises(ValueError, self.theclass,
1075 2000, 1, 31, 23, 59, 59, -1)
1076 self.assertRaises(ValueError, self.theclass,
1077 2000, 1, 31, 23, 59, 59,
1078 1000000)
1079
1080 def test_hash_equality(self):
1081 d = self.theclass(2000, 12, 31, 23, 30, 17)
1082 e = self.theclass(2000, 12, 31, 23, 30, 17)
1083 self.assertEqual(d, e)
1084 self.assertEqual(hash(d), hash(e))
1085
1086 dic = {d: 1}
1087 dic[e] = 2
1088 self.assertEqual(len(dic), 1)
1089 self.assertEqual(dic[d], 2)
1090 self.assertEqual(dic[e], 2)
1091
1092 d = self.theclass(2001, 1, 1, 0, 5, 17)
1093 e = self.theclass(2001, 1, 1, 0, 5, 17)
1094 self.assertEqual(d, e)
1095 self.assertEqual(hash(d), hash(e))
1096
1097 dic = {d: 1}
1098 dic[e] = 2
1099 self.assertEqual(len(dic), 1)
1100 self.assertEqual(dic[d], 2)
1101 self.assertEqual(dic[e], 2)
1102
1103 def test_computations(self):
1104 a = self.theclass(2002, 1, 31)
1105 b = self.theclass(1956, 1, 31)
1106 diff = a-b
1107 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1108 self.assertEqual(diff.seconds, 0)
1109 self.assertEqual(diff.microseconds, 0)
1110 a = self.theclass(2002, 3, 2, 17, 6)
1111 millisec = timedelta(0, 0, 1000)
1112 hour = timedelta(0, 3600)
1113 day = timedelta(1)
1114 week = timedelta(7)
1115 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1116 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1117 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1118 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1119 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1120 self.assertEqual(a - hour, a + -hour)
1121 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1122 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1123 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1124 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1125 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1126 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1127 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1128 self.assertEqual((a + week) - a, week)
1129 self.assertEqual((a + day) - a, day)
1130 self.assertEqual((a + hour) - a, hour)
1131 self.assertEqual((a + millisec) - a, millisec)
1132 self.assertEqual((a - week) - a, -week)
1133 self.assertEqual((a - day) - a, -day)
1134 self.assertEqual((a - hour) - a, -hour)
1135 self.assertEqual((a - millisec) - a, -millisec)
1136 self.assertEqual(a - (a + week), -week)
1137 self.assertEqual(a - (a + day), -day)
1138 self.assertEqual(a - (a + hour), -hour)
1139 self.assertEqual(a - (a + millisec), -millisec)
1140 self.assertEqual(a - (a - week), week)
1141 self.assertEqual(a - (a - day), day)
1142 self.assertEqual(a - (a - hour), hour)
1143 self.assertEqual(a - (a - millisec), millisec)
1144 self.assertEqual(a + (week + day + hour + millisec),
1145 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1146 self.assertEqual(a + (week + day + hour + millisec),
1147 (((a + week) + day) + hour) + millisec)
1148 self.assertEqual(a - (week + day + hour + millisec),
1149 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1150 self.assertEqual(a - (week + day + hour + millisec),
1151 (((a - week) - day) - hour) - millisec)
1152 # Add/sub ints, longs, floats should be illegal
1153 for i in 1, 1L, 1.0:
1154 self.assertRaises(TypeError, lambda: a+i)
1155 self.assertRaises(TypeError, lambda: a-i)
1156 self.assertRaises(TypeError, lambda: i+a)
1157 self.assertRaises(TypeError, lambda: i-a)
1158
1159 # delta - datetime is senseless.
1160 self.assertRaises(TypeError, lambda: day - a)
1161 # mixing datetime and (delta or datetime) via * or // is senseless
1162 self.assertRaises(TypeError, lambda: day * a)
1163 self.assertRaises(TypeError, lambda: a * day)
1164 self.assertRaises(TypeError, lambda: day // a)
1165 self.assertRaises(TypeError, lambda: a // day)
1166 self.assertRaises(TypeError, lambda: a * a)
1167 self.assertRaises(TypeError, lambda: a // a)
1168 # datetime + datetime is senseless
1169 self.assertRaises(TypeError, lambda: a + a)
1170
1171 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001172 args = 6, 7, 23, 20, 59, 1, 64**2
1173 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001174 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001175 green = pickler.dumps(orig, proto)
1176 derived = unpickler.loads(green)
1177 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001178
Guido van Rossum275666f2003-02-07 21:49:01 +00001179 def test_more_pickling(self):
1180 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1181 s = pickle.dumps(a)
1182 b = pickle.loads(s)
1183 self.assertEqual(b.year, 2003)
1184 self.assertEqual(b.month, 2)
1185 self.assertEqual(b.day, 7)
1186
Tim Peters2a799bf2002-12-16 20:18:38 +00001187 def test_more_compare(self):
1188 # The test_compare() inherited from TestDate covers the error cases.
1189 # We just want to test lexicographic ordering on the members datetime
1190 # has that date lacks.
1191 args = [2000, 11, 29, 20, 58, 16, 999998]
1192 t1 = self.theclass(*args)
1193 t2 = self.theclass(*args)
1194 self.failUnless(t1 == t2)
1195 self.failUnless(t1 <= t2)
1196 self.failUnless(t1 >= t2)
1197 self.failUnless(not t1 != t2)
1198 self.failUnless(not t1 < t2)
1199 self.failUnless(not t1 > t2)
1200 self.assertEqual(cmp(t1, t2), 0)
1201 self.assertEqual(cmp(t2, t1), 0)
1202
1203 for i in range(len(args)):
1204 newargs = args[:]
1205 newargs[i] = args[i] + 1
1206 t2 = self.theclass(*newargs) # this is larger than t1
1207 self.failUnless(t1 < t2)
1208 self.failUnless(t2 > t1)
1209 self.failUnless(t1 <= t2)
1210 self.failUnless(t2 >= t1)
1211 self.failUnless(t1 != t2)
1212 self.failUnless(t2 != t1)
1213 self.failUnless(not t1 == t2)
1214 self.failUnless(not t2 == t1)
1215 self.failUnless(not t1 > t2)
1216 self.failUnless(not t2 < t1)
1217 self.failUnless(not t1 >= t2)
1218 self.failUnless(not t2 <= t1)
1219 self.assertEqual(cmp(t1, t2), -1)
1220 self.assertEqual(cmp(t2, t1), 1)
1221
1222
1223 # A helper for timestamp constructor tests.
1224 def verify_field_equality(self, expected, got):
1225 self.assertEqual(expected.tm_year, got.year)
1226 self.assertEqual(expected.tm_mon, got.month)
1227 self.assertEqual(expected.tm_mday, got.day)
1228 self.assertEqual(expected.tm_hour, got.hour)
1229 self.assertEqual(expected.tm_min, got.minute)
1230 self.assertEqual(expected.tm_sec, got.second)
1231
1232 def test_fromtimestamp(self):
1233 import time
1234
1235 ts = time.time()
1236 expected = time.localtime(ts)
1237 got = self.theclass.fromtimestamp(ts)
1238 self.verify_field_equality(expected, got)
1239
1240 def test_utcfromtimestamp(self):
1241 import time
1242
1243 ts = time.time()
1244 expected = time.gmtime(ts)
1245 got = self.theclass.utcfromtimestamp(ts)
1246 self.verify_field_equality(expected, got)
1247
1248 def test_utcnow(self):
1249 import time
1250
1251 # Call it a success if utcnow() and utcfromtimestamp() are within
1252 # a second of each other.
1253 tolerance = timedelta(seconds=1)
1254 for dummy in range(3):
1255 from_now = self.theclass.utcnow()
1256 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1257 if abs(from_timestamp - from_now) <= tolerance:
1258 break
1259 # Else try again a few times.
1260 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1261
1262 def test_more_timetuple(self):
1263 # This tests fields beyond those tested by the TestDate.test_timetuple.
1264 t = self.theclass(2004, 12, 31, 6, 22, 33)
1265 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1266 self.assertEqual(t.timetuple(),
1267 (t.year, t.month, t.day,
1268 t.hour, t.minute, t.second,
1269 t.weekday(),
1270 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1271 -1))
1272 tt = t.timetuple()
1273 self.assertEqual(tt.tm_year, t.year)
1274 self.assertEqual(tt.tm_mon, t.month)
1275 self.assertEqual(tt.tm_mday, t.day)
1276 self.assertEqual(tt.tm_hour, t.hour)
1277 self.assertEqual(tt.tm_min, t.minute)
1278 self.assertEqual(tt.tm_sec, t.second)
1279 self.assertEqual(tt.tm_wday, t.weekday())
1280 self.assertEqual(tt.tm_yday, t.toordinal() -
1281 date(t.year, 1, 1).toordinal() + 1)
1282 self.assertEqual(tt.tm_isdst, -1)
1283
1284 def test_more_strftime(self):
1285 # This tests fields beyond those tested by the TestDate.test_strftime.
1286 t = self.theclass(2004, 12, 31, 6, 22, 33)
1287 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1288 "12 31 04 33 22 06 366")
1289
1290 def test_extract(self):
1291 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1292 self.assertEqual(dt.date(), date(2002, 3, 4))
1293 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1294
1295 def test_combine(self):
1296 d = date(2002, 3, 4)
1297 t = time(18, 45, 3, 1234)
1298 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1299 combine = self.theclass.combine
1300 dt = combine(d, t)
1301 self.assertEqual(dt, expected)
1302
1303 dt = combine(time=t, date=d)
1304 self.assertEqual(dt, expected)
1305
1306 self.assertEqual(d, dt.date())
1307 self.assertEqual(t, dt.time())
1308 self.assertEqual(dt, combine(dt.date(), dt.time()))
1309
1310 self.assertRaises(TypeError, combine) # need an arg
1311 self.assertRaises(TypeError, combine, d) # need two args
1312 self.assertRaises(TypeError, combine, t, d) # args reversed
1313 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1314 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1315
Tim Peters12bf3392002-12-24 05:41:27 +00001316 def test_replace(self):
1317 cls = self.theclass
1318 args = [1, 2, 3, 4, 5, 6, 7]
1319 base = cls(*args)
1320 self.assertEqual(base, base.replace())
1321
1322 i = 0
1323 for name, newval in (("year", 2),
1324 ("month", 3),
1325 ("day", 4),
1326 ("hour", 5),
1327 ("minute", 6),
1328 ("second", 7),
1329 ("microsecond", 8)):
1330 newargs = args[:]
1331 newargs[i] = newval
1332 expected = cls(*newargs)
1333 got = base.replace(**{name: newval})
1334 self.assertEqual(expected, got)
1335 i += 1
1336
1337 # Out of bounds.
1338 base = cls(2000, 2, 29)
1339 self.assertRaises(ValueError, base.replace, year=2001)
1340
Tim Peters80475bb2002-12-25 07:40:55 +00001341 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001342 # Pretty boring! The TZ test is more interesting here. astimezone()
1343 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001344 dt = self.theclass.now()
1345 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001346 self.assertRaises(TypeError, dt.astimezone) # not enough args
1347 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1348 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001349 self.assertRaises(ValueError, dt.astimezone, f) # naive
1350 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001351
Tim Peters52dcce22003-01-23 16:36:11 +00001352 class Bogus(tzinfo):
1353 def utcoffset(self, dt): return None
1354 def dst(self, dt): return timedelta(0)
1355 bog = Bogus()
1356 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1357
1358 class AlsoBogus(tzinfo):
1359 def utcoffset(self, dt): return timedelta(0)
1360 def dst(self, dt): return None
1361 alsobog = AlsoBogus()
1362 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001363
Tim Peters2a799bf2002-12-16 20:18:38 +00001364class TestTime(unittest.TestCase):
1365
1366 theclass = time
1367
1368 def test_basic_attributes(self):
1369 t = self.theclass(12, 0)
1370 self.assertEqual(t.hour, 12)
1371 self.assertEqual(t.minute, 0)
1372 self.assertEqual(t.second, 0)
1373 self.assertEqual(t.microsecond, 0)
1374
1375 def test_basic_attributes_nonzero(self):
1376 # Make sure all attributes are non-zero so bugs in
1377 # bit-shifting access show up.
1378 t = self.theclass(12, 59, 59, 8000)
1379 self.assertEqual(t.hour, 12)
1380 self.assertEqual(t.minute, 59)
1381 self.assertEqual(t.second, 59)
1382 self.assertEqual(t.microsecond, 8000)
1383
1384 def test_roundtrip(self):
1385 t = self.theclass(1, 2, 3, 4)
1386
1387 # Verify t -> string -> time identity.
1388 s = repr(t)
1389 self.failUnless(s.startswith('datetime.'))
1390 s = s[9:]
1391 t2 = eval(s)
1392 self.assertEqual(t, t2)
1393
1394 # Verify identity via reconstructing from pieces.
1395 t2 = self.theclass(t.hour, t.minute, t.second,
1396 t.microsecond)
1397 self.assertEqual(t, t2)
1398
1399 def test_comparing(self):
1400 args = [1, 2, 3, 4]
1401 t1 = self.theclass(*args)
1402 t2 = self.theclass(*args)
1403 self.failUnless(t1 == t2)
1404 self.failUnless(t1 <= t2)
1405 self.failUnless(t1 >= t2)
1406 self.failUnless(not t1 != t2)
1407 self.failUnless(not t1 < t2)
1408 self.failUnless(not t1 > t2)
1409 self.assertEqual(cmp(t1, t2), 0)
1410 self.assertEqual(cmp(t2, t1), 0)
1411
1412 for i in range(len(args)):
1413 newargs = args[:]
1414 newargs[i] = args[i] + 1
1415 t2 = self.theclass(*newargs) # this is larger than t1
1416 self.failUnless(t1 < t2)
1417 self.failUnless(t2 > t1)
1418 self.failUnless(t1 <= t2)
1419 self.failUnless(t2 >= t1)
1420 self.failUnless(t1 != t2)
1421 self.failUnless(t2 != t1)
1422 self.failUnless(not t1 == t2)
1423 self.failUnless(not t2 == t1)
1424 self.failUnless(not t1 > t2)
1425 self.failUnless(not t2 < t1)
1426 self.failUnless(not t1 >= t2)
1427 self.failUnless(not t2 <= t1)
1428 self.assertEqual(cmp(t1, t2), -1)
1429 self.assertEqual(cmp(t2, t1), 1)
1430
Tim Peters0bf60bd2003-01-08 20:40:01 +00001431 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1432 if CMP_BUG_FIXED:
1433 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
1434 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001435 self.assertRaises(TypeError, lambda: t1 == badarg)
1436 self.assertRaises(TypeError, lambda: t1 != badarg)
1437 self.assertRaises(TypeError, lambda: t1 <= badarg)
1438 self.assertRaises(TypeError, lambda: t1 < badarg)
1439 self.assertRaises(TypeError, lambda: t1 > badarg)
1440 self.assertRaises(TypeError, lambda: t1 >= badarg)
1441 self.assertRaises(TypeError, lambda: badarg == t1)
1442 self.assertRaises(TypeError, lambda: badarg != t1)
1443 self.assertRaises(TypeError, lambda: badarg <= t1)
1444 self.assertRaises(TypeError, lambda: badarg < t1)
1445 self.assertRaises(TypeError, lambda: badarg > t1)
1446 self.assertRaises(TypeError, lambda: badarg >= t1)
1447
1448 def test_bad_constructor_arguments(self):
1449 # bad hours
1450 self.theclass(0, 0) # no exception
1451 self.theclass(23, 0) # no exception
1452 self.assertRaises(ValueError, self.theclass, -1, 0)
1453 self.assertRaises(ValueError, self.theclass, 24, 0)
1454 # bad minutes
1455 self.theclass(23, 0) # no exception
1456 self.theclass(23, 59) # no exception
1457 self.assertRaises(ValueError, self.theclass, 23, -1)
1458 self.assertRaises(ValueError, self.theclass, 23, 60)
1459 # bad seconds
1460 self.theclass(23, 59, 0) # no exception
1461 self.theclass(23, 59, 59) # no exception
1462 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1463 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1464 # bad microseconds
1465 self.theclass(23, 59, 59, 0) # no exception
1466 self.theclass(23, 59, 59, 999999) # no exception
1467 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1468 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1469
1470 def test_hash_equality(self):
1471 d = self.theclass(23, 30, 17)
1472 e = self.theclass(23, 30, 17)
1473 self.assertEqual(d, e)
1474 self.assertEqual(hash(d), hash(e))
1475
1476 dic = {d: 1}
1477 dic[e] = 2
1478 self.assertEqual(len(dic), 1)
1479 self.assertEqual(dic[d], 2)
1480 self.assertEqual(dic[e], 2)
1481
1482 d = self.theclass(0, 5, 17)
1483 e = self.theclass(0, 5, 17)
1484 self.assertEqual(d, e)
1485 self.assertEqual(hash(d), hash(e))
1486
1487 dic = {d: 1}
1488 dic[e] = 2
1489 self.assertEqual(len(dic), 1)
1490 self.assertEqual(dic[d], 2)
1491 self.assertEqual(dic[e], 2)
1492
1493 def test_isoformat(self):
1494 t = self.theclass(4, 5, 1, 123)
1495 self.assertEqual(t.isoformat(), "04:05:01.000123")
1496 self.assertEqual(t.isoformat(), str(t))
1497
1498 t = self.theclass()
1499 self.assertEqual(t.isoformat(), "00:00:00")
1500 self.assertEqual(t.isoformat(), str(t))
1501
1502 t = self.theclass(microsecond=1)
1503 self.assertEqual(t.isoformat(), "00:00:00.000001")
1504 self.assertEqual(t.isoformat(), str(t))
1505
1506 t = self.theclass(microsecond=10)
1507 self.assertEqual(t.isoformat(), "00:00:00.000010")
1508 self.assertEqual(t.isoformat(), str(t))
1509
1510 t = self.theclass(microsecond=100)
1511 self.assertEqual(t.isoformat(), "00:00:00.000100")
1512 self.assertEqual(t.isoformat(), str(t))
1513
1514 t = self.theclass(microsecond=1000)
1515 self.assertEqual(t.isoformat(), "00:00:00.001000")
1516 self.assertEqual(t.isoformat(), str(t))
1517
1518 t = self.theclass(microsecond=10000)
1519 self.assertEqual(t.isoformat(), "00:00:00.010000")
1520 self.assertEqual(t.isoformat(), str(t))
1521
1522 t = self.theclass(microsecond=100000)
1523 self.assertEqual(t.isoformat(), "00:00:00.100000")
1524 self.assertEqual(t.isoformat(), str(t))
1525
1526 def test_strftime(self):
1527 t = self.theclass(1, 2, 3, 4)
1528 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1529 # A naive object replaces %z and %Z with empty strings.
1530 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1531
1532 def test_str(self):
1533 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1534 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1535 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1536 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1537 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1538
1539 def test_repr(self):
1540 name = 'datetime.' + self.theclass.__name__
1541 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1542 "%s(1, 2, 3, 4)" % name)
1543 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1544 "%s(10, 2, 3, 4000)" % name)
1545 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1546 "%s(0, 2, 3, 400000)" % name)
1547 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1548 "%s(12, 2, 3)" % name)
1549 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1550 "%s(23, 15)" % name)
1551
1552 def test_resolution_info(self):
1553 self.assert_(isinstance(self.theclass.min, self.theclass))
1554 self.assert_(isinstance(self.theclass.max, self.theclass))
1555 self.assert_(isinstance(self.theclass.resolution, timedelta))
1556 self.assert_(self.theclass.max > self.theclass.min)
1557
1558 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001559 args = 20, 59, 16, 64**2
1560 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001561 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001562 green = pickler.dumps(orig, proto)
1563 derived = unpickler.loads(green)
1564 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001565
1566 def test_bool(self):
1567 cls = self.theclass
1568 self.failUnless(cls(1))
1569 self.failUnless(cls(0, 1))
1570 self.failUnless(cls(0, 0, 1))
1571 self.failUnless(cls(0, 0, 0, 1))
1572 self.failUnless(not cls(0))
1573 self.failUnless(not cls())
1574
Tim Peters12bf3392002-12-24 05:41:27 +00001575 def test_replace(self):
1576 cls = self.theclass
1577 args = [1, 2, 3, 4]
1578 base = cls(*args)
1579 self.assertEqual(base, base.replace())
1580
1581 i = 0
1582 for name, newval in (("hour", 5),
1583 ("minute", 6),
1584 ("second", 7),
1585 ("microsecond", 8)):
1586 newargs = args[:]
1587 newargs[i] = newval
1588 expected = cls(*newargs)
1589 got = base.replace(**{name: newval})
1590 self.assertEqual(expected, got)
1591 i += 1
1592
1593 # Out of bounds.
1594 base = cls(1)
1595 self.assertRaises(ValueError, base.replace, hour=24)
1596 self.assertRaises(ValueError, base.replace, minute=-1)
1597 self.assertRaises(ValueError, base.replace, second=100)
1598 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1599
Tim Peters855fe882002-12-22 03:43:39 +00001600# A mixin for classes with a tzinfo= argument. Subclasses must define
1601# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001602# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001603class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001604
Tim Petersbad8ff02002-12-30 20:52:32 +00001605 def test_argument_passing(self):
1606 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001607 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001608 class introspective(tzinfo):
1609 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001610 def utcoffset(self, dt):
1611 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001612 dst = utcoffset
1613
1614 obj = cls(1, 2, 3, tzinfo=introspective())
1615
Tim Peters0bf60bd2003-01-08 20:40:01 +00001616 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001617 self.assertEqual(obj.tzname(), expected)
1618
Tim Peters0bf60bd2003-01-08 20:40:01 +00001619 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001620 self.assertEqual(obj.utcoffset(), expected)
1621 self.assertEqual(obj.dst(), expected)
1622
Tim Peters855fe882002-12-22 03:43:39 +00001623 def test_bad_tzinfo_classes(self):
1624 cls = self.theclass
1625 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001626
Tim Peters855fe882002-12-22 03:43:39 +00001627 class NiceTry(object):
1628 def __init__(self): pass
1629 def utcoffset(self, dt): pass
1630 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1631
1632 class BetterTry(tzinfo):
1633 def __init__(self): pass
1634 def utcoffset(self, dt): pass
1635 b = BetterTry()
1636 t = cls(1, 1, 1, tzinfo=b)
1637 self.failUnless(t.tzinfo is b)
1638
1639 def test_utc_offset_out_of_bounds(self):
1640 class Edgy(tzinfo):
1641 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001642 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001643 def utcoffset(self, dt):
1644 return self.offset
1645
1646 cls = self.theclass
1647 for offset, legit in ((-1440, False),
1648 (-1439, True),
1649 (1439, True),
1650 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001651 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001652 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001653 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001654 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001655 else:
1656 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001657 if legit:
1658 aofs = abs(offset)
1659 h, m = divmod(aofs, 60)
1660 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001661 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001662 t = t.timetz()
1663 self.assertEqual(str(t), "01:02:03" + tag)
1664 else:
1665 self.assertRaises(ValueError, str, t)
1666
1667 def test_tzinfo_classes(self):
1668 cls = self.theclass
1669 class C1(tzinfo):
1670 def utcoffset(self, dt): return None
1671 def dst(self, dt): return None
1672 def tzname(self, dt): return None
1673 for t in (cls(1, 1, 1),
1674 cls(1, 1, 1, tzinfo=None),
1675 cls(1, 1, 1, tzinfo=C1())):
1676 self.failUnless(t.utcoffset() is None)
1677 self.failUnless(t.dst() is None)
1678 self.failUnless(t.tzname() is None)
1679
Tim Peters855fe882002-12-22 03:43:39 +00001680 class C3(tzinfo):
1681 def utcoffset(self, dt): return timedelta(minutes=-1439)
1682 def dst(self, dt): return timedelta(minutes=1439)
1683 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001684 t = cls(1, 1, 1, tzinfo=C3())
1685 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1686 self.assertEqual(t.dst(), timedelta(minutes=1439))
1687 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001688
1689 # Wrong types.
1690 class C4(tzinfo):
1691 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001692 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001693 def tzname(self, dt): return 0
1694 t = cls(1, 1, 1, tzinfo=C4())
1695 self.assertRaises(TypeError, t.utcoffset)
1696 self.assertRaises(TypeError, t.dst)
1697 self.assertRaises(TypeError, t.tzname)
1698
1699 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001700 class C6(tzinfo):
1701 def utcoffset(self, dt): return timedelta(hours=-24)
1702 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001703 t = cls(1, 1, 1, tzinfo=C6())
1704 self.assertRaises(ValueError, t.utcoffset)
1705 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001706
1707 # Not a whole number of minutes.
1708 class C7(tzinfo):
1709 def utcoffset(self, dt): return timedelta(seconds=61)
1710 def dst(self, dt): return timedelta(microseconds=-81)
1711 t = cls(1, 1, 1, tzinfo=C7())
1712 self.assertRaises(ValueError, t.utcoffset)
1713 self.assertRaises(ValueError, t.dst)
1714
Tim Peters4c0db782002-12-26 05:01:19 +00001715 def test_aware_compare(self):
1716 cls = self.theclass
1717
Tim Peters60c76e42002-12-27 00:41:11 +00001718 # Ensure that utcoffset() gets ignored if the comparands have
1719 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001720 class OperandDependentOffset(tzinfo):
1721 def utcoffset(self, t):
1722 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001723 # d0 and d1 equal after adjustment
1724 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001725 else:
Tim Peters397301e2003-01-02 21:28:08 +00001726 # d2 off in the weeds
1727 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001728
1729 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1730 d0 = base.replace(minute=3)
1731 d1 = base.replace(minute=9)
1732 d2 = base.replace(minute=11)
1733 for x in d0, d1, d2:
1734 for y in d0, d1, d2:
1735 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001736 expected = cmp(x.minute, y.minute)
1737 self.assertEqual(got, expected)
1738
1739 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001740 # Note that a time can't actually have an operand-depedent offset,
1741 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1742 # so skip this test for time.
1743 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001744 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1745 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1746 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1747 for x in d0, d1, d2:
1748 for y in d0, d1, d2:
1749 got = cmp(x, y)
1750 if (x is d0 or x is d1) and (y is d0 or y is d1):
1751 expected = 0
1752 elif x is y is d2:
1753 expected = 0
1754 elif x is d2:
1755 expected = -1
1756 else:
1757 assert y is d2
1758 expected = 1
1759 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001760
Tim Peters855fe882002-12-22 03:43:39 +00001761
Tim Peters0bf60bd2003-01-08 20:40:01 +00001762# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001763class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001764 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001765
1766 def test_empty(self):
1767 t = self.theclass()
1768 self.assertEqual(t.hour, 0)
1769 self.assertEqual(t.minute, 0)
1770 self.assertEqual(t.second, 0)
1771 self.assertEqual(t.microsecond, 0)
1772 self.failUnless(t.tzinfo is None)
1773
Tim Peters2a799bf2002-12-16 20:18:38 +00001774 def test_zones(self):
1775 est = FixedOffset(-300, "EST", 1)
1776 utc = FixedOffset(0, "UTC", -2)
1777 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001778 t1 = time( 7, 47, tzinfo=est)
1779 t2 = time(12, 47, tzinfo=utc)
1780 t3 = time(13, 47, tzinfo=met)
1781 t4 = time(microsecond=40)
1782 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001783
1784 self.assertEqual(t1.tzinfo, est)
1785 self.assertEqual(t2.tzinfo, utc)
1786 self.assertEqual(t3.tzinfo, met)
1787 self.failUnless(t4.tzinfo is None)
1788 self.assertEqual(t5.tzinfo, utc)
1789
Tim Peters855fe882002-12-22 03:43:39 +00001790 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1791 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1792 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001793 self.failUnless(t4.utcoffset() is None)
1794 self.assertRaises(TypeError, t1.utcoffset, "no args")
1795
1796 self.assertEqual(t1.tzname(), "EST")
1797 self.assertEqual(t2.tzname(), "UTC")
1798 self.assertEqual(t3.tzname(), "MET")
1799 self.failUnless(t4.tzname() is None)
1800 self.assertRaises(TypeError, t1.tzname, "no args")
1801
Tim Peters855fe882002-12-22 03:43:39 +00001802 self.assertEqual(t1.dst(), timedelta(minutes=1))
1803 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1804 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001805 self.failUnless(t4.dst() is None)
1806 self.assertRaises(TypeError, t1.dst, "no args")
1807
1808 self.assertEqual(hash(t1), hash(t2))
1809 self.assertEqual(hash(t1), hash(t3))
1810 self.assertEqual(hash(t2), hash(t3))
1811
1812 self.assertEqual(t1, t2)
1813 self.assertEqual(t1, t3)
1814 self.assertEqual(t2, t3)
1815 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1816 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1817 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1818
1819 self.assertEqual(str(t1), "07:47:00-05:00")
1820 self.assertEqual(str(t2), "12:47:00+00:00")
1821 self.assertEqual(str(t3), "13:47:00+01:00")
1822 self.assertEqual(str(t4), "00:00:00.000040")
1823 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1824
1825 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1826 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1827 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1828 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1829 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1830
Tim Peters0bf60bd2003-01-08 20:40:01 +00001831 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001832 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1833 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1834 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1835 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1836 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1837
1838 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1839 "07:47:00 %Z=EST %z=-0500")
1840 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1841 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1842
1843 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001844 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001845 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1846 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1847
Tim Petersb92bb712002-12-21 17:44:07 +00001848 # Check that an invalid tzname result raises an exception.
1849 class Badtzname(tzinfo):
1850 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001851 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001852 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1853 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001854
1855 def test_hash_edge_cases(self):
1856 # Offsets that overflow a basic time.
1857 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1858 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1859 self.assertEqual(hash(t1), hash(t2))
1860
1861 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1862 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1863 self.assertEqual(hash(t1), hash(t2))
1864
Tim Peters2a799bf2002-12-16 20:18:38 +00001865 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001866 # Try one without a tzinfo.
1867 args = 20, 59, 16, 64**2
1868 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001869 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001870 green = pickler.dumps(orig, proto)
1871 derived = unpickler.loads(green)
1872 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001873
1874 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001875 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001876 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001877 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001878 green = pickler.dumps(orig, proto)
1879 derived = unpickler.loads(green)
1880 self.assertEqual(orig, derived)
1881 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1882 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1883 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001884
1885 def test_more_bool(self):
1886 # Test cases with non-None tzinfo.
1887 cls = self.theclass
1888
1889 t = cls(0, tzinfo=FixedOffset(-300, ""))
1890 self.failUnless(t)
1891
1892 t = cls(5, tzinfo=FixedOffset(-300, ""))
1893 self.failUnless(t)
1894
1895 t = cls(5, tzinfo=FixedOffset(300, ""))
1896 self.failUnless(not t)
1897
1898 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1899 self.failUnless(not t)
1900
1901 # Mostly ensuring this doesn't overflow internally.
1902 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1903 self.failUnless(t)
1904
1905 # But this should yield a value error -- the utcoffset is bogus.
1906 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1907 self.assertRaises(ValueError, lambda: bool(t))
1908
1909 # Likewise.
1910 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1911 self.assertRaises(ValueError, lambda: bool(t))
1912
Tim Peters12bf3392002-12-24 05:41:27 +00001913 def test_replace(self):
1914 cls = self.theclass
1915 z100 = FixedOffset(100, "+100")
1916 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1917 args = [1, 2, 3, 4, z100]
1918 base = cls(*args)
1919 self.assertEqual(base, base.replace())
1920
1921 i = 0
1922 for name, newval in (("hour", 5),
1923 ("minute", 6),
1924 ("second", 7),
1925 ("microsecond", 8),
1926 ("tzinfo", zm200)):
1927 newargs = args[:]
1928 newargs[i] = newval
1929 expected = cls(*newargs)
1930 got = base.replace(**{name: newval})
1931 self.assertEqual(expected, got)
1932 i += 1
1933
1934 # Ensure we can get rid of a tzinfo.
1935 self.assertEqual(base.tzname(), "+100")
1936 base2 = base.replace(tzinfo=None)
1937 self.failUnless(base2.tzinfo is None)
1938 self.failUnless(base2.tzname() is None)
1939
1940 # Ensure we can add one.
1941 base3 = base2.replace(tzinfo=z100)
1942 self.assertEqual(base, base3)
1943 self.failUnless(base.tzinfo is base3.tzinfo)
1944
1945 # Out of bounds.
1946 base = cls(1)
1947 self.assertRaises(ValueError, base.replace, hour=24)
1948 self.assertRaises(ValueError, base.replace, minute=-1)
1949 self.assertRaises(ValueError, base.replace, second=100)
1950 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1951
Tim Peters60c76e42002-12-27 00:41:11 +00001952 def test_mixed_compare(self):
1953 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001954 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001955 self.assertEqual(t1, t2)
1956 t2 = t2.replace(tzinfo=None)
1957 self.assertEqual(t1, t2)
1958 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1959 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001960 if CMP_BUG_FIXED:
1961 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1962 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001963
Tim Peters0bf60bd2003-01-08 20:40:01 +00001964 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00001965 class Varies(tzinfo):
1966 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00001967 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00001968 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00001969 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00001970 return self.offset
1971
1972 v = Varies()
1973 t1 = t2.replace(tzinfo=v)
1974 t2 = t2.replace(tzinfo=v)
1975 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
1976 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
1977 self.assertEqual(t1, t2)
1978
1979 # But if they're not identical, it isn't ignored.
1980 t2 = t2.replace(tzinfo=Varies())
1981 self.failUnless(t1 < t2) # t1's offset counter still going up
1982
Tim Peters4c0db782002-12-26 05:01:19 +00001983
Tim Peters0bf60bd2003-01-08 20:40:01 +00001984# Testing datetime objects with a non-None tzinfo.
1985
Tim Peters855fe882002-12-22 03:43:39 +00001986class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001987 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00001988
1989 def test_trivial(self):
1990 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
1991 self.assertEqual(dt.year, 1)
1992 self.assertEqual(dt.month, 2)
1993 self.assertEqual(dt.day, 3)
1994 self.assertEqual(dt.hour, 4)
1995 self.assertEqual(dt.minute, 5)
1996 self.assertEqual(dt.second, 6)
1997 self.assertEqual(dt.microsecond, 7)
1998 self.assertEqual(dt.tzinfo, None)
1999
2000 def test_even_more_compare(self):
2001 # The test_compare() and test_more_compare() inherited from TestDate
2002 # and TestDateTime covered non-tzinfo cases.
2003
2004 # Smallest possible after UTC adjustment.
2005 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2006 # Largest possible after UTC adjustment.
2007 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2008 tzinfo=FixedOffset(-1439, ""))
2009
2010 # Make sure those compare correctly, and w/o overflow.
2011 self.failUnless(t1 < t2)
2012 self.failUnless(t1 != t2)
2013 self.failUnless(t2 > t1)
2014
2015 self.failUnless(t1 == t1)
2016 self.failUnless(t2 == t2)
2017
2018 # Equal afer adjustment.
2019 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2020 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2021 self.assertEqual(t1, t2)
2022
2023 # Change t1 not to subtract a minute, and t1 should be larger.
2024 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2025 self.failUnless(t1 > t2)
2026
2027 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2028 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2029 self.failUnless(t1 < t2)
2030
2031 # Back to the original t1, but make seconds resolve it.
2032 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2033 second=1)
2034 self.failUnless(t1 > t2)
2035
2036 # Likewise, but make microseconds resolve it.
2037 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2038 microsecond=1)
2039 self.failUnless(t1 > t2)
2040
2041 # Make t2 naive and it should fail.
2042 t2 = self.theclass.min
2043 self.assertRaises(TypeError, lambda: t1 == t2)
2044 self.assertEqual(t2, t2)
2045
2046 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2047 class Naive(tzinfo):
2048 def utcoffset(self, dt): return None
2049 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2050 self.assertRaises(TypeError, lambda: t1 == t2)
2051 self.assertEqual(t2, t2)
2052
2053 # OTOH, it's OK to compare two of these mixing the two ways of being
2054 # naive.
2055 t1 = self.theclass(5, 6, 7)
2056 self.assertEqual(t1, t2)
2057
2058 # Try a bogus uctoffset.
2059 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002060 def utcoffset(self, dt):
2061 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002062 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2063 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002064 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002065
Tim Peters2a799bf2002-12-16 20:18:38 +00002066 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002067 # Try one without a tzinfo.
2068 args = 6, 7, 23, 20, 59, 1, 64**2
2069 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002070 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002071 green = pickler.dumps(orig, proto)
2072 derived = unpickler.loads(green)
2073 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002074
2075 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002076 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002077 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002078 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002079 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002080 green = pickler.dumps(orig, proto)
2081 derived = unpickler.loads(green)
2082 self.assertEqual(orig, derived)
2083 self.failUnless(isinstance(derived.tzinfo,
2084 PicklableFixedOffset))
2085 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2086 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002087
2088 def test_extreme_hashes(self):
2089 # If an attempt is made to hash these via subtracting the offset
2090 # then hashing a datetime object, OverflowError results. The
2091 # Python implementation used to blow up here.
2092 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2093 hash(t)
2094 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2095 tzinfo=FixedOffset(-1439, ""))
2096 hash(t)
2097
2098 # OTOH, an OOB offset should blow up.
2099 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2100 self.assertRaises(ValueError, hash, t)
2101
2102 def test_zones(self):
2103 est = FixedOffset(-300, "EST")
2104 utc = FixedOffset(0, "UTC")
2105 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002106 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2107 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2108 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002109 self.assertEqual(t1.tzinfo, est)
2110 self.assertEqual(t2.tzinfo, utc)
2111 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002112 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2113 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2114 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002115 self.assertEqual(t1.tzname(), "EST")
2116 self.assertEqual(t2.tzname(), "UTC")
2117 self.assertEqual(t3.tzname(), "MET")
2118 self.assertEqual(hash(t1), hash(t2))
2119 self.assertEqual(hash(t1), hash(t3))
2120 self.assertEqual(hash(t2), hash(t3))
2121 self.assertEqual(t1, t2)
2122 self.assertEqual(t1, t3)
2123 self.assertEqual(t2, t3)
2124 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2125 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2126 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002127 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002128 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2129 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2130 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2131
2132 def test_combine(self):
2133 met = FixedOffset(60, "MET")
2134 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002135 tz = time(18, 45, 3, 1234, tzinfo=met)
2136 dt = datetime.combine(d, tz)
2137 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002138 tzinfo=met))
2139
2140 def test_extract(self):
2141 met = FixedOffset(60, "MET")
2142 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2143 self.assertEqual(dt.date(), date(2002, 3, 4))
2144 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002145 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002146
2147 def test_tz_aware_arithmetic(self):
2148 import random
2149
2150 now = self.theclass.now()
2151 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002152 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002153 nowaware = self.theclass.combine(now.date(), timeaware)
2154 self.failUnless(nowaware.tzinfo is tz55)
2155 self.assertEqual(nowaware.timetz(), timeaware)
2156
2157 # Can't mix aware and non-aware.
2158 self.assertRaises(TypeError, lambda: now - nowaware)
2159 self.assertRaises(TypeError, lambda: nowaware - now)
2160
Tim Peters0bf60bd2003-01-08 20:40:01 +00002161 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002162 self.assertRaises(TypeError, lambda: now + nowaware)
2163 self.assertRaises(TypeError, lambda: nowaware + now)
2164 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2165
2166 # Subtracting should yield 0.
2167 self.assertEqual(now - now, timedelta(0))
2168 self.assertEqual(nowaware - nowaware, timedelta(0))
2169
2170 # Adding a delta should preserve tzinfo.
2171 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2172 nowawareplus = nowaware + delta
2173 self.failUnless(nowaware.tzinfo is tz55)
2174 nowawareplus2 = delta + nowaware
2175 self.failUnless(nowawareplus2.tzinfo is tz55)
2176 self.assertEqual(nowawareplus, nowawareplus2)
2177
2178 # that - delta should be what we started with, and that - what we
2179 # started with should be delta.
2180 diff = nowawareplus - delta
2181 self.failUnless(diff.tzinfo is tz55)
2182 self.assertEqual(nowaware, diff)
2183 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2184 self.assertEqual(nowawareplus - nowaware, delta)
2185
2186 # Make up a random timezone.
2187 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002188 # Attach it to nowawareplus.
2189 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002190 self.failUnless(nowawareplus.tzinfo is tzr)
2191 # Make sure the difference takes the timezone adjustments into account.
2192 got = nowaware - nowawareplus
2193 # Expected: (nowaware base - nowaware offset) -
2194 # (nowawareplus base - nowawareplus offset) =
2195 # (nowaware base - nowawareplus base) +
2196 # (nowawareplus offset - nowaware offset) =
2197 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002198 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002199 self.assertEqual(got, expected)
2200
2201 # Try max possible difference.
2202 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2203 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2204 tzinfo=FixedOffset(-1439, "max"))
2205 maxdiff = max - min
2206 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2207 timedelta(minutes=2*1439))
2208
2209 def test_tzinfo_now(self):
2210 meth = self.theclass.now
2211 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2212 base = meth()
2213 # Try with and without naming the keyword.
2214 off42 = FixedOffset(42, "42")
2215 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002216 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002217 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002218 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002219 # Bad argument with and w/o naming the keyword.
2220 self.assertRaises(TypeError, meth, 16)
2221 self.assertRaises(TypeError, meth, tzinfo=16)
2222 # Bad keyword name.
2223 self.assertRaises(TypeError, meth, tinfo=off42)
2224 # Too many args.
2225 self.assertRaises(TypeError, meth, off42, off42)
2226
Tim Peters10cadce2003-01-23 19:58:02 +00002227 # We don't know which time zone we're in, and don't have a tzinfo
2228 # class to represent it, so seeing whether a tz argument actually
2229 # does a conversion is tricky.
2230 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2231 utc = FixedOffset(0, "utc", 0)
2232 for dummy in range(3):
2233 now = datetime.now(weirdtz)
2234 self.failUnless(now.tzinfo is weirdtz)
2235 utcnow = datetime.utcnow().replace(tzinfo=utc)
2236 now2 = utcnow.astimezone(weirdtz)
2237 if abs(now - now2) < timedelta(seconds=30):
2238 break
2239 # Else the code is broken, or more than 30 seconds passed between
2240 # calls; assuming the latter, just try again.
2241 else:
2242 # Three strikes and we're out.
2243 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2244
Tim Peters2a799bf2002-12-16 20:18:38 +00002245 def test_tzinfo_fromtimestamp(self):
2246 import time
2247 meth = self.theclass.fromtimestamp
2248 ts = time.time()
2249 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2250 base = meth(ts)
2251 # Try with and without naming the keyword.
2252 off42 = FixedOffset(42, "42")
2253 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002254 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002255 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002256 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002257 # Bad argument with and w/o naming the keyword.
2258 self.assertRaises(TypeError, meth, ts, 16)
2259 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2260 # Bad keyword name.
2261 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2262 # Too many args.
2263 self.assertRaises(TypeError, meth, ts, off42, off42)
2264 # Too few args.
2265 self.assertRaises(TypeError, meth)
2266
Tim Peters2a44a8d2003-01-23 20:53:10 +00002267 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002268 timestamp = 1000000000
2269 utcdatetime = datetime.utcfromtimestamp(timestamp)
2270 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2271 # But on some flavor of Mac, it's nowhere near that. So we can't have
2272 # any idea here what time that actually is, we can only test that
2273 # relative changes match.
2274 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2275 tz = FixedOffset(utcoffset, "tz", 0)
2276 expected = utcdatetime + utcoffset
2277 got = datetime.fromtimestamp(timestamp, tz)
2278 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002279
Tim Peters2a799bf2002-12-16 20:18:38 +00002280 def test_tzinfo_utcnow(self):
2281 meth = self.theclass.utcnow
2282 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2283 base = meth()
2284 # Try with and without naming the keyword; for whatever reason,
2285 # utcnow() doesn't accept a tzinfo argument.
2286 off42 = FixedOffset(42, "42")
2287 self.assertRaises(TypeError, meth, off42)
2288 self.assertRaises(TypeError, meth, tzinfo=off42)
2289
2290 def test_tzinfo_utcfromtimestamp(self):
2291 import time
2292 meth = self.theclass.utcfromtimestamp
2293 ts = time.time()
2294 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2295 base = meth(ts)
2296 # Try with and without naming the keyword; for whatever reason,
2297 # utcfromtimestamp() doesn't accept a tzinfo argument.
2298 off42 = FixedOffset(42, "42")
2299 self.assertRaises(TypeError, meth, ts, off42)
2300 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2301
2302 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002303 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002304 # DST flag.
2305 class DST(tzinfo):
2306 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002307 if isinstance(dstvalue, int):
2308 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002309 self.dstvalue = dstvalue
2310 def dst(self, dt):
2311 return self.dstvalue
2312
2313 cls = self.theclass
2314 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2315 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2316 t = d.timetuple()
2317 self.assertEqual(1, t.tm_year)
2318 self.assertEqual(1, t.tm_mon)
2319 self.assertEqual(1, t.tm_mday)
2320 self.assertEqual(10, t.tm_hour)
2321 self.assertEqual(20, t.tm_min)
2322 self.assertEqual(30, t.tm_sec)
2323 self.assertEqual(0, t.tm_wday)
2324 self.assertEqual(1, t.tm_yday)
2325 self.assertEqual(flag, t.tm_isdst)
2326
2327 # dst() returns wrong type.
2328 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2329
2330 # dst() at the edge.
2331 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2332 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2333
2334 # dst() out of range.
2335 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2336 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2337
2338 def test_utctimetuple(self):
2339 class DST(tzinfo):
2340 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002341 if isinstance(dstvalue, int):
2342 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002343 self.dstvalue = dstvalue
2344 def dst(self, dt):
2345 return self.dstvalue
2346
2347 cls = self.theclass
2348 # This can't work: DST didn't implement utcoffset.
2349 self.assertRaises(NotImplementedError,
2350 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2351
2352 class UOFS(DST):
2353 def __init__(self, uofs, dofs=None):
2354 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002355 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002356 def utcoffset(self, dt):
2357 return self.uofs
2358
2359 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2360 # in effect for a UTC time.
2361 for dstvalue in -33, 33, 0, None:
2362 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2363 t = d.utctimetuple()
2364 self.assertEqual(d.year, t.tm_year)
2365 self.assertEqual(d.month, t.tm_mon)
2366 self.assertEqual(d.day, t.tm_mday)
2367 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2368 self.assertEqual(13, t.tm_min)
2369 self.assertEqual(d.second, t.tm_sec)
2370 self.assertEqual(d.weekday(), t.tm_wday)
2371 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2372 t.tm_yday)
2373 self.assertEqual(0, t.tm_isdst)
2374
2375 # At the edges, UTC adjustment can normalize into years out-of-range
2376 # for a datetime object. Ensure that a correct timetuple is
2377 # created anyway.
2378 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2379 # That goes back 1 minute less than a full day.
2380 t = tiny.utctimetuple()
2381 self.assertEqual(t.tm_year, MINYEAR-1)
2382 self.assertEqual(t.tm_mon, 12)
2383 self.assertEqual(t.tm_mday, 31)
2384 self.assertEqual(t.tm_hour, 0)
2385 self.assertEqual(t.tm_min, 1)
2386 self.assertEqual(t.tm_sec, 37)
2387 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2388 self.assertEqual(t.tm_isdst, 0)
2389
2390 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2391 # That goes forward 1 minute less than a full day.
2392 t = huge.utctimetuple()
2393 self.assertEqual(t.tm_year, MAXYEAR+1)
2394 self.assertEqual(t.tm_mon, 1)
2395 self.assertEqual(t.tm_mday, 1)
2396 self.assertEqual(t.tm_hour, 23)
2397 self.assertEqual(t.tm_min, 58)
2398 self.assertEqual(t.tm_sec, 37)
2399 self.assertEqual(t.tm_yday, 1)
2400 self.assertEqual(t.tm_isdst, 0)
2401
2402 def test_tzinfo_isoformat(self):
2403 zero = FixedOffset(0, "+00:00")
2404 plus = FixedOffset(220, "+03:40")
2405 minus = FixedOffset(-231, "-03:51")
2406 unknown = FixedOffset(None, "")
2407
2408 cls = self.theclass
2409 datestr = '0001-02-03'
2410 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002411 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002412 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2413 timestr = '04:05:59' + (us and '.987001' or '')
2414 ofsstr = ofs is not None and d.tzname() or ''
2415 tailstr = timestr + ofsstr
2416 iso = d.isoformat()
2417 self.assertEqual(iso, datestr + 'T' + tailstr)
2418 self.assertEqual(iso, d.isoformat('T'))
2419 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2420 self.assertEqual(str(d), datestr + ' ' + tailstr)
2421
Tim Peters12bf3392002-12-24 05:41:27 +00002422 def test_replace(self):
2423 cls = self.theclass
2424 z100 = FixedOffset(100, "+100")
2425 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2426 args = [1, 2, 3, 4, 5, 6, 7, z100]
2427 base = cls(*args)
2428 self.assertEqual(base, base.replace())
2429
2430 i = 0
2431 for name, newval in (("year", 2),
2432 ("month", 3),
2433 ("day", 4),
2434 ("hour", 5),
2435 ("minute", 6),
2436 ("second", 7),
2437 ("microsecond", 8),
2438 ("tzinfo", zm200)):
2439 newargs = args[:]
2440 newargs[i] = newval
2441 expected = cls(*newargs)
2442 got = base.replace(**{name: newval})
2443 self.assertEqual(expected, got)
2444 i += 1
2445
2446 # Ensure we can get rid of a tzinfo.
2447 self.assertEqual(base.tzname(), "+100")
2448 base2 = base.replace(tzinfo=None)
2449 self.failUnless(base2.tzinfo is None)
2450 self.failUnless(base2.tzname() is None)
2451
2452 # Ensure we can add one.
2453 base3 = base2.replace(tzinfo=z100)
2454 self.assertEqual(base, base3)
2455 self.failUnless(base.tzinfo is base3.tzinfo)
2456
2457 # Out of bounds.
2458 base = cls(2000, 2, 29)
2459 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002460
Tim Peters80475bb2002-12-25 07:40:55 +00002461 def test_more_astimezone(self):
2462 # The inherited test_astimezone covered some trivial and error cases.
2463 fnone = FixedOffset(None, "None")
2464 f44m = FixedOffset(44, "44")
2465 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2466
Tim Peters10cadce2003-01-23 19:58:02 +00002467 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002468 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002469 # Replacing with degenerate tzinfo raises an exception.
2470 self.assertRaises(ValueError, dt.astimezone, fnone)
2471 # Ditto with None tz.
2472 self.assertRaises(TypeError, dt.astimezone, None)
2473 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002474 x = dt.astimezone(dt.tzinfo)
2475 self.failUnless(x.tzinfo is f44m)
2476 self.assertEqual(x.date(), dt.date())
2477 self.assertEqual(x.time(), dt.time())
2478
2479 # Replacing with different tzinfo does adjust.
2480 got = dt.astimezone(fm5h)
2481 self.failUnless(got.tzinfo is fm5h)
2482 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2483 expected = dt - dt.utcoffset() # in effect, convert to UTC
2484 expected += fm5h.utcoffset(dt) # and from there to local time
2485 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2486 self.assertEqual(got.date(), expected.date())
2487 self.assertEqual(got.time(), expected.time())
2488 self.assertEqual(got.timetz(), expected.timetz())
2489 self.failUnless(got.tzinfo is expected.tzinfo)
2490 self.assertEqual(got, expected)
2491
Tim Peters4c0db782002-12-26 05:01:19 +00002492 def test_aware_subtract(self):
2493 cls = self.theclass
2494
Tim Peters60c76e42002-12-27 00:41:11 +00002495 # Ensure that utcoffset() is ignored when the operands have the
2496 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002497 class OperandDependentOffset(tzinfo):
2498 def utcoffset(self, t):
2499 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002500 # d0 and d1 equal after adjustment
2501 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002502 else:
Tim Peters397301e2003-01-02 21:28:08 +00002503 # d2 off in the weeds
2504 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002505
2506 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2507 d0 = base.replace(minute=3)
2508 d1 = base.replace(minute=9)
2509 d2 = base.replace(minute=11)
2510 for x in d0, d1, d2:
2511 for y in d0, d1, d2:
2512 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002513 expected = timedelta(minutes=x.minute - y.minute)
2514 self.assertEqual(got, expected)
2515
2516 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2517 # ignored.
2518 base = cls(8, 9, 10, 11, 12, 13, 14)
2519 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2520 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2521 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2522 for x in d0, d1, d2:
2523 for y in d0, d1, d2:
2524 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002525 if (x is d0 or x is d1) and (y is d0 or y is d1):
2526 expected = timedelta(0)
2527 elif x is y is d2:
2528 expected = timedelta(0)
2529 elif x is d2:
2530 expected = timedelta(minutes=(11-59)-0)
2531 else:
2532 assert y is d2
2533 expected = timedelta(minutes=0-(11-59))
2534 self.assertEqual(got, expected)
2535
Tim Peters60c76e42002-12-27 00:41:11 +00002536 def test_mixed_compare(self):
2537 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002538 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002539 self.assertEqual(t1, t2)
2540 t2 = t2.replace(tzinfo=None)
2541 self.assertEqual(t1, t2)
2542 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2543 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002544 if CMP_BUG_FIXED:
2545 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2546 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002547
Tim Peters0bf60bd2003-01-08 20:40:01 +00002548 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002549 class Varies(tzinfo):
2550 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002551 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002552 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002553 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002554 return self.offset
2555
2556 v = Varies()
2557 t1 = t2.replace(tzinfo=v)
2558 t2 = t2.replace(tzinfo=v)
2559 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2560 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2561 self.assertEqual(t1, t2)
2562
2563 # But if they're not identical, it isn't ignored.
2564 t2 = t2.replace(tzinfo=Varies())
2565 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002566
Tim Peters621818b2002-12-29 23:44:49 +00002567# Pain to set up DST-aware tzinfo classes.
2568
2569def first_sunday_on_or_after(dt):
2570 days_to_go = 6 - dt.weekday()
2571 if days_to_go:
2572 dt += timedelta(days_to_go)
2573 return dt
2574
2575ZERO = timedelta(0)
2576HOUR = timedelta(hours=1)
2577DAY = timedelta(days=1)
2578# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2579DSTSTART = datetime(1, 4, 1, 2)
2580# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002581# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2582# being standard time on that day, there is no spelling in local time of
2583# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2584DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002585
2586class USTimeZone(tzinfo):
2587
2588 def __init__(self, hours, reprname, stdname, dstname):
2589 self.stdoffset = timedelta(hours=hours)
2590 self.reprname = reprname
2591 self.stdname = stdname
2592 self.dstname = dstname
2593
2594 def __repr__(self):
2595 return self.reprname
2596
2597 def tzname(self, dt):
2598 if self.dst(dt):
2599 return self.dstname
2600 else:
2601 return self.stdname
2602
2603 def utcoffset(self, dt):
2604 return self.stdoffset + self.dst(dt)
2605
2606 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002607 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002608 # An exception instead may be sensible here, in one or more of
2609 # the cases.
2610 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002611 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002612
2613 # Find first Sunday in April.
2614 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2615 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2616
2617 # Find last Sunday in October.
2618 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2619 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2620
Tim Peters621818b2002-12-29 23:44:49 +00002621 # Can't compare naive to aware objects, so strip the timezone from
2622 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002623 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002624 return HOUR
2625 else:
2626 return ZERO
2627
Tim Peters521fc152002-12-31 17:36:56 +00002628Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2629Central = USTimeZone(-6, "Central", "CST", "CDT")
2630Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2631Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002632utc_real = FixedOffset(0, "UTC", 0)
2633# For better test coverage, we want another flavor of UTC that's west of
2634# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002635utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002636
2637class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002638 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002639 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002640 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002641
Tim Peters0bf60bd2003-01-08 20:40:01 +00002642 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002643
Tim Peters521fc152002-12-31 17:36:56 +00002644 # Check a time that's inside DST.
2645 def checkinside(self, dt, tz, utc, dston, dstoff):
2646 self.assertEqual(dt.dst(), HOUR)
2647
2648 # Conversion to our own timezone is always an identity.
2649 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002650
2651 asutc = dt.astimezone(utc)
2652 there_and_back = asutc.astimezone(tz)
2653
2654 # Conversion to UTC and back isn't always an identity here,
2655 # because there are redundant spellings (in local time) of
2656 # UTC time when DST begins: the clock jumps from 1:59:59
2657 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2658 # make sense then. The classes above treat 2:MM:SS as
2659 # daylight time then (it's "after 2am"), really an alias
2660 # for 1:MM:SS standard time. The latter form is what
2661 # conversion back from UTC produces.
2662 if dt.date() == dston.date() and dt.hour == 2:
2663 # We're in the redundant hour, and coming back from
2664 # UTC gives the 1:MM:SS standard-time spelling.
2665 self.assertEqual(there_and_back + HOUR, dt)
2666 # Although during was considered to be in daylight
2667 # time, there_and_back is not.
2668 self.assertEqual(there_and_back.dst(), ZERO)
2669 # They're the same times in UTC.
2670 self.assertEqual(there_and_back.astimezone(utc),
2671 dt.astimezone(utc))
2672 else:
2673 # We're not in the redundant hour.
2674 self.assertEqual(dt, there_and_back)
2675
Tim Peters327098a2003-01-20 22:54:38 +00002676 # Because we have a redundant spelling when DST begins, there is
2677 # (unforunately) an hour when DST ends that can't be spelled at all in
2678 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2679 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2680 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2681 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2682 # expressed in local time. Nevertheless, we want conversion back
2683 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002684 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002685 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002686 if dt.date() == dstoff.date() and dt.hour == 0:
2687 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002688 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002689 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2690 nexthour_utc += HOUR
2691 nexthour_tz = nexthour_utc.astimezone(tz)
2692 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002693 else:
Tim Peters327098a2003-01-20 22:54:38 +00002694 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002695
2696 # Check a time that's outside DST.
2697 def checkoutside(self, dt, tz, utc):
2698 self.assertEqual(dt.dst(), ZERO)
2699
2700 # Conversion to our own timezone is always an identity.
2701 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002702
2703 # Converting to UTC and back is an identity too.
2704 asutc = dt.astimezone(utc)
2705 there_and_back = asutc.astimezone(tz)
2706 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002707
Tim Peters1024bf82002-12-30 17:09:40 +00002708 def convert_between_tz_and_utc(self, tz, utc):
2709 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002710 # Because 1:MM on the day DST ends is taken as being standard time,
2711 # there is no spelling in tz for the last hour of daylight time.
2712 # For purposes of the test, the last hour of DST is 0:MM, which is
2713 # taken as being daylight time (and 1:MM is taken as being standard
2714 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002715 dstoff = self.dstoff.replace(tzinfo=tz)
2716 for delta in (timedelta(weeks=13),
2717 DAY,
2718 HOUR,
2719 timedelta(minutes=1),
2720 timedelta(microseconds=1)):
2721
Tim Peters521fc152002-12-31 17:36:56 +00002722 self.checkinside(dston, tz, utc, dston, dstoff)
2723 for during in dston + delta, dstoff - delta:
2724 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002725
Tim Peters521fc152002-12-31 17:36:56 +00002726 self.checkoutside(dstoff, tz, utc)
2727 for outside in dston - delta, dstoff + delta:
2728 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002729
Tim Peters621818b2002-12-29 23:44:49 +00002730 def test_easy(self):
2731 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002732 self.convert_between_tz_and_utc(Eastern, utc_real)
2733 self.convert_between_tz_and_utc(Pacific, utc_real)
2734 self.convert_between_tz_and_utc(Eastern, utc_fake)
2735 self.convert_between_tz_and_utc(Pacific, utc_fake)
2736 # The next is really dancing near the edge. It works because
2737 # Pacific and Eastern are far enough apart that their "problem
2738 # hours" don't overlap.
2739 self.convert_between_tz_and_utc(Eastern, Pacific)
2740 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002741 # OTOH, these fail! Don't enable them. The difficulty is that
2742 # the edge case tests assume that every hour is representable in
2743 # the "utc" class. This is always true for a fixed-offset tzinfo
2744 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2745 # For these adjacent DST-aware time zones, the range of time offsets
2746 # tested ends up creating hours in the one that aren't representable
2747 # in the other. For the same reason, we would see failures in the
2748 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2749 # offset deltas in convert_between_tz_and_utc().
2750 #
2751 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2752 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002753
Tim Petersf3615152003-01-01 21:51:37 +00002754 def test_tricky(self):
2755 # 22:00 on day before daylight starts.
2756 fourback = self.dston - timedelta(hours=4)
2757 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002758 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002759 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2760 # 2", we should get the 3 spelling.
2761 # If we plug 22:00 the day before into Eastern, it "looks like std
2762 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2763 # to 22:00 lands on 2:00, which makes no sense in local time (the
2764 # local clock jumps from 1 to 3). The point here is to make sure we
2765 # get the 3 spelling.
2766 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002767 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002768 self.assertEqual(expected, got)
2769
2770 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2771 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002772 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002773 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2774 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2775 # spelling.
2776 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002777 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002778 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002779
Tim Petersadf64202003-01-04 06:03:15 +00002780 # Now on the day DST ends, we want "repeat an hour" behavior.
2781 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2782 # EST 23:MM 0:MM 1:MM 2:MM
2783 # EDT 0:MM 1:MM 2:MM 3:MM
2784 # wall 0:MM 1:MM 1:MM 2:MM against these
2785 for utc in utc_real, utc_fake:
2786 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002787 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002788 # Convert that to UTC.
2789 first_std_hour -= tz.utcoffset(None)
2790 # Adjust for possibly fake UTC.
2791 asutc = first_std_hour + utc.utcoffset(None)
2792 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2793 # tz=Eastern.
2794 asutcbase = asutc.replace(tzinfo=utc)
2795 for tzhour in (0, 1, 1, 2):
2796 expectedbase = self.dstoff.replace(hour=tzhour)
2797 for minute in 0, 30, 59:
2798 expected = expectedbase.replace(minute=minute)
2799 asutc = asutcbase.replace(minute=minute)
2800 astz = asutc.astimezone(tz)
2801 self.assertEqual(astz.replace(tzinfo=None), expected)
2802 asutcbase += HOUR
2803
2804
Tim Peters710fb152003-01-02 19:35:54 +00002805 def test_bogus_dst(self):
2806 class ok(tzinfo):
2807 def utcoffset(self, dt): return HOUR
2808 def dst(self, dt): return HOUR
2809
2810 now = self.theclass.now().replace(tzinfo=utc_real)
2811 # Doesn't blow up.
2812 now.astimezone(ok())
2813
2814 # Does blow up.
2815 class notok(ok):
2816 def dst(self, dt): return None
2817 self.assertRaises(ValueError, now.astimezone, notok())
2818
Tim Peters52dcce22003-01-23 16:36:11 +00002819 def test_fromutc(self):
2820 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2821 now = datetime.utcnow().replace(tzinfo=utc_real)
2822 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2823 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2824 enow = Eastern.fromutc(now) # doesn't blow up
2825 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2826 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2827 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2828
2829 # Always converts UTC to standard time.
2830 class FauxUSTimeZone(USTimeZone):
2831 def fromutc(self, dt):
2832 return dt + self.stdoffset
2833 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2834
2835 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2836 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2837 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2838
2839 # Check around DST start.
2840 start = self.dston.replace(hour=4, tzinfo=Eastern)
2841 fstart = start.replace(tzinfo=FEastern)
2842 for wall in 23, 0, 1, 3, 4, 5:
2843 expected = start.replace(hour=wall)
2844 if wall == 23:
2845 expected -= timedelta(days=1)
2846 got = Eastern.fromutc(start)
2847 self.assertEqual(expected, got)
2848
2849 expected = fstart + FEastern.stdoffset
2850 got = FEastern.fromutc(fstart)
2851 self.assertEqual(expected, got)
2852
2853 # Ensure astimezone() calls fromutc() too.
2854 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2855 self.assertEqual(expected, got)
2856
2857 start += HOUR
2858 fstart += HOUR
2859
2860 # Check around DST end.
2861 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2862 fstart = start.replace(tzinfo=FEastern)
2863 for wall in 0, 1, 1, 2, 3, 4:
2864 expected = start.replace(hour=wall)
2865 got = Eastern.fromutc(start)
2866 self.assertEqual(expected, got)
2867
2868 expected = fstart + FEastern.stdoffset
2869 got = FEastern.fromutc(fstart)
2870 self.assertEqual(expected, got)
2871
2872 # Ensure astimezone() calls fromutc() too.
2873 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2874 self.assertEqual(expected, got)
2875
2876 start += HOUR
2877 fstart += HOUR
2878
Tim Peters710fb152003-01-02 19:35:54 +00002879
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002880def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002881 allsuites = [unittest.makeSuite(klass, 'test')
2882 for klass in (TestModule,
2883 TestTZInfo,
2884 TestTimeDelta,
2885 TestDateOnly,
2886 TestDate,
2887 TestDateTime,
2888 TestTime,
2889 TestTimeTZ,
2890 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002891 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002892 )
2893 ]
2894 return unittest.TestSuite(allsuites)
2895
2896def test_main():
2897 import gc
2898 import sys
2899
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002900 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002901 lastrc = None
2902 while True:
2903 test_support.run_suite(thesuite)
2904 if 1: # change to 0, under a debug build, for some leak detection
2905 break
2906 gc.collect()
2907 if gc.garbage:
2908 raise SystemError("gc.garbage not empty after test run: %r" %
2909 gc.garbage)
2910 if hasattr(sys, 'gettotalrefcount'):
2911 thisrc = sys.gettotalrefcount()
2912 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2913 if lastrc:
2914 print >> sys.stderr, 'delta:', thisrc - lastrc
2915 else:
2916 print >> sys.stderr
2917 lastrc = thisrc
2918
2919if __name__ == "__main__":
2920 test_main()