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