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