blob: 3329104501ab5010c1c7779df398e9f35a4bd4fc [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
Tim Peters2a799bf2002-12-16 20:18:38 +00008import unittest
Guido van Rossumbf12cdb2006-08-17 20:24:18 +00009try:
10 import cPickle
11except ImportError:
12 cPickle = None
Tim Peters2a799bf2002-12-16 20:18:38 +000013
14from test import test_support
15
16from datetime import MINYEAR, MAXYEAR
17from datetime import timedelta
18from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000019from datetime import time
20from datetime import date, datetime
21
Tim Peters35ad6412003-02-05 04:08:07 +000022pickle_choices = [(pickler, unpickler, proto)
23 for pickler in pickle, cPickle
Guido van Rossumbf12cdb2006-08-17 20:24:18 +000024 if pickler is not None
Tim Peters35ad6412003-02-05 04:08:07 +000025 for unpickler in pickle, cPickle
Guido van Rossumbf12cdb2006-08-17 20:24:18 +000026 if unpickler is not None
Tim Peters35ad6412003-02-05 04:08:07 +000027 for proto in range(3)]
Guido van Rossumbf12cdb2006-08-17 20:24:18 +000028if cPickle is None:
29 assert len(pickle_choices) == 3
30else:
31 assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000032
Tim Peters68124bb2003-02-08 03:46:31 +000033# An arbitrary collection of objects of non-datetime types, for testing
34# mixed-type comparisons.
35OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000036
Tim Peters2a799bf2002-12-16 20:18:38 +000037
38#############################################################################
39# module tests
40
41class TestModule(unittest.TestCase):
42
43 def test_constants(self):
44 import datetime
45 self.assertEqual(datetime.MINYEAR, 1)
46 self.assertEqual(datetime.MAXYEAR, 9999)
47
48#############################################################################
49# tzinfo tests
50
51class FixedOffset(tzinfo):
52 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000053 if isinstance(offset, int):
54 offset = timedelta(minutes=offset)
55 if isinstance(dstoffset, int):
56 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000057 self.__offset = offset
58 self.__name = name
59 self.__dstoffset = dstoffset
60 def __repr__(self):
61 return self.__name.lower()
62 def utcoffset(self, dt):
63 return self.__offset
64 def tzname(self, dt):
65 return self.__name
66 def dst(self, dt):
67 return self.__dstoffset
68
Tim Petersfb8472c2002-12-21 05:04:42 +000069class PicklableFixedOffset(FixedOffset):
70 def __init__(self, offset=None, name=None, dstoffset=None):
71 FixedOffset.__init__(self, offset, name, dstoffset)
72
Tim Peters2a799bf2002-12-16 20:18:38 +000073class TestTZInfo(unittest.TestCase):
74
75 def test_non_abstractness(self):
76 # In order to allow subclasses to get pickled, the C implementation
77 # wasn't able to get away with having __init__ raise
78 # NotImplementedError.
79 useless = tzinfo()
80 dt = datetime.max
81 self.assertRaises(NotImplementedError, useless.tzname, dt)
82 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
83 self.assertRaises(NotImplementedError, useless.dst, dt)
84
85 def test_subclass_must_override(self):
86 class NotEnough(tzinfo):
87 def __init__(self, offset, name):
88 self.__offset = offset
89 self.__name = name
90 self.failUnless(issubclass(NotEnough, tzinfo))
91 ne = NotEnough(3, "NotByALongShot")
92 self.failUnless(isinstance(ne, tzinfo))
93
94 dt = datetime.now()
95 self.assertRaises(NotImplementedError, ne.tzname, dt)
96 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
97 self.assertRaises(NotImplementedError, ne.dst, dt)
98
99 def test_normal(self):
100 fo = FixedOffset(3, "Three")
101 self.failUnless(isinstance(fo, tzinfo))
102 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +0000103 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +0000104 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000105 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000106
107 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000108 # There's no point to pickling tzinfo objects on their own (they
109 # carry no data), but they need to be picklable anyway else
110 # concrete subclasses can't be pickled.
111 orig = tzinfo.__new__(tzinfo)
112 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000113 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000114 green = pickler.dumps(orig, proto)
115 derived = unpickler.loads(green)
116 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117
118 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000119 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000120 offset = timedelta(minutes=-300)
121 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000122 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000123 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000124 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000125 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000126 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000127 green = pickler.dumps(orig, proto)
128 derived = unpickler.loads(green)
129 self.failUnless(isinstance(derived, tzinfo))
130 self.failUnless(type(derived) is PicklableFixedOffset)
131 self.assertEqual(derived.utcoffset(None), offset)
132 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000133
134#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000135# Base clase for testing a particular aspect of timedelta, time, date and
136# datetime comparisons.
137
138class HarmlessMixedComparison(unittest.TestCase):
139 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
140
141 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
142 # legit constructor.
143
144 def test_harmless_mixed_comparison(self):
145 me = self.theclass(1, 1, 1)
146
147 self.failIf(me == ())
148 self.failUnless(me != ())
149 self.failIf(() == me)
150 self.failUnless(() != me)
151
152 self.failUnless(me in [1, 20L, [], me])
153 self.failIf(me not in [1, 20L, [], me])
154
155 self.failUnless([] in [me, 1, 20L, []])
156 self.failIf([] not in [me, 1, 20L, []])
157
158 def test_harmful_mixed_comparison(self):
159 me = self.theclass(1, 1, 1)
160
161 self.assertRaises(TypeError, lambda: me < ())
162 self.assertRaises(TypeError, lambda: me <= ())
163 self.assertRaises(TypeError, lambda: me > ())
164 self.assertRaises(TypeError, lambda: me >= ())
165
166 self.assertRaises(TypeError, lambda: () < me)
167 self.assertRaises(TypeError, lambda: () <= me)
168 self.assertRaises(TypeError, lambda: () > me)
169 self.assertRaises(TypeError, lambda: () >= me)
170
171 self.assertRaises(TypeError, cmp, (), me)
172 self.assertRaises(TypeError, cmp, me, ())
173
174#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000175# timedelta tests
176
Tim Peters07534a62003-02-07 22:50:28 +0000177class TestTimeDelta(HarmlessMixedComparison):
178
179 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000180
181 def test_constructor(self):
182 eq = self.assertEqual
183 td = timedelta
184
185 # Check keyword args to constructor
186 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
187 milliseconds=0, microseconds=0))
188 eq(td(1), td(days=1))
189 eq(td(0, 1), td(seconds=1))
190 eq(td(0, 0, 1), td(microseconds=1))
191 eq(td(weeks=1), td(days=7))
192 eq(td(days=1), td(hours=24))
193 eq(td(hours=1), td(minutes=60))
194 eq(td(minutes=1), td(seconds=60))
195 eq(td(seconds=1), td(milliseconds=1000))
196 eq(td(milliseconds=1), td(microseconds=1000))
197
198 # Check float args to constructor
199 eq(td(weeks=1.0/7), td(days=1))
200 eq(td(days=1.0/24), td(hours=1))
201 eq(td(hours=1.0/60), td(minutes=1))
202 eq(td(minutes=1.0/60), td(seconds=1))
203 eq(td(seconds=0.001), td(milliseconds=1))
204 eq(td(milliseconds=0.001), td(microseconds=1))
205
206 def test_computations(self):
207 eq = self.assertEqual
208 td = timedelta
209
210 a = td(7) # One week
211 b = td(0, 60) # One minute
212 c = td(0, 0, 1000) # One millisecond
213 eq(a+b+c, td(7, 60, 1000))
214 eq(a-b, td(6, 24*3600 - 60))
215 eq(-a, td(-7))
216 eq(+a, td(7))
217 eq(-b, td(-1, 24*3600 - 60))
218 eq(-c, td(-1, 24*3600 - 1, 999000))
219 eq(abs(a), a)
220 eq(abs(-a), a)
221 eq(td(6, 24*3600), a)
222 eq(td(0, 0, 60*1000000), b)
223 eq(a*10, td(70))
224 eq(a*10, 10*a)
225 eq(a*10L, 10*a)
226 eq(b*10, td(0, 600))
227 eq(10*b, td(0, 600))
228 eq(b*10L, td(0, 600))
229 eq(c*10, td(0, 0, 10000))
230 eq(10*c, td(0, 0, 10000))
231 eq(c*10L, td(0, 0, 10000))
232 eq(a*-1, -a)
233 eq(b*-2, -b-b)
234 eq(c*-2, -c+-c)
235 eq(b*(60*24), (b*60)*24)
236 eq(b*(60*24), (60*b)*24)
237 eq(c*1000, td(0, 1))
238 eq(1000*c, td(0, 1))
239 eq(a//7, td(1))
240 eq(b//10, td(0, 6))
241 eq(c//1000, td(0, 0, 1))
242 eq(a//10, td(0, 7*24*360))
243 eq(a//3600000, td(0, 0, 7*24*1000))
244
245 def test_disallowed_computations(self):
246 a = timedelta(42)
247
248 # Add/sub ints, longs, floats should be illegal
249 for i in 1, 1L, 1.0:
250 self.assertRaises(TypeError, lambda: a+i)
251 self.assertRaises(TypeError, lambda: a-i)
252 self.assertRaises(TypeError, lambda: i+a)
253 self.assertRaises(TypeError, lambda: i-a)
254
255 # Mul/div by float isn't supported.
256 x = 2.3
257 self.assertRaises(TypeError, lambda: a*x)
258 self.assertRaises(TypeError, lambda: x*a)
259 self.assertRaises(TypeError, lambda: a/x)
260 self.assertRaises(TypeError, lambda: x/a)
261 self.assertRaises(TypeError, lambda: a // x)
262 self.assertRaises(TypeError, lambda: x // a)
263
264 # Divison of int by timedelta doesn't make sense.
265 # Division by zero doesn't make sense.
266 for zero in 0, 0L:
267 self.assertRaises(TypeError, lambda: zero // a)
268 self.assertRaises(ZeroDivisionError, lambda: a // zero)
269
270 def test_basic_attributes(self):
271 days, seconds, us = 1, 7, 31
272 td = timedelta(days, seconds, us)
273 self.assertEqual(td.days, days)
274 self.assertEqual(td.seconds, seconds)
275 self.assertEqual(td.microseconds, us)
276
277 def test_carries(self):
278 t1 = timedelta(days=100,
279 weeks=-7,
280 hours=-24*(100-49),
281 minutes=-3,
282 seconds=12,
283 microseconds=(3*60 - 12) * 1e6 + 1)
284 t2 = timedelta(microseconds=1)
285 self.assertEqual(t1, t2)
286
287 def test_hash_equality(self):
288 t1 = timedelta(days=100,
289 weeks=-7,
290 hours=-24*(100-49),
291 minutes=-3,
292 seconds=12,
293 microseconds=(3*60 - 12) * 1000000)
294 t2 = timedelta()
295 self.assertEqual(hash(t1), hash(t2))
296
297 t1 += timedelta(weeks=7)
298 t2 += timedelta(days=7*7)
299 self.assertEqual(t1, t2)
300 self.assertEqual(hash(t1), hash(t2))
301
302 d = {t1: 1}
303 d[t2] = 2
304 self.assertEqual(len(d), 1)
305 self.assertEqual(d[t1], 2)
306
307 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000308 args = 12, 34, 56
309 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000310 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000311 green = pickler.dumps(orig, proto)
312 derived = unpickler.loads(green)
313 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000314
315 def test_compare(self):
316 t1 = timedelta(2, 3, 4)
317 t2 = timedelta(2, 3, 4)
318 self.failUnless(t1 == t2)
319 self.failUnless(t1 <= t2)
320 self.failUnless(t1 >= t2)
321 self.failUnless(not t1 != t2)
322 self.failUnless(not t1 < t2)
323 self.failUnless(not t1 > t2)
324 self.assertEqual(cmp(t1, t2), 0)
325 self.assertEqual(cmp(t2, t1), 0)
326
327 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
328 t2 = timedelta(*args) # this is larger than t1
329 self.failUnless(t1 < t2)
330 self.failUnless(t2 > t1)
331 self.failUnless(t1 <= t2)
332 self.failUnless(t2 >= t1)
333 self.failUnless(t1 != t2)
334 self.failUnless(t2 != t1)
335 self.failUnless(not t1 == t2)
336 self.failUnless(not t2 == t1)
337 self.failUnless(not t1 > t2)
338 self.failUnless(not t2 < t1)
339 self.failUnless(not t1 >= t2)
340 self.failUnless(not t2 <= t1)
341 self.assertEqual(cmp(t1, t2), -1)
342 self.assertEqual(cmp(t2, t1), 1)
343
Tim Peters68124bb2003-02-08 03:46:31 +0000344 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000345 self.assertEqual(t1 == badarg, False)
346 self.assertEqual(t1 != badarg, True)
347 self.assertEqual(badarg == t1, False)
348 self.assertEqual(badarg != t1, True)
349
Tim Peters2a799bf2002-12-16 20:18:38 +0000350 self.assertRaises(TypeError, lambda: t1 <= badarg)
351 self.assertRaises(TypeError, lambda: t1 < badarg)
352 self.assertRaises(TypeError, lambda: t1 > badarg)
353 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000354 self.assertRaises(TypeError, lambda: badarg <= t1)
355 self.assertRaises(TypeError, lambda: badarg < t1)
356 self.assertRaises(TypeError, lambda: badarg > t1)
357 self.assertRaises(TypeError, lambda: badarg >= t1)
358
359 def test_str(self):
360 td = timedelta
361 eq = self.assertEqual
362
363 eq(str(td(1)), "1 day, 0:00:00")
364 eq(str(td(-1)), "-1 day, 0:00:00")
365 eq(str(td(2)), "2 days, 0:00:00")
366 eq(str(td(-2)), "-2 days, 0:00:00")
367
368 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
369 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
370 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
371 "-210 days, 23:12:34")
372
373 eq(str(td(milliseconds=1)), "0:00:00.001000")
374 eq(str(td(microseconds=3)), "0:00:00.000003")
375
376 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
377 microseconds=999999)),
378 "999999999 days, 23:59:59.999999")
379
380 def test_roundtrip(self):
381 for td in (timedelta(days=999999999, hours=23, minutes=59,
382 seconds=59, microseconds=999999),
383 timedelta(days=-999999999),
384 timedelta(days=1, seconds=2, microseconds=3)):
385
386 # Verify td -> string -> td identity.
387 s = repr(td)
388 self.failUnless(s.startswith('datetime.'))
389 s = s[9:]
390 td2 = eval(s)
391 self.assertEqual(td, td2)
392
393 # Verify identity via reconstructing from pieces.
394 td2 = timedelta(td.days, td.seconds, td.microseconds)
395 self.assertEqual(td, td2)
396
397 def test_resolution_info(self):
398 self.assert_(isinstance(timedelta.min, timedelta))
399 self.assert_(isinstance(timedelta.max, timedelta))
400 self.assert_(isinstance(timedelta.resolution, timedelta))
401 self.assert_(timedelta.max > timedelta.min)
402 self.assertEqual(timedelta.min, timedelta(-999999999))
403 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
404 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
405
406 def test_overflow(self):
407 tiny = timedelta.resolution
408
409 td = timedelta.min + tiny
410 td -= tiny # no problem
411 self.assertRaises(OverflowError, td.__sub__, tiny)
412 self.assertRaises(OverflowError, td.__add__, -tiny)
413
414 td = timedelta.max - tiny
415 td += tiny # no problem
416 self.assertRaises(OverflowError, td.__add__, tiny)
417 self.assertRaises(OverflowError, td.__sub__, -tiny)
418
419 self.assertRaises(OverflowError, lambda: -timedelta.max)
420
421 def test_microsecond_rounding(self):
422 td = timedelta
423 eq = self.assertEqual
424
425 # Single-field rounding.
426 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
427 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
428 eq(td(milliseconds=0.6/1000), td(microseconds=1))
429 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
430
431 # Rounding due to contributions from more than one field.
432 us_per_hour = 3600e6
433 us_per_day = us_per_hour * 24
434 eq(td(days=.4/us_per_day), td(0))
435 eq(td(hours=.2/us_per_hour), td(0))
436 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
437
438 eq(td(days=-.4/us_per_day), td(0))
439 eq(td(hours=-.2/us_per_hour), td(0))
440 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
441
442 def test_massive_normalization(self):
443 td = timedelta(microseconds=-1)
444 self.assertEqual((td.days, td.seconds, td.microseconds),
445 (-1, 24*3600-1, 999999))
446
447 def test_bool(self):
448 self.failUnless(timedelta(1))
449 self.failUnless(timedelta(0, 1))
450 self.failUnless(timedelta(0, 0, 1))
451 self.failUnless(timedelta(microseconds=1))
452 self.failUnless(not timedelta(0))
453
Tim Petersb0c854d2003-05-17 15:57:00 +0000454 def test_subclass_timedelta(self):
455
456 class T(timedelta):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000457 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000458 def from_td(td):
459 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000460
461 def as_hours(self):
462 sum = (self.days * 24 +
463 self.seconds / 3600.0 +
464 self.microseconds / 3600e6)
465 return round(sum)
466
467 t1 = T(days=1)
468 self.assert_(type(t1) is T)
469 self.assertEqual(t1.as_hours(), 24)
470
471 t2 = T(days=-1, seconds=-3600)
472 self.assert_(type(t2) is T)
473 self.assertEqual(t2.as_hours(), -25)
474
475 t3 = t1 + t2
476 self.assert_(type(t3) is timedelta)
477 t4 = T.from_td(t3)
478 self.assert_(type(t4) is T)
479 self.assertEqual(t3.days, t4.days)
480 self.assertEqual(t3.seconds, t4.seconds)
481 self.assertEqual(t3.microseconds, t4.microseconds)
482 self.assertEqual(str(t3), str(t4))
483 self.assertEqual(t4.as_hours(), -1)
484
Tim Peters2a799bf2002-12-16 20:18:38 +0000485#############################################################################
486# date tests
487
488class TestDateOnly(unittest.TestCase):
489 # Tests here won't pass if also run on datetime objects, so don't
490 # subclass this to test datetimes too.
491
492 def test_delta_non_days_ignored(self):
493 dt = date(2000, 1, 2)
494 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
495 microseconds=5)
496 days = timedelta(delta.days)
497 self.assertEqual(days, timedelta(1))
498
499 dt2 = dt + delta
500 self.assertEqual(dt2, dt + days)
501
502 dt2 = delta + dt
503 self.assertEqual(dt2, dt + days)
504
505 dt2 = dt - delta
506 self.assertEqual(dt2, dt - days)
507
508 delta = -delta
509 days = timedelta(delta.days)
510 self.assertEqual(days, timedelta(-2))
511
512 dt2 = dt + delta
513 self.assertEqual(dt2, dt + days)
514
515 dt2 = delta + dt
516 self.assertEqual(dt2, dt + days)
517
518 dt2 = dt - delta
519 self.assertEqual(dt2, dt - days)
520
Tim Peters604c0132004-06-07 23:04:33 +0000521class SubclassDate(date):
522 sub_var = 1
523
Tim Peters07534a62003-02-07 22:50:28 +0000524class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000525 # Tests here should pass for both dates and datetimes, except for a
526 # few tests that TestDateTime overrides.
527
528 theclass = date
529
530 def test_basic_attributes(self):
531 dt = self.theclass(2002, 3, 1)
532 self.assertEqual(dt.year, 2002)
533 self.assertEqual(dt.month, 3)
534 self.assertEqual(dt.day, 1)
535
536 def test_roundtrip(self):
537 for dt in (self.theclass(1, 2, 3),
538 self.theclass.today()):
539 # Verify dt -> string -> date identity.
540 s = repr(dt)
541 self.failUnless(s.startswith('datetime.'))
542 s = s[9:]
543 dt2 = eval(s)
544 self.assertEqual(dt, dt2)
545
546 # Verify identity via reconstructing from pieces.
547 dt2 = self.theclass(dt.year, dt.month, dt.day)
548 self.assertEqual(dt, dt2)
549
550 def test_ordinal_conversions(self):
551 # Check some fixed values.
552 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
553 (1, 12, 31, 365),
554 (2, 1, 1, 366),
555 # first example from "Calendrical Calculations"
556 (1945, 11, 12, 710347)]:
557 d = self.theclass(y, m, d)
558 self.assertEqual(n, d.toordinal())
559 fromord = self.theclass.fromordinal(n)
560 self.assertEqual(d, fromord)
561 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000562 # if we're checking something fancier than a date, verify
563 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000564 self.assertEqual(fromord.hour, 0)
565 self.assertEqual(fromord.minute, 0)
566 self.assertEqual(fromord.second, 0)
567 self.assertEqual(fromord.microsecond, 0)
568
Tim Peters0bf60bd2003-01-08 20:40:01 +0000569 # Check first and last days of year spottily across the whole
570 # range of years supported.
571 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000572 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
573 d = self.theclass(year, 1, 1)
574 n = d.toordinal()
575 d2 = self.theclass.fromordinal(n)
576 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000577 # Verify that moving back a day gets to the end of year-1.
578 if year > 1:
579 d = self.theclass.fromordinal(n-1)
580 d2 = self.theclass(year-1, 12, 31)
581 self.assertEqual(d, d2)
582 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000583
584 # Test every day in a leap-year and a non-leap year.
585 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
586 for year, isleap in (2000, True), (2002, False):
587 n = self.theclass(year, 1, 1).toordinal()
588 for month, maxday in zip(range(1, 13), dim):
589 if month == 2 and isleap:
590 maxday += 1
591 for day in range(1, maxday+1):
592 d = self.theclass(year, month, day)
593 self.assertEqual(d.toordinal(), n)
594 self.assertEqual(d, self.theclass.fromordinal(n))
595 n += 1
596
597 def test_extreme_ordinals(self):
598 a = self.theclass.min
599 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
600 aord = a.toordinal()
601 b = a.fromordinal(aord)
602 self.assertEqual(a, b)
603
604 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
605
606 b = a + timedelta(days=1)
607 self.assertEqual(b.toordinal(), aord + 1)
608 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
609
610 a = self.theclass.max
611 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
612 aord = a.toordinal()
613 b = a.fromordinal(aord)
614 self.assertEqual(a, b)
615
616 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
617
618 b = a - timedelta(days=1)
619 self.assertEqual(b.toordinal(), aord - 1)
620 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
621
622 def test_bad_constructor_arguments(self):
623 # bad years
624 self.theclass(MINYEAR, 1, 1) # no exception
625 self.theclass(MAXYEAR, 1, 1) # no exception
626 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
627 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
628 # bad months
629 self.theclass(2000, 1, 1) # no exception
630 self.theclass(2000, 12, 1) # no exception
631 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
632 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
633 # bad days
634 self.theclass(2000, 2, 29) # no exception
635 self.theclass(2004, 2, 29) # no exception
636 self.theclass(2400, 2, 29) # no exception
637 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
638 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
639 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
640 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
641 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
642 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
643
644 def test_hash_equality(self):
645 d = self.theclass(2000, 12, 31)
646 # same thing
647 e = self.theclass(2000, 12, 31)
648 self.assertEqual(d, e)
649 self.assertEqual(hash(d), hash(e))
650
651 dic = {d: 1}
652 dic[e] = 2
653 self.assertEqual(len(dic), 1)
654 self.assertEqual(dic[d], 2)
655 self.assertEqual(dic[e], 2)
656
657 d = self.theclass(2001, 1, 1)
658 # same thing
659 e = self.theclass(2001, 1, 1)
660 self.assertEqual(d, e)
661 self.assertEqual(hash(d), hash(e))
662
663 dic = {d: 1}
664 dic[e] = 2
665 self.assertEqual(len(dic), 1)
666 self.assertEqual(dic[d], 2)
667 self.assertEqual(dic[e], 2)
668
669 def test_computations(self):
670 a = self.theclass(2002, 1, 31)
671 b = self.theclass(1956, 1, 31)
672
673 diff = a-b
674 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
675 self.assertEqual(diff.seconds, 0)
676 self.assertEqual(diff.microseconds, 0)
677
678 day = timedelta(1)
679 week = timedelta(7)
680 a = self.theclass(2002, 3, 2)
681 self.assertEqual(a + day, self.theclass(2002, 3, 3))
682 self.assertEqual(day + a, self.theclass(2002, 3, 3))
683 self.assertEqual(a - day, self.theclass(2002, 3, 1))
684 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
685 self.assertEqual(a + week, self.theclass(2002, 3, 9))
686 self.assertEqual(a - week, self.theclass(2002, 2, 23))
687 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
688 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
689 self.assertEqual((a + week) - a, week)
690 self.assertEqual((a + day) - a, day)
691 self.assertEqual((a - week) - a, -week)
692 self.assertEqual((a - day) - a, -day)
693 self.assertEqual(a - (a + week), -week)
694 self.assertEqual(a - (a + day), -day)
695 self.assertEqual(a - (a - week), week)
696 self.assertEqual(a - (a - day), day)
697
698 # Add/sub ints, longs, floats should be illegal
699 for i in 1, 1L, 1.0:
700 self.assertRaises(TypeError, lambda: a+i)
701 self.assertRaises(TypeError, lambda: a-i)
702 self.assertRaises(TypeError, lambda: i+a)
703 self.assertRaises(TypeError, lambda: i-a)
704
705 # delta - date is senseless.
706 self.assertRaises(TypeError, lambda: day - a)
707 # mixing date and (delta or date) via * or // is senseless
708 self.assertRaises(TypeError, lambda: day * a)
709 self.assertRaises(TypeError, lambda: a * day)
710 self.assertRaises(TypeError, lambda: day // a)
711 self.assertRaises(TypeError, lambda: a // day)
712 self.assertRaises(TypeError, lambda: a * a)
713 self.assertRaises(TypeError, lambda: a // a)
714 # date + date is senseless
715 self.assertRaises(TypeError, lambda: a + a)
716
717 def test_overflow(self):
718 tiny = self.theclass.resolution
719
720 dt = self.theclass.min + tiny
721 dt -= tiny # no problem
722 self.assertRaises(OverflowError, dt.__sub__, tiny)
723 self.assertRaises(OverflowError, dt.__add__, -tiny)
724
725 dt = self.theclass.max - tiny
726 dt += tiny # no problem
727 self.assertRaises(OverflowError, dt.__add__, tiny)
728 self.assertRaises(OverflowError, dt.__sub__, -tiny)
729
730 def test_fromtimestamp(self):
731 import time
732
733 # Try an arbitrary fixed value.
734 year, month, day = 1999, 9, 19
735 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
736 d = self.theclass.fromtimestamp(ts)
737 self.assertEqual(d.year, year)
738 self.assertEqual(d.month, month)
739 self.assertEqual(d.day, day)
740
Tim Peters1b6f7a92004-06-20 02:50:16 +0000741 def test_insane_fromtimestamp(self):
742 # It's possible that some platform maps time_t to double,
743 # and that this test will fail there. This test should
744 # exempt such platforms (provided they return reasonable
745 # results!).
746 for insane in -1e200, 1e200:
747 self.assertRaises(ValueError, self.theclass.fromtimestamp,
748 insane)
749
Tim Peters2a799bf2002-12-16 20:18:38 +0000750 def test_today(self):
751 import time
752
753 # We claim that today() is like fromtimestamp(time.time()), so
754 # prove it.
755 for dummy in range(3):
756 today = self.theclass.today()
757 ts = time.time()
758 todayagain = self.theclass.fromtimestamp(ts)
759 if today == todayagain:
760 break
761 # There are several legit reasons that could fail:
762 # 1. It recently became midnight, between the today() and the
763 # time() calls.
764 # 2. The platform time() has such fine resolution that we'll
765 # never get the same value twice.
766 # 3. The platform time() has poor resolution, and we just
767 # happened to call today() right before a resolution quantum
768 # boundary.
769 # 4. The system clock got fiddled between calls.
770 # In any case, wait a little while and try again.
771 time.sleep(0.1)
772
773 # It worked or it didn't. If it didn't, assume it's reason #2, and
774 # let the test pass if they're within half a second of each other.
775 self.failUnless(today == todayagain or
776 abs(todayagain - today) < timedelta(seconds=0.5))
777
778 def test_weekday(self):
779 for i in range(7):
780 # March 4, 2002 is a Monday
781 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
782 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
783 # January 2, 1956 is a Monday
784 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
785 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
786
787 def test_isocalendar(self):
788 # Check examples from
789 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
790 for i in range(7):
791 d = self.theclass(2003, 12, 22+i)
792 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
793 d = self.theclass(2003, 12, 29) + timedelta(i)
794 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
795 d = self.theclass(2004, 1, 5+i)
796 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
797 d = self.theclass(2009, 12, 21+i)
798 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
799 d = self.theclass(2009, 12, 28) + timedelta(i)
800 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
801 d = self.theclass(2010, 1, 4+i)
802 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
803
804 def test_iso_long_years(self):
805 # Calculate long ISO years and compare to table from
806 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
807 ISO_LONG_YEARS_TABLE = """
808 4 32 60 88
809 9 37 65 93
810 15 43 71 99
811 20 48 76
812 26 54 82
813
814 105 133 161 189
815 111 139 167 195
816 116 144 172
817 122 150 178
818 128 156 184
819
820 201 229 257 285
821 207 235 263 291
822 212 240 268 296
823 218 246 274
824 224 252 280
825
826 303 331 359 387
827 308 336 364 392
828 314 342 370 398
829 320 348 376
830 325 353 381
831 """
832 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
833 iso_long_years.sort()
834 L = []
835 for i in range(400):
836 d = self.theclass(2000+i, 12, 31)
837 d1 = self.theclass(1600+i, 12, 31)
838 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
839 if d.isocalendar()[1] == 53:
840 L.append(i)
841 self.assertEqual(L, iso_long_years)
842
843 def test_isoformat(self):
844 t = self.theclass(2, 3, 2)
845 self.assertEqual(t.isoformat(), "0002-03-02")
846
847 def test_ctime(self):
848 t = self.theclass(2002, 3, 2)
849 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
850
851 def test_strftime(self):
852 t = self.theclass(2005, 3, 2)
853 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000854 self.assertEqual(t.strftime(""), "") # SF bug #761337
Thomas Wouters89f507f2006-12-13 04:49:30 +0000855 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
Tim Peters2a799bf2002-12-16 20:18:38 +0000856
857 self.assertRaises(TypeError, t.strftime) # needs an arg
858 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
859 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
860
861 # A naive object replaces %z and %Z w/ empty strings.
862 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
863
864 def test_resolution_info(self):
865 self.assert_(isinstance(self.theclass.min, self.theclass))
866 self.assert_(isinstance(self.theclass.max, self.theclass))
867 self.assert_(isinstance(self.theclass.resolution, timedelta))
868 self.assert_(self.theclass.max > self.theclass.min)
869
870 def test_extreme_timedelta(self):
871 big = self.theclass.max - self.theclass.min
872 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
873 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
874 # n == 315537897599999999 ~= 2**58.13
875 justasbig = timedelta(0, 0, n)
876 self.assertEqual(big, justasbig)
877 self.assertEqual(self.theclass.min + big, self.theclass.max)
878 self.assertEqual(self.theclass.max - big, self.theclass.min)
879
880 def test_timetuple(self):
881 for i in range(7):
882 # January 2, 1956 is a Monday (0)
883 d = self.theclass(1956, 1, 2+i)
884 t = d.timetuple()
885 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
886 # February 1, 1956 is a Wednesday (2)
887 d = self.theclass(1956, 2, 1+i)
888 t = d.timetuple()
889 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
890 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
891 # of the year.
892 d = self.theclass(1956, 3, 1+i)
893 t = d.timetuple()
894 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
895 self.assertEqual(t.tm_year, 1956)
896 self.assertEqual(t.tm_mon, 3)
897 self.assertEqual(t.tm_mday, 1+i)
898 self.assertEqual(t.tm_hour, 0)
899 self.assertEqual(t.tm_min, 0)
900 self.assertEqual(t.tm_sec, 0)
901 self.assertEqual(t.tm_wday, (3+i)%7)
902 self.assertEqual(t.tm_yday, 61+i)
903 self.assertEqual(t.tm_isdst, -1)
904
905 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000906 args = 6, 7, 23
907 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000908 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000909 green = pickler.dumps(orig, proto)
910 derived = unpickler.loads(green)
911 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000912
913 def test_compare(self):
914 t1 = self.theclass(2, 3, 4)
915 t2 = self.theclass(2, 3, 4)
916 self.failUnless(t1 == t2)
917 self.failUnless(t1 <= t2)
918 self.failUnless(t1 >= t2)
919 self.failUnless(not t1 != t2)
920 self.failUnless(not t1 < t2)
921 self.failUnless(not t1 > t2)
922 self.assertEqual(cmp(t1, t2), 0)
923 self.assertEqual(cmp(t2, t1), 0)
924
925 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
926 t2 = self.theclass(*args) # this is larger than t1
927 self.failUnless(t1 < t2)
928 self.failUnless(t2 > t1)
929 self.failUnless(t1 <= t2)
930 self.failUnless(t2 >= t1)
931 self.failUnless(t1 != t2)
932 self.failUnless(t2 != t1)
933 self.failUnless(not t1 == t2)
934 self.failUnless(not t2 == t1)
935 self.failUnless(not t1 > t2)
936 self.failUnless(not t2 < t1)
937 self.failUnless(not t1 >= t2)
938 self.failUnless(not t2 <= t1)
939 self.assertEqual(cmp(t1, t2), -1)
940 self.assertEqual(cmp(t2, t1), 1)
941
Tim Peters68124bb2003-02-08 03:46:31 +0000942 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000943 self.assertEqual(t1 == badarg, False)
944 self.assertEqual(t1 != badarg, True)
945 self.assertEqual(badarg == t1, False)
946 self.assertEqual(badarg != t1, True)
947
Tim Peters2a799bf2002-12-16 20:18:38 +0000948 self.assertRaises(TypeError, lambda: t1 < badarg)
949 self.assertRaises(TypeError, lambda: t1 > badarg)
950 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000951 self.assertRaises(TypeError, lambda: badarg <= t1)
952 self.assertRaises(TypeError, lambda: badarg < t1)
953 self.assertRaises(TypeError, lambda: badarg > t1)
954 self.assertRaises(TypeError, lambda: badarg >= t1)
955
Tim Peters8d81a012003-01-24 22:36:34 +0000956 def test_mixed_compare(self):
957 our = self.theclass(2000, 4, 5)
Guido van Rossum19960592006-08-24 17:29:38 +0000958
959 # Our class can be compared for equality to other classes
960 self.assertEqual(our == 1, False)
961 self.assertEqual(1 == our, False)
962 self.assertEqual(our != 1, True)
963 self.assertEqual(1 != our, True)
964
965 # But the ordering is undefined
966 self.assertRaises(TypeError, lambda: our < 1)
967 self.assertRaises(TypeError, lambda: 1 < our)
Tim Peters8d81a012003-01-24 22:36:34 +0000968 self.assertRaises(TypeError, cmp, our, 1)
969 self.assertRaises(TypeError, cmp, 1, our)
970
Guido van Rossum19960592006-08-24 17:29:38 +0000971 # Repeat those tests with a different class
Tim Peters8d81a012003-01-24 22:36:34 +0000972
Guido van Rossum19960592006-08-24 17:29:38 +0000973 class SomeClass:
974 pass
975
976 their = SomeClass()
977 self.assertEqual(our == their, False)
978 self.assertEqual(their == our, False)
979 self.assertEqual(our != their, True)
980 self.assertEqual(their != our, True)
981 self.assertRaises(TypeError, lambda: our < their)
982 self.assertRaises(TypeError, lambda: their < our)
Tim Peters8d81a012003-01-24 22:36:34 +0000983 self.assertRaises(TypeError, cmp, our, their)
Guido van Rossum19960592006-08-24 17:29:38 +0000984 self.assertRaises(TypeError, cmp, their, our)
Tim Peters8d81a012003-01-24 22:36:34 +0000985
Guido van Rossum19960592006-08-24 17:29:38 +0000986 # However, if the other class explicitly defines ordering
987 # relative to our class, it is allowed to do so
Tim Peters8d81a012003-01-24 22:36:34 +0000988
Guido van Rossum19960592006-08-24 17:29:38 +0000989 class LargerThanAnything:
990 def __lt__(self, other):
991 return False
992 def __le__(self, other):
993 return isinstance(other, LargerThanAnything)
994 def __eq__(self, other):
995 return isinstance(other, LargerThanAnything)
996 def __ne__(self, other):
997 return not isinstance(other, LargerThanAnything)
998 def __gt__(self, other):
999 return not isinstance(other, LargerThanAnything)
1000 def __ge__(self, other):
1001 return True
1002
1003 their = LargerThanAnything()
1004 self.assertEqual(our == their, False)
1005 self.assertEqual(their == our, False)
1006 self.assertEqual(our != their, True)
1007 self.assertEqual(their != our, True)
1008 self.assertEqual(our < their, True)
1009 self.assertEqual(their < our, False)
1010 self.assertEqual(cmp(our, their), -1)
1011 self.assertEqual(cmp(their, our), 1)
Tim Peters8d81a012003-01-24 22:36:34 +00001012
Tim Peters2a799bf2002-12-16 20:18:38 +00001013 def test_bool(self):
1014 # All dates are considered true.
1015 self.failUnless(self.theclass.min)
1016 self.failUnless(self.theclass.max)
1017
Tim Petersd6844152002-12-22 20:58:42 +00001018 def test_srftime_out_of_range(self):
1019 # For nasty technical reasons, we can't handle years before 1900.
1020 cls = self.theclass
1021 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1022 for y in 1, 49, 51, 99, 100, 1000, 1899:
1023 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +00001024
1025 def test_replace(self):
1026 cls = self.theclass
1027 args = [1, 2, 3]
1028 base = cls(*args)
1029 self.assertEqual(base, base.replace())
1030
1031 i = 0
1032 for name, newval in (("year", 2),
1033 ("month", 3),
1034 ("day", 4)):
1035 newargs = args[:]
1036 newargs[i] = newval
1037 expected = cls(*newargs)
1038 got = base.replace(**{name: newval})
1039 self.assertEqual(expected, got)
1040 i += 1
1041
1042 # Out of bounds.
1043 base = cls(2000, 2, 29)
1044 self.assertRaises(ValueError, base.replace, year=2001)
1045
Tim Petersa98924a2003-05-17 05:55:19 +00001046 def test_subclass_date(self):
1047
1048 class C(self.theclass):
1049 theAnswer = 42
1050
1051 def __new__(cls, *args, **kws):
1052 temp = kws.copy()
1053 extra = temp.pop('extra')
1054 result = self.theclass.__new__(cls, *args, **temp)
1055 result.extra = extra
1056 return result
1057
1058 def newmeth(self, start):
1059 return start + self.year + self.month
1060
1061 args = 2003, 4, 14
1062
1063 dt1 = self.theclass(*args)
1064 dt2 = C(*args, **{'extra': 7})
1065
1066 self.assertEqual(dt2.__class__, C)
1067 self.assertEqual(dt2.theAnswer, 42)
1068 self.assertEqual(dt2.extra, 7)
1069 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1070 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1071
Tim Peters604c0132004-06-07 23:04:33 +00001072 def test_pickling_subclass_date(self):
1073
1074 args = 6, 7, 23
1075 orig = SubclassDate(*args)
1076 for pickler, unpickler, proto in pickle_choices:
1077 green = pickler.dumps(orig, proto)
1078 derived = unpickler.loads(green)
1079 self.assertEqual(orig, derived)
1080
Tim Peters3f606292004-03-21 23:38:41 +00001081 def test_backdoor_resistance(self):
1082 # For fast unpickling, the constructor accepts a pickle string.
1083 # This is a low-overhead backdoor. A user can (by intent or
1084 # mistake) pass a string directly, which (if it's the right length)
1085 # will get treated like a pickle, and bypass the normal sanity
1086 # checks in the constructor. This can create insane objects.
1087 # The constructor doesn't want to burn the time to validate all
1088 # fields, but does check the month field. This stops, e.g.,
1089 # datetime.datetime('1995-03-25') from yielding an insane object.
1090 base = '1995-03-25'
1091 if not issubclass(self.theclass, datetime):
1092 base = base[:4]
1093 for month_byte in '9', chr(0), chr(13), '\xff':
1094 self.assertRaises(TypeError, self.theclass,
1095 base[:2] + month_byte + base[3:])
1096 for ord_byte in range(1, 13):
1097 # This shouldn't blow up because of the month byte alone. If
1098 # the implementation changes to do more-careful checking, it may
1099 # blow up because other fields are insane.
1100 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001101
Tim Peters2a799bf2002-12-16 20:18:38 +00001102#############################################################################
1103# datetime tests
1104
Tim Peters604c0132004-06-07 23:04:33 +00001105class SubclassDatetime(datetime):
1106 sub_var = 1
1107
Tim Peters2a799bf2002-12-16 20:18:38 +00001108class TestDateTime(TestDate):
1109
1110 theclass = datetime
1111
1112 def test_basic_attributes(self):
1113 dt = self.theclass(2002, 3, 1, 12, 0)
1114 self.assertEqual(dt.year, 2002)
1115 self.assertEqual(dt.month, 3)
1116 self.assertEqual(dt.day, 1)
1117 self.assertEqual(dt.hour, 12)
1118 self.assertEqual(dt.minute, 0)
1119 self.assertEqual(dt.second, 0)
1120 self.assertEqual(dt.microsecond, 0)
1121
1122 def test_basic_attributes_nonzero(self):
1123 # Make sure all attributes are non-zero so bugs in
1124 # bit-shifting access show up.
1125 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1126 self.assertEqual(dt.year, 2002)
1127 self.assertEqual(dt.month, 3)
1128 self.assertEqual(dt.day, 1)
1129 self.assertEqual(dt.hour, 12)
1130 self.assertEqual(dt.minute, 59)
1131 self.assertEqual(dt.second, 59)
1132 self.assertEqual(dt.microsecond, 8000)
1133
1134 def test_roundtrip(self):
1135 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1136 self.theclass.now()):
1137 # Verify dt -> string -> datetime identity.
1138 s = repr(dt)
1139 self.failUnless(s.startswith('datetime.'))
1140 s = s[9:]
1141 dt2 = eval(s)
1142 self.assertEqual(dt, dt2)
1143
1144 # Verify identity via reconstructing from pieces.
1145 dt2 = self.theclass(dt.year, dt.month, dt.day,
1146 dt.hour, dt.minute, dt.second,
1147 dt.microsecond)
1148 self.assertEqual(dt, dt2)
1149
1150 def test_isoformat(self):
1151 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1152 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1153 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1154 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1155 # str is ISO format with the separator forced to a blank.
1156 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1157
1158 t = self.theclass(2, 3, 2)
1159 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1160 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1161 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1162 # str is ISO format with the separator forced to a blank.
1163 self.assertEqual(str(t), "0002-03-02 00:00:00")
1164
1165 def test_more_ctime(self):
1166 # Test fields that TestDate doesn't touch.
1167 import time
1168
1169 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1170 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1171 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1172 # out. The difference is that t.ctime() produces " 2" for the day,
1173 # but platform ctime() produces "02" for the day. According to
1174 # C99, t.ctime() is correct here.
1175 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1176
1177 # So test a case where that difference doesn't matter.
1178 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1179 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1180
1181 def test_tz_independent_comparing(self):
1182 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1183 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1184 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1185 self.assertEqual(dt1, dt3)
1186 self.assert_(dt2 > dt3)
1187
1188 # Make sure comparison doesn't forget microseconds, and isn't done
1189 # via comparing a float timestamp (an IEEE double doesn't have enough
1190 # precision to span microsecond resolution across years 1 thru 9999,
1191 # so comparing via timestamp necessarily calls some distinct values
1192 # equal).
1193 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1194 us = timedelta(microseconds=1)
1195 dt2 = dt1 + us
1196 self.assertEqual(dt2 - dt1, us)
1197 self.assert_(dt1 < dt2)
1198
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001199 def test_strftime_with_bad_tzname_replace(self):
1200 # verify ok if tzinfo.tzname().replace() returns a non-string
1201 class MyTzInfo(FixedOffset):
1202 def tzname(self, dt):
1203 class MyStr(str):
1204 def replace(self, *args):
1205 return None
1206 return MyStr('name')
1207 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1208 self.assertRaises(TypeError, t.strftime, '%Z')
1209
Tim Peters2a799bf2002-12-16 20:18:38 +00001210 def test_bad_constructor_arguments(self):
1211 # bad years
1212 self.theclass(MINYEAR, 1, 1) # no exception
1213 self.theclass(MAXYEAR, 1, 1) # no exception
1214 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1215 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1216 # bad months
1217 self.theclass(2000, 1, 1) # no exception
1218 self.theclass(2000, 12, 1) # no exception
1219 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1220 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1221 # bad days
1222 self.theclass(2000, 2, 29) # no exception
1223 self.theclass(2004, 2, 29) # no exception
1224 self.theclass(2400, 2, 29) # no exception
1225 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1226 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1227 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1228 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1229 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1230 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1231 # bad hours
1232 self.theclass(2000, 1, 31, 0) # no exception
1233 self.theclass(2000, 1, 31, 23) # no exception
1234 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1235 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1236 # bad minutes
1237 self.theclass(2000, 1, 31, 23, 0) # no exception
1238 self.theclass(2000, 1, 31, 23, 59) # no exception
1239 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1240 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1241 # bad seconds
1242 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1243 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1244 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1245 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1246 # bad microseconds
1247 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1248 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1249 self.assertRaises(ValueError, self.theclass,
1250 2000, 1, 31, 23, 59, 59, -1)
1251 self.assertRaises(ValueError, self.theclass,
1252 2000, 1, 31, 23, 59, 59,
1253 1000000)
1254
1255 def test_hash_equality(self):
1256 d = self.theclass(2000, 12, 31, 23, 30, 17)
1257 e = self.theclass(2000, 12, 31, 23, 30, 17)
1258 self.assertEqual(d, e)
1259 self.assertEqual(hash(d), hash(e))
1260
1261 dic = {d: 1}
1262 dic[e] = 2
1263 self.assertEqual(len(dic), 1)
1264 self.assertEqual(dic[d], 2)
1265 self.assertEqual(dic[e], 2)
1266
1267 d = self.theclass(2001, 1, 1, 0, 5, 17)
1268 e = self.theclass(2001, 1, 1, 0, 5, 17)
1269 self.assertEqual(d, e)
1270 self.assertEqual(hash(d), hash(e))
1271
1272 dic = {d: 1}
1273 dic[e] = 2
1274 self.assertEqual(len(dic), 1)
1275 self.assertEqual(dic[d], 2)
1276 self.assertEqual(dic[e], 2)
1277
1278 def test_computations(self):
1279 a = self.theclass(2002, 1, 31)
1280 b = self.theclass(1956, 1, 31)
1281 diff = a-b
1282 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1283 self.assertEqual(diff.seconds, 0)
1284 self.assertEqual(diff.microseconds, 0)
1285 a = self.theclass(2002, 3, 2, 17, 6)
1286 millisec = timedelta(0, 0, 1000)
1287 hour = timedelta(0, 3600)
1288 day = timedelta(1)
1289 week = timedelta(7)
1290 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1291 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1292 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1293 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1294 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1295 self.assertEqual(a - hour, a + -hour)
1296 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1297 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1298 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1299 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1300 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1301 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1302 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1303 self.assertEqual((a + week) - a, week)
1304 self.assertEqual((a + day) - a, day)
1305 self.assertEqual((a + hour) - a, hour)
1306 self.assertEqual((a + millisec) - a, millisec)
1307 self.assertEqual((a - week) - a, -week)
1308 self.assertEqual((a - day) - a, -day)
1309 self.assertEqual((a - hour) - a, -hour)
1310 self.assertEqual((a - millisec) - a, -millisec)
1311 self.assertEqual(a - (a + week), -week)
1312 self.assertEqual(a - (a + day), -day)
1313 self.assertEqual(a - (a + hour), -hour)
1314 self.assertEqual(a - (a + millisec), -millisec)
1315 self.assertEqual(a - (a - week), week)
1316 self.assertEqual(a - (a - day), day)
1317 self.assertEqual(a - (a - hour), hour)
1318 self.assertEqual(a - (a - millisec), millisec)
1319 self.assertEqual(a + (week + day + hour + millisec),
1320 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1321 self.assertEqual(a + (week + day + hour + millisec),
1322 (((a + week) + day) + hour) + millisec)
1323 self.assertEqual(a - (week + day + hour + millisec),
1324 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1325 self.assertEqual(a - (week + day + hour + millisec),
1326 (((a - week) - day) - hour) - millisec)
1327 # Add/sub ints, longs, floats should be illegal
1328 for i in 1, 1L, 1.0:
1329 self.assertRaises(TypeError, lambda: a+i)
1330 self.assertRaises(TypeError, lambda: a-i)
1331 self.assertRaises(TypeError, lambda: i+a)
1332 self.assertRaises(TypeError, lambda: i-a)
1333
1334 # delta - datetime is senseless.
1335 self.assertRaises(TypeError, lambda: day - a)
1336 # mixing datetime and (delta or datetime) via * or // is senseless
1337 self.assertRaises(TypeError, lambda: day * a)
1338 self.assertRaises(TypeError, lambda: a * day)
1339 self.assertRaises(TypeError, lambda: day // a)
1340 self.assertRaises(TypeError, lambda: a // day)
1341 self.assertRaises(TypeError, lambda: a * a)
1342 self.assertRaises(TypeError, lambda: a // a)
1343 # datetime + datetime is senseless
1344 self.assertRaises(TypeError, lambda: a + a)
1345
1346 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001347 args = 6, 7, 23, 20, 59, 1, 64**2
1348 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001349 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001350 green = pickler.dumps(orig, proto)
1351 derived = unpickler.loads(green)
1352 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001353
Guido van Rossum275666f2003-02-07 21:49:01 +00001354 def test_more_pickling(self):
1355 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1356 s = pickle.dumps(a)
1357 b = pickle.loads(s)
1358 self.assertEqual(b.year, 2003)
1359 self.assertEqual(b.month, 2)
1360 self.assertEqual(b.day, 7)
1361
Tim Peters604c0132004-06-07 23:04:33 +00001362 def test_pickling_subclass_datetime(self):
1363 args = 6, 7, 23, 20, 59, 1, 64**2
1364 orig = SubclassDatetime(*args)
1365 for pickler, unpickler, proto in pickle_choices:
1366 green = pickler.dumps(orig, proto)
1367 derived = unpickler.loads(green)
1368 self.assertEqual(orig, derived)
1369
Tim Peters2a799bf2002-12-16 20:18:38 +00001370 def test_more_compare(self):
1371 # The test_compare() inherited from TestDate covers the error cases.
1372 # We just want to test lexicographic ordering on the members datetime
1373 # has that date lacks.
1374 args = [2000, 11, 29, 20, 58, 16, 999998]
1375 t1 = self.theclass(*args)
1376 t2 = self.theclass(*args)
1377 self.failUnless(t1 == t2)
1378 self.failUnless(t1 <= t2)
1379 self.failUnless(t1 >= t2)
1380 self.failUnless(not t1 != t2)
1381 self.failUnless(not t1 < t2)
1382 self.failUnless(not t1 > t2)
1383 self.assertEqual(cmp(t1, t2), 0)
1384 self.assertEqual(cmp(t2, t1), 0)
1385
1386 for i in range(len(args)):
1387 newargs = args[:]
1388 newargs[i] = args[i] + 1
1389 t2 = self.theclass(*newargs) # this is larger than t1
1390 self.failUnless(t1 < t2)
1391 self.failUnless(t2 > t1)
1392 self.failUnless(t1 <= t2)
1393 self.failUnless(t2 >= t1)
1394 self.failUnless(t1 != t2)
1395 self.failUnless(t2 != t1)
1396 self.failUnless(not t1 == t2)
1397 self.failUnless(not t2 == t1)
1398 self.failUnless(not t1 > t2)
1399 self.failUnless(not t2 < t1)
1400 self.failUnless(not t1 >= t2)
1401 self.failUnless(not t2 <= t1)
1402 self.assertEqual(cmp(t1, t2), -1)
1403 self.assertEqual(cmp(t2, t1), 1)
1404
1405
1406 # A helper for timestamp constructor tests.
1407 def verify_field_equality(self, expected, got):
1408 self.assertEqual(expected.tm_year, got.year)
1409 self.assertEqual(expected.tm_mon, got.month)
1410 self.assertEqual(expected.tm_mday, got.day)
1411 self.assertEqual(expected.tm_hour, got.hour)
1412 self.assertEqual(expected.tm_min, got.minute)
1413 self.assertEqual(expected.tm_sec, got.second)
1414
1415 def test_fromtimestamp(self):
1416 import time
1417
1418 ts = time.time()
1419 expected = time.localtime(ts)
1420 got = self.theclass.fromtimestamp(ts)
1421 self.verify_field_equality(expected, got)
1422
1423 def test_utcfromtimestamp(self):
1424 import time
1425
1426 ts = time.time()
1427 expected = time.gmtime(ts)
1428 got = self.theclass.utcfromtimestamp(ts)
1429 self.verify_field_equality(expected, got)
1430
Thomas Wouters477c8d52006-05-27 19:21:47 +00001431 def test_microsecond_rounding(self):
1432 # Test whether fromtimestamp "rounds up" floats that are less
1433 # than one microsecond smaller than an integer.
1434 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1435 self.theclass.fromtimestamp(1))
1436
Tim Peters1b6f7a92004-06-20 02:50:16 +00001437 def test_insane_fromtimestamp(self):
1438 # It's possible that some platform maps time_t to double,
1439 # and that this test will fail there. This test should
1440 # exempt such platforms (provided they return reasonable
1441 # results!).
1442 for insane in -1e200, 1e200:
1443 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1444 insane)
1445
1446 def test_insane_utcfromtimestamp(self):
1447 # It's possible that some platform maps time_t to double,
1448 # and that this test will fail there. This test should
1449 # exempt such platforms (provided they return reasonable
1450 # results!).
1451 for insane in -1e200, 1e200:
1452 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1453 insane)
1454
Tim Peters2a799bf2002-12-16 20:18:38 +00001455 def test_utcnow(self):
1456 import time
1457
1458 # Call it a success if utcnow() and utcfromtimestamp() are within
1459 # a second of each other.
1460 tolerance = timedelta(seconds=1)
1461 for dummy in range(3):
1462 from_now = self.theclass.utcnow()
1463 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1464 if abs(from_timestamp - from_now) <= tolerance:
1465 break
1466 # Else try again a few times.
1467 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1468
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001469 def test_strptime(self):
1470 import time
1471
1472 string = '2004-12-01 13:02:47'
1473 format = '%Y-%m-%d %H:%M:%S'
1474 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1475 got = self.theclass.strptime(string, format)
1476 self.assertEqual(expected, got)
1477
Tim Peters2a799bf2002-12-16 20:18:38 +00001478 def test_more_timetuple(self):
1479 # This tests fields beyond those tested by the TestDate.test_timetuple.
1480 t = self.theclass(2004, 12, 31, 6, 22, 33)
1481 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1482 self.assertEqual(t.timetuple(),
1483 (t.year, t.month, t.day,
1484 t.hour, t.minute, t.second,
1485 t.weekday(),
1486 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1487 -1))
1488 tt = t.timetuple()
1489 self.assertEqual(tt.tm_year, t.year)
1490 self.assertEqual(tt.tm_mon, t.month)
1491 self.assertEqual(tt.tm_mday, t.day)
1492 self.assertEqual(tt.tm_hour, t.hour)
1493 self.assertEqual(tt.tm_min, t.minute)
1494 self.assertEqual(tt.tm_sec, t.second)
1495 self.assertEqual(tt.tm_wday, t.weekday())
1496 self.assertEqual(tt.tm_yday, t.toordinal() -
1497 date(t.year, 1, 1).toordinal() + 1)
1498 self.assertEqual(tt.tm_isdst, -1)
1499
1500 def test_more_strftime(self):
1501 # This tests fields beyond those tested by the TestDate.test_strftime.
1502 t = self.theclass(2004, 12, 31, 6, 22, 33)
1503 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1504 "12 31 04 33 22 06 366")
1505
1506 def test_extract(self):
1507 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1508 self.assertEqual(dt.date(), date(2002, 3, 4))
1509 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1510
1511 def test_combine(self):
1512 d = date(2002, 3, 4)
1513 t = time(18, 45, 3, 1234)
1514 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1515 combine = self.theclass.combine
1516 dt = combine(d, t)
1517 self.assertEqual(dt, expected)
1518
1519 dt = combine(time=t, date=d)
1520 self.assertEqual(dt, expected)
1521
1522 self.assertEqual(d, dt.date())
1523 self.assertEqual(t, dt.time())
1524 self.assertEqual(dt, combine(dt.date(), dt.time()))
1525
1526 self.assertRaises(TypeError, combine) # need an arg
1527 self.assertRaises(TypeError, combine, d) # need two args
1528 self.assertRaises(TypeError, combine, t, d) # args reversed
1529 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1530 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1531
Tim Peters12bf3392002-12-24 05:41:27 +00001532 def test_replace(self):
1533 cls = self.theclass
1534 args = [1, 2, 3, 4, 5, 6, 7]
1535 base = cls(*args)
1536 self.assertEqual(base, base.replace())
1537
1538 i = 0
1539 for name, newval in (("year", 2),
1540 ("month", 3),
1541 ("day", 4),
1542 ("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(2000, 2, 29)
1555 self.assertRaises(ValueError, base.replace, year=2001)
1556
Tim Peters80475bb2002-12-25 07:40:55 +00001557 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001558 # Pretty boring! The TZ test is more interesting here. astimezone()
1559 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001560 dt = self.theclass.now()
1561 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001562 self.assertRaises(TypeError, dt.astimezone) # not enough args
1563 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1564 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001565 self.assertRaises(ValueError, dt.astimezone, f) # naive
1566 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001567
Tim Peters52dcce22003-01-23 16:36:11 +00001568 class Bogus(tzinfo):
1569 def utcoffset(self, dt): return None
1570 def dst(self, dt): return timedelta(0)
1571 bog = Bogus()
1572 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1573
1574 class AlsoBogus(tzinfo):
1575 def utcoffset(self, dt): return timedelta(0)
1576 def dst(self, dt): return None
1577 alsobog = AlsoBogus()
1578 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001579
Tim Petersa98924a2003-05-17 05:55:19 +00001580 def test_subclass_datetime(self):
1581
1582 class C(self.theclass):
1583 theAnswer = 42
1584
1585 def __new__(cls, *args, **kws):
1586 temp = kws.copy()
1587 extra = temp.pop('extra')
1588 result = self.theclass.__new__(cls, *args, **temp)
1589 result.extra = extra
1590 return result
1591
1592 def newmeth(self, start):
1593 return start + self.year + self.month + self.second
1594
1595 args = 2003, 4, 14, 12, 13, 41
1596
1597 dt1 = self.theclass(*args)
1598 dt2 = C(*args, **{'extra': 7})
1599
1600 self.assertEqual(dt2.__class__, C)
1601 self.assertEqual(dt2.theAnswer, 42)
1602 self.assertEqual(dt2.extra, 7)
1603 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1604 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1605 dt1.second - 7)
1606
Tim Peters604c0132004-06-07 23:04:33 +00001607class SubclassTime(time):
1608 sub_var = 1
1609
Tim Peters07534a62003-02-07 22:50:28 +00001610class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001611
1612 theclass = time
1613
1614 def test_basic_attributes(self):
1615 t = self.theclass(12, 0)
1616 self.assertEqual(t.hour, 12)
1617 self.assertEqual(t.minute, 0)
1618 self.assertEqual(t.second, 0)
1619 self.assertEqual(t.microsecond, 0)
1620
1621 def test_basic_attributes_nonzero(self):
1622 # Make sure all attributes are non-zero so bugs in
1623 # bit-shifting access show up.
1624 t = self.theclass(12, 59, 59, 8000)
1625 self.assertEqual(t.hour, 12)
1626 self.assertEqual(t.minute, 59)
1627 self.assertEqual(t.second, 59)
1628 self.assertEqual(t.microsecond, 8000)
1629
1630 def test_roundtrip(self):
1631 t = self.theclass(1, 2, 3, 4)
1632
1633 # Verify t -> string -> time identity.
1634 s = repr(t)
1635 self.failUnless(s.startswith('datetime.'))
1636 s = s[9:]
1637 t2 = eval(s)
1638 self.assertEqual(t, t2)
1639
1640 # Verify identity via reconstructing from pieces.
1641 t2 = self.theclass(t.hour, t.minute, t.second,
1642 t.microsecond)
1643 self.assertEqual(t, t2)
1644
1645 def test_comparing(self):
1646 args = [1, 2, 3, 4]
1647 t1 = self.theclass(*args)
1648 t2 = self.theclass(*args)
1649 self.failUnless(t1 == t2)
1650 self.failUnless(t1 <= t2)
1651 self.failUnless(t1 >= t2)
1652 self.failUnless(not t1 != t2)
1653 self.failUnless(not t1 < t2)
1654 self.failUnless(not t1 > t2)
1655 self.assertEqual(cmp(t1, t2), 0)
1656 self.assertEqual(cmp(t2, t1), 0)
1657
1658 for i in range(len(args)):
1659 newargs = args[:]
1660 newargs[i] = args[i] + 1
1661 t2 = self.theclass(*newargs) # this is larger than t1
1662 self.failUnless(t1 < t2)
1663 self.failUnless(t2 > t1)
1664 self.failUnless(t1 <= t2)
1665 self.failUnless(t2 >= t1)
1666 self.failUnless(t1 != t2)
1667 self.failUnless(t2 != t1)
1668 self.failUnless(not t1 == t2)
1669 self.failUnless(not t2 == t1)
1670 self.failUnless(not t1 > t2)
1671 self.failUnless(not t2 < t1)
1672 self.failUnless(not t1 >= t2)
1673 self.failUnless(not t2 <= t1)
1674 self.assertEqual(cmp(t1, t2), -1)
1675 self.assertEqual(cmp(t2, t1), 1)
1676
Tim Peters68124bb2003-02-08 03:46:31 +00001677 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001678 self.assertEqual(t1 == badarg, False)
1679 self.assertEqual(t1 != badarg, True)
1680 self.assertEqual(badarg == t1, False)
1681 self.assertEqual(badarg != t1, True)
1682
Tim Peters2a799bf2002-12-16 20:18:38 +00001683 self.assertRaises(TypeError, lambda: t1 <= badarg)
1684 self.assertRaises(TypeError, lambda: t1 < badarg)
1685 self.assertRaises(TypeError, lambda: t1 > badarg)
1686 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001687 self.assertRaises(TypeError, lambda: badarg <= t1)
1688 self.assertRaises(TypeError, lambda: badarg < t1)
1689 self.assertRaises(TypeError, lambda: badarg > t1)
1690 self.assertRaises(TypeError, lambda: badarg >= t1)
1691
1692 def test_bad_constructor_arguments(self):
1693 # bad hours
1694 self.theclass(0, 0) # no exception
1695 self.theclass(23, 0) # no exception
1696 self.assertRaises(ValueError, self.theclass, -1, 0)
1697 self.assertRaises(ValueError, self.theclass, 24, 0)
1698 # bad minutes
1699 self.theclass(23, 0) # no exception
1700 self.theclass(23, 59) # no exception
1701 self.assertRaises(ValueError, self.theclass, 23, -1)
1702 self.assertRaises(ValueError, self.theclass, 23, 60)
1703 # bad seconds
1704 self.theclass(23, 59, 0) # no exception
1705 self.theclass(23, 59, 59) # no exception
1706 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1707 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1708 # bad microseconds
1709 self.theclass(23, 59, 59, 0) # no exception
1710 self.theclass(23, 59, 59, 999999) # no exception
1711 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1712 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1713
1714 def test_hash_equality(self):
1715 d = self.theclass(23, 30, 17)
1716 e = self.theclass(23, 30, 17)
1717 self.assertEqual(d, e)
1718 self.assertEqual(hash(d), hash(e))
1719
1720 dic = {d: 1}
1721 dic[e] = 2
1722 self.assertEqual(len(dic), 1)
1723 self.assertEqual(dic[d], 2)
1724 self.assertEqual(dic[e], 2)
1725
1726 d = self.theclass(0, 5, 17)
1727 e = self.theclass(0, 5, 17)
1728 self.assertEqual(d, e)
1729 self.assertEqual(hash(d), hash(e))
1730
1731 dic = {d: 1}
1732 dic[e] = 2
1733 self.assertEqual(len(dic), 1)
1734 self.assertEqual(dic[d], 2)
1735 self.assertEqual(dic[e], 2)
1736
1737 def test_isoformat(self):
1738 t = self.theclass(4, 5, 1, 123)
1739 self.assertEqual(t.isoformat(), "04:05:01.000123")
1740 self.assertEqual(t.isoformat(), str(t))
1741
1742 t = self.theclass()
1743 self.assertEqual(t.isoformat(), "00:00:00")
1744 self.assertEqual(t.isoformat(), str(t))
1745
1746 t = self.theclass(microsecond=1)
1747 self.assertEqual(t.isoformat(), "00:00:00.000001")
1748 self.assertEqual(t.isoformat(), str(t))
1749
1750 t = self.theclass(microsecond=10)
1751 self.assertEqual(t.isoformat(), "00:00:00.000010")
1752 self.assertEqual(t.isoformat(), str(t))
1753
1754 t = self.theclass(microsecond=100)
1755 self.assertEqual(t.isoformat(), "00:00:00.000100")
1756 self.assertEqual(t.isoformat(), str(t))
1757
1758 t = self.theclass(microsecond=1000)
1759 self.assertEqual(t.isoformat(), "00:00:00.001000")
1760 self.assertEqual(t.isoformat(), str(t))
1761
1762 t = self.theclass(microsecond=10000)
1763 self.assertEqual(t.isoformat(), "00:00:00.010000")
1764 self.assertEqual(t.isoformat(), str(t))
1765
1766 t = self.theclass(microsecond=100000)
1767 self.assertEqual(t.isoformat(), "00:00:00.100000")
1768 self.assertEqual(t.isoformat(), str(t))
1769
1770 def test_strftime(self):
1771 t = self.theclass(1, 2, 3, 4)
1772 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1773 # A naive object replaces %z and %Z with empty strings.
1774 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1775
1776 def test_str(self):
1777 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1778 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1779 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1780 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1781 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1782
1783 def test_repr(self):
1784 name = 'datetime.' + self.theclass.__name__
1785 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1786 "%s(1, 2, 3, 4)" % name)
1787 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1788 "%s(10, 2, 3, 4000)" % name)
1789 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1790 "%s(0, 2, 3, 400000)" % name)
1791 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1792 "%s(12, 2, 3)" % name)
1793 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1794 "%s(23, 15)" % name)
1795
1796 def test_resolution_info(self):
1797 self.assert_(isinstance(self.theclass.min, self.theclass))
1798 self.assert_(isinstance(self.theclass.max, self.theclass))
1799 self.assert_(isinstance(self.theclass.resolution, timedelta))
1800 self.assert_(self.theclass.max > self.theclass.min)
1801
1802 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001803 args = 20, 59, 16, 64**2
1804 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001805 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001806 green = pickler.dumps(orig, proto)
1807 derived = unpickler.loads(green)
1808 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001809
Tim Peters604c0132004-06-07 23:04:33 +00001810 def test_pickling_subclass_time(self):
1811 args = 20, 59, 16, 64**2
1812 orig = SubclassTime(*args)
1813 for pickler, unpickler, proto in pickle_choices:
1814 green = pickler.dumps(orig, proto)
1815 derived = unpickler.loads(green)
1816 self.assertEqual(orig, derived)
1817
Tim Peters2a799bf2002-12-16 20:18:38 +00001818 def test_bool(self):
1819 cls = self.theclass
1820 self.failUnless(cls(1))
1821 self.failUnless(cls(0, 1))
1822 self.failUnless(cls(0, 0, 1))
1823 self.failUnless(cls(0, 0, 0, 1))
1824 self.failUnless(not cls(0))
1825 self.failUnless(not cls())
1826
Tim Peters12bf3392002-12-24 05:41:27 +00001827 def test_replace(self):
1828 cls = self.theclass
1829 args = [1, 2, 3, 4]
1830 base = cls(*args)
1831 self.assertEqual(base, base.replace())
1832
1833 i = 0
1834 for name, newval in (("hour", 5),
1835 ("minute", 6),
1836 ("second", 7),
1837 ("microsecond", 8)):
1838 newargs = args[:]
1839 newargs[i] = newval
1840 expected = cls(*newargs)
1841 got = base.replace(**{name: newval})
1842 self.assertEqual(expected, got)
1843 i += 1
1844
1845 # Out of bounds.
1846 base = cls(1)
1847 self.assertRaises(ValueError, base.replace, hour=24)
1848 self.assertRaises(ValueError, base.replace, minute=-1)
1849 self.assertRaises(ValueError, base.replace, second=100)
1850 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1851
Tim Petersa98924a2003-05-17 05:55:19 +00001852 def test_subclass_time(self):
1853
1854 class C(self.theclass):
1855 theAnswer = 42
1856
1857 def __new__(cls, *args, **kws):
1858 temp = kws.copy()
1859 extra = temp.pop('extra')
1860 result = self.theclass.__new__(cls, *args, **temp)
1861 result.extra = extra
1862 return result
1863
1864 def newmeth(self, start):
1865 return start + self.hour + self.second
1866
1867 args = 4, 5, 6
1868
1869 dt1 = self.theclass(*args)
1870 dt2 = C(*args, **{'extra': 7})
1871
1872 self.assertEqual(dt2.__class__, C)
1873 self.assertEqual(dt2.theAnswer, 42)
1874 self.assertEqual(dt2.extra, 7)
1875 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1876 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1877
Armin Rigof4afb212005-11-07 07:15:48 +00001878 def test_backdoor_resistance(self):
1879 # see TestDate.test_backdoor_resistance().
1880 base = '2:59.0'
1881 for hour_byte in ' ', '9', chr(24), '\xff':
1882 self.assertRaises(TypeError, self.theclass,
1883 hour_byte + base[1:])
1884
Tim Peters855fe882002-12-22 03:43:39 +00001885# A mixin for classes with a tzinfo= argument. Subclasses must define
1886# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001887# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001888class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001889
Tim Petersbad8ff02002-12-30 20:52:32 +00001890 def test_argument_passing(self):
1891 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001892 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001893 class introspective(tzinfo):
1894 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001895 def utcoffset(self, dt):
1896 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001897 dst = utcoffset
1898
1899 obj = cls(1, 2, 3, tzinfo=introspective())
1900
Tim Peters0bf60bd2003-01-08 20:40:01 +00001901 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001902 self.assertEqual(obj.tzname(), expected)
1903
Tim Peters0bf60bd2003-01-08 20:40:01 +00001904 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001905 self.assertEqual(obj.utcoffset(), expected)
1906 self.assertEqual(obj.dst(), expected)
1907
Tim Peters855fe882002-12-22 03:43:39 +00001908 def test_bad_tzinfo_classes(self):
1909 cls = self.theclass
1910 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001911
Tim Peters855fe882002-12-22 03:43:39 +00001912 class NiceTry(object):
1913 def __init__(self): pass
1914 def utcoffset(self, dt): pass
1915 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1916
1917 class BetterTry(tzinfo):
1918 def __init__(self): pass
1919 def utcoffset(self, dt): pass
1920 b = BetterTry()
1921 t = cls(1, 1, 1, tzinfo=b)
1922 self.failUnless(t.tzinfo is b)
1923
1924 def test_utc_offset_out_of_bounds(self):
1925 class Edgy(tzinfo):
1926 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001927 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001928 def utcoffset(self, dt):
1929 return self.offset
1930
1931 cls = self.theclass
1932 for offset, legit in ((-1440, False),
1933 (-1439, True),
1934 (1439, True),
1935 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001936 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001937 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001938 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001939 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001940 else:
1941 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001942 if legit:
1943 aofs = abs(offset)
1944 h, m = divmod(aofs, 60)
1945 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001946 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001947 t = t.timetz()
1948 self.assertEqual(str(t), "01:02:03" + tag)
1949 else:
1950 self.assertRaises(ValueError, str, t)
1951
1952 def test_tzinfo_classes(self):
1953 cls = self.theclass
1954 class C1(tzinfo):
1955 def utcoffset(self, dt): return None
1956 def dst(self, dt): return None
1957 def tzname(self, dt): return None
1958 for t in (cls(1, 1, 1),
1959 cls(1, 1, 1, tzinfo=None),
1960 cls(1, 1, 1, tzinfo=C1())):
1961 self.failUnless(t.utcoffset() is None)
1962 self.failUnless(t.dst() is None)
1963 self.failUnless(t.tzname() is None)
1964
Tim Peters855fe882002-12-22 03:43:39 +00001965 class C3(tzinfo):
1966 def utcoffset(self, dt): return timedelta(minutes=-1439)
1967 def dst(self, dt): return timedelta(minutes=1439)
1968 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001969 t = cls(1, 1, 1, tzinfo=C3())
1970 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1971 self.assertEqual(t.dst(), timedelta(minutes=1439))
1972 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001973
1974 # Wrong types.
1975 class C4(tzinfo):
1976 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001977 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001978 def tzname(self, dt): return 0
1979 t = cls(1, 1, 1, tzinfo=C4())
1980 self.assertRaises(TypeError, t.utcoffset)
1981 self.assertRaises(TypeError, t.dst)
1982 self.assertRaises(TypeError, t.tzname)
1983
1984 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001985 class C6(tzinfo):
1986 def utcoffset(self, dt): return timedelta(hours=-24)
1987 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001988 t = cls(1, 1, 1, tzinfo=C6())
1989 self.assertRaises(ValueError, t.utcoffset)
1990 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001991
1992 # Not a whole number of minutes.
1993 class C7(tzinfo):
1994 def utcoffset(self, dt): return timedelta(seconds=61)
1995 def dst(self, dt): return timedelta(microseconds=-81)
1996 t = cls(1, 1, 1, tzinfo=C7())
1997 self.assertRaises(ValueError, t.utcoffset)
1998 self.assertRaises(ValueError, t.dst)
1999
Tim Peters4c0db782002-12-26 05:01:19 +00002000 def test_aware_compare(self):
2001 cls = self.theclass
2002
Tim Peters60c76e42002-12-27 00:41:11 +00002003 # Ensure that utcoffset() gets ignored if the comparands have
2004 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002005 class OperandDependentOffset(tzinfo):
2006 def utcoffset(self, t):
2007 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002008 # d0 and d1 equal after adjustment
2009 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002010 else:
Tim Peters397301e2003-01-02 21:28:08 +00002011 # d2 off in the weeds
2012 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002013
2014 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2015 d0 = base.replace(minute=3)
2016 d1 = base.replace(minute=9)
2017 d2 = base.replace(minute=11)
2018 for x in d0, d1, d2:
2019 for y in d0, d1, d2:
2020 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002021 expected = cmp(x.minute, y.minute)
2022 self.assertEqual(got, expected)
2023
2024 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002025 # Note that a time can't actually have an operand-depedent offset,
2026 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2027 # so skip this test for time.
2028 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002029 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2030 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2031 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2032 for x in d0, d1, d2:
2033 for y in d0, d1, d2:
2034 got = cmp(x, y)
2035 if (x is d0 or x is d1) and (y is d0 or y is d1):
2036 expected = 0
2037 elif x is y is d2:
2038 expected = 0
2039 elif x is d2:
2040 expected = -1
2041 else:
2042 assert y is d2
2043 expected = 1
2044 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002045
Tim Peters855fe882002-12-22 03:43:39 +00002046
Tim Peters0bf60bd2003-01-08 20:40:01 +00002047# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002048class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002049 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002050
2051 def test_empty(self):
2052 t = self.theclass()
2053 self.assertEqual(t.hour, 0)
2054 self.assertEqual(t.minute, 0)
2055 self.assertEqual(t.second, 0)
2056 self.assertEqual(t.microsecond, 0)
2057 self.failUnless(t.tzinfo is None)
2058
Tim Peters2a799bf2002-12-16 20:18:38 +00002059 def test_zones(self):
2060 est = FixedOffset(-300, "EST", 1)
2061 utc = FixedOffset(0, "UTC", -2)
2062 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002063 t1 = time( 7, 47, tzinfo=est)
2064 t2 = time(12, 47, tzinfo=utc)
2065 t3 = time(13, 47, tzinfo=met)
2066 t4 = time(microsecond=40)
2067 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002068
2069 self.assertEqual(t1.tzinfo, est)
2070 self.assertEqual(t2.tzinfo, utc)
2071 self.assertEqual(t3.tzinfo, met)
2072 self.failUnless(t4.tzinfo is None)
2073 self.assertEqual(t5.tzinfo, utc)
2074
Tim Peters855fe882002-12-22 03:43:39 +00002075 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2076 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2077 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002078 self.failUnless(t4.utcoffset() is None)
2079 self.assertRaises(TypeError, t1.utcoffset, "no args")
2080
2081 self.assertEqual(t1.tzname(), "EST")
2082 self.assertEqual(t2.tzname(), "UTC")
2083 self.assertEqual(t3.tzname(), "MET")
2084 self.failUnless(t4.tzname() is None)
2085 self.assertRaises(TypeError, t1.tzname, "no args")
2086
Tim Peters855fe882002-12-22 03:43:39 +00002087 self.assertEqual(t1.dst(), timedelta(minutes=1))
2088 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2089 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002090 self.failUnless(t4.dst() is None)
2091 self.assertRaises(TypeError, t1.dst, "no args")
2092
2093 self.assertEqual(hash(t1), hash(t2))
2094 self.assertEqual(hash(t1), hash(t3))
2095 self.assertEqual(hash(t2), hash(t3))
2096
2097 self.assertEqual(t1, t2)
2098 self.assertEqual(t1, t3)
2099 self.assertEqual(t2, t3)
2100 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2101 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2102 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2103
2104 self.assertEqual(str(t1), "07:47:00-05:00")
2105 self.assertEqual(str(t2), "12:47:00+00:00")
2106 self.assertEqual(str(t3), "13:47:00+01:00")
2107 self.assertEqual(str(t4), "00:00:00.000040")
2108 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2109
2110 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2111 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2112 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2113 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2114 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2115
Tim Peters0bf60bd2003-01-08 20:40:01 +00002116 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002117 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2118 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2119 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2120 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2121 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2122
2123 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2124 "07:47:00 %Z=EST %z=-0500")
2125 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2126 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2127
2128 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002129 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002130 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2131 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2132
Tim Petersb92bb712002-12-21 17:44:07 +00002133 # Check that an invalid tzname result raises an exception.
2134 class Badtzname(tzinfo):
2135 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002136 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002137 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2138 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002139
2140 def test_hash_edge_cases(self):
2141 # Offsets that overflow a basic time.
2142 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2143 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2144 self.assertEqual(hash(t1), hash(t2))
2145
2146 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2147 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2148 self.assertEqual(hash(t1), hash(t2))
2149
Tim Peters2a799bf2002-12-16 20:18:38 +00002150 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002151 # Try one without a tzinfo.
2152 args = 20, 59, 16, 64**2
2153 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002154 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002155 green = pickler.dumps(orig, proto)
2156 derived = unpickler.loads(green)
2157 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002158
2159 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002160 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002161 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002162 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002163 green = pickler.dumps(orig, proto)
2164 derived = unpickler.loads(green)
2165 self.assertEqual(orig, derived)
2166 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2167 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2168 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002169
2170 def test_more_bool(self):
2171 # Test cases with non-None tzinfo.
2172 cls = self.theclass
2173
2174 t = cls(0, tzinfo=FixedOffset(-300, ""))
2175 self.failUnless(t)
2176
2177 t = cls(5, tzinfo=FixedOffset(-300, ""))
2178 self.failUnless(t)
2179
2180 t = cls(5, tzinfo=FixedOffset(300, ""))
2181 self.failUnless(not t)
2182
2183 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2184 self.failUnless(not t)
2185
2186 # Mostly ensuring this doesn't overflow internally.
2187 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2188 self.failUnless(t)
2189
2190 # But this should yield a value error -- the utcoffset is bogus.
2191 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2192 self.assertRaises(ValueError, lambda: bool(t))
2193
2194 # Likewise.
2195 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2196 self.assertRaises(ValueError, lambda: bool(t))
2197
Tim Peters12bf3392002-12-24 05:41:27 +00002198 def test_replace(self):
2199 cls = self.theclass
2200 z100 = FixedOffset(100, "+100")
2201 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2202 args = [1, 2, 3, 4, z100]
2203 base = cls(*args)
2204 self.assertEqual(base, base.replace())
2205
2206 i = 0
2207 for name, newval in (("hour", 5),
2208 ("minute", 6),
2209 ("second", 7),
2210 ("microsecond", 8),
2211 ("tzinfo", zm200)):
2212 newargs = args[:]
2213 newargs[i] = newval
2214 expected = cls(*newargs)
2215 got = base.replace(**{name: newval})
2216 self.assertEqual(expected, got)
2217 i += 1
2218
2219 # Ensure we can get rid of a tzinfo.
2220 self.assertEqual(base.tzname(), "+100")
2221 base2 = base.replace(tzinfo=None)
2222 self.failUnless(base2.tzinfo is None)
2223 self.failUnless(base2.tzname() is None)
2224
2225 # Ensure we can add one.
2226 base3 = base2.replace(tzinfo=z100)
2227 self.assertEqual(base, base3)
2228 self.failUnless(base.tzinfo is base3.tzinfo)
2229
2230 # Out of bounds.
2231 base = cls(1)
2232 self.assertRaises(ValueError, base.replace, hour=24)
2233 self.assertRaises(ValueError, base.replace, minute=-1)
2234 self.assertRaises(ValueError, base.replace, second=100)
2235 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2236
Tim Peters60c76e42002-12-27 00:41:11 +00002237 def test_mixed_compare(self):
2238 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002239 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002240 self.assertEqual(t1, t2)
2241 t2 = t2.replace(tzinfo=None)
2242 self.assertEqual(t1, t2)
2243 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2244 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002245 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2246 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002247
Tim Peters0bf60bd2003-01-08 20:40:01 +00002248 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002249 class Varies(tzinfo):
2250 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002251 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002252 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002253 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002254 return self.offset
2255
2256 v = Varies()
2257 t1 = t2.replace(tzinfo=v)
2258 t2 = t2.replace(tzinfo=v)
2259 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2260 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2261 self.assertEqual(t1, t2)
2262
2263 # But if they're not identical, it isn't ignored.
2264 t2 = t2.replace(tzinfo=Varies())
2265 self.failUnless(t1 < t2) # t1's offset counter still going up
2266
Tim Petersa98924a2003-05-17 05:55:19 +00002267 def test_subclass_timetz(self):
2268
2269 class C(self.theclass):
2270 theAnswer = 42
2271
2272 def __new__(cls, *args, **kws):
2273 temp = kws.copy()
2274 extra = temp.pop('extra')
2275 result = self.theclass.__new__(cls, *args, **temp)
2276 result.extra = extra
2277 return result
2278
2279 def newmeth(self, start):
2280 return start + self.hour + self.second
2281
2282 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2283
2284 dt1 = self.theclass(*args)
2285 dt2 = C(*args, **{'extra': 7})
2286
2287 self.assertEqual(dt2.__class__, C)
2288 self.assertEqual(dt2.theAnswer, 42)
2289 self.assertEqual(dt2.extra, 7)
2290 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2291 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2292
Tim Peters4c0db782002-12-26 05:01:19 +00002293
Tim Peters0bf60bd2003-01-08 20:40:01 +00002294# Testing datetime objects with a non-None tzinfo.
2295
Tim Peters855fe882002-12-22 03:43:39 +00002296class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002297 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002298
2299 def test_trivial(self):
2300 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2301 self.assertEqual(dt.year, 1)
2302 self.assertEqual(dt.month, 2)
2303 self.assertEqual(dt.day, 3)
2304 self.assertEqual(dt.hour, 4)
2305 self.assertEqual(dt.minute, 5)
2306 self.assertEqual(dt.second, 6)
2307 self.assertEqual(dt.microsecond, 7)
2308 self.assertEqual(dt.tzinfo, None)
2309
2310 def test_even_more_compare(self):
2311 # The test_compare() and test_more_compare() inherited from TestDate
2312 # and TestDateTime covered non-tzinfo cases.
2313
2314 # Smallest possible after UTC adjustment.
2315 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2316 # Largest possible after UTC adjustment.
2317 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2318 tzinfo=FixedOffset(-1439, ""))
2319
2320 # Make sure those compare correctly, and w/o overflow.
2321 self.failUnless(t1 < t2)
2322 self.failUnless(t1 != t2)
2323 self.failUnless(t2 > t1)
2324
2325 self.failUnless(t1 == t1)
2326 self.failUnless(t2 == t2)
2327
2328 # Equal afer adjustment.
2329 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2330 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2331 self.assertEqual(t1, t2)
2332
2333 # Change t1 not to subtract a minute, and t1 should be larger.
2334 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2335 self.failUnless(t1 > t2)
2336
2337 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2338 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2339 self.failUnless(t1 < t2)
2340
2341 # Back to the original t1, but make seconds resolve it.
2342 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2343 second=1)
2344 self.failUnless(t1 > t2)
2345
2346 # Likewise, but make microseconds resolve it.
2347 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2348 microsecond=1)
2349 self.failUnless(t1 > t2)
2350
2351 # Make t2 naive and it should fail.
2352 t2 = self.theclass.min
2353 self.assertRaises(TypeError, lambda: t1 == t2)
2354 self.assertEqual(t2, t2)
2355
2356 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2357 class Naive(tzinfo):
2358 def utcoffset(self, dt): return None
2359 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2360 self.assertRaises(TypeError, lambda: t1 == t2)
2361 self.assertEqual(t2, t2)
2362
2363 # OTOH, it's OK to compare two of these mixing the two ways of being
2364 # naive.
2365 t1 = self.theclass(5, 6, 7)
2366 self.assertEqual(t1, t2)
2367
2368 # Try a bogus uctoffset.
2369 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002370 def utcoffset(self, dt):
2371 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002372 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2373 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002374 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002375
Tim Peters2a799bf2002-12-16 20:18:38 +00002376 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002377 # Try one without a tzinfo.
2378 args = 6, 7, 23, 20, 59, 1, 64**2
2379 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002380 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002381 green = pickler.dumps(orig, proto)
2382 derived = unpickler.loads(green)
2383 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002384
2385 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002386 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002387 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002388 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002389 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002390 green = pickler.dumps(orig, proto)
2391 derived = unpickler.loads(green)
2392 self.assertEqual(orig, derived)
2393 self.failUnless(isinstance(derived.tzinfo,
2394 PicklableFixedOffset))
2395 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2396 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002397
2398 def test_extreme_hashes(self):
2399 # If an attempt is made to hash these via subtracting the offset
2400 # then hashing a datetime object, OverflowError results. The
2401 # Python implementation used to blow up here.
2402 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2403 hash(t)
2404 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2405 tzinfo=FixedOffset(-1439, ""))
2406 hash(t)
2407
2408 # OTOH, an OOB offset should blow up.
2409 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2410 self.assertRaises(ValueError, hash, t)
2411
2412 def test_zones(self):
2413 est = FixedOffset(-300, "EST")
2414 utc = FixedOffset(0, "UTC")
2415 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002416 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2417 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2418 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002419 self.assertEqual(t1.tzinfo, est)
2420 self.assertEqual(t2.tzinfo, utc)
2421 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002422 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2423 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2424 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002425 self.assertEqual(t1.tzname(), "EST")
2426 self.assertEqual(t2.tzname(), "UTC")
2427 self.assertEqual(t3.tzname(), "MET")
2428 self.assertEqual(hash(t1), hash(t2))
2429 self.assertEqual(hash(t1), hash(t3))
2430 self.assertEqual(hash(t2), hash(t3))
2431 self.assertEqual(t1, t2)
2432 self.assertEqual(t1, t3)
2433 self.assertEqual(t2, t3)
2434 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2435 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2436 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002437 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002438 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2439 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2440 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2441
2442 def test_combine(self):
2443 met = FixedOffset(60, "MET")
2444 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002445 tz = time(18, 45, 3, 1234, tzinfo=met)
2446 dt = datetime.combine(d, tz)
2447 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002448 tzinfo=met))
2449
2450 def test_extract(self):
2451 met = FixedOffset(60, "MET")
2452 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2453 self.assertEqual(dt.date(), date(2002, 3, 4))
2454 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002455 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002456
2457 def test_tz_aware_arithmetic(self):
2458 import random
2459
2460 now = self.theclass.now()
2461 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002462 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002463 nowaware = self.theclass.combine(now.date(), timeaware)
2464 self.failUnless(nowaware.tzinfo is tz55)
2465 self.assertEqual(nowaware.timetz(), timeaware)
2466
2467 # Can't mix aware and non-aware.
2468 self.assertRaises(TypeError, lambda: now - nowaware)
2469 self.assertRaises(TypeError, lambda: nowaware - now)
2470
Tim Peters0bf60bd2003-01-08 20:40:01 +00002471 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002472 self.assertRaises(TypeError, lambda: now + nowaware)
2473 self.assertRaises(TypeError, lambda: nowaware + now)
2474 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2475
2476 # Subtracting should yield 0.
2477 self.assertEqual(now - now, timedelta(0))
2478 self.assertEqual(nowaware - nowaware, timedelta(0))
2479
2480 # Adding a delta should preserve tzinfo.
2481 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2482 nowawareplus = nowaware + delta
2483 self.failUnless(nowaware.tzinfo is tz55)
2484 nowawareplus2 = delta + nowaware
2485 self.failUnless(nowawareplus2.tzinfo is tz55)
2486 self.assertEqual(nowawareplus, nowawareplus2)
2487
2488 # that - delta should be what we started with, and that - what we
2489 # started with should be delta.
2490 diff = nowawareplus - delta
2491 self.failUnless(diff.tzinfo is tz55)
2492 self.assertEqual(nowaware, diff)
2493 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2494 self.assertEqual(nowawareplus - nowaware, delta)
2495
2496 # Make up a random timezone.
2497 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002498 # Attach it to nowawareplus.
2499 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002500 self.failUnless(nowawareplus.tzinfo is tzr)
2501 # Make sure the difference takes the timezone adjustments into account.
2502 got = nowaware - nowawareplus
2503 # Expected: (nowaware base - nowaware offset) -
2504 # (nowawareplus base - nowawareplus offset) =
2505 # (nowaware base - nowawareplus base) +
2506 # (nowawareplus offset - nowaware offset) =
2507 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002508 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002509 self.assertEqual(got, expected)
2510
2511 # Try max possible difference.
2512 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2513 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2514 tzinfo=FixedOffset(-1439, "max"))
2515 maxdiff = max - min
2516 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2517 timedelta(minutes=2*1439))
2518
2519 def test_tzinfo_now(self):
2520 meth = self.theclass.now
2521 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2522 base = meth()
2523 # Try with and without naming the keyword.
2524 off42 = FixedOffset(42, "42")
2525 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002526 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002527 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002528 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002529 # Bad argument with and w/o naming the keyword.
2530 self.assertRaises(TypeError, meth, 16)
2531 self.assertRaises(TypeError, meth, tzinfo=16)
2532 # Bad keyword name.
2533 self.assertRaises(TypeError, meth, tinfo=off42)
2534 # Too many args.
2535 self.assertRaises(TypeError, meth, off42, off42)
2536
Tim Peters10cadce2003-01-23 19:58:02 +00002537 # We don't know which time zone we're in, and don't have a tzinfo
2538 # class to represent it, so seeing whether a tz argument actually
2539 # does a conversion is tricky.
2540 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2541 utc = FixedOffset(0, "utc", 0)
2542 for dummy in range(3):
2543 now = datetime.now(weirdtz)
2544 self.failUnless(now.tzinfo is weirdtz)
2545 utcnow = datetime.utcnow().replace(tzinfo=utc)
2546 now2 = utcnow.astimezone(weirdtz)
2547 if abs(now - now2) < timedelta(seconds=30):
2548 break
2549 # Else the code is broken, or more than 30 seconds passed between
2550 # calls; assuming the latter, just try again.
2551 else:
2552 # Three strikes and we're out.
2553 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2554
Tim Peters2a799bf2002-12-16 20:18:38 +00002555 def test_tzinfo_fromtimestamp(self):
2556 import time
2557 meth = self.theclass.fromtimestamp
2558 ts = time.time()
2559 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2560 base = meth(ts)
2561 # Try with and without naming the keyword.
2562 off42 = FixedOffset(42, "42")
2563 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002564 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002565 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002566 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002567 # Bad argument with and w/o naming the keyword.
2568 self.assertRaises(TypeError, meth, ts, 16)
2569 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2570 # Bad keyword name.
2571 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2572 # Too many args.
2573 self.assertRaises(TypeError, meth, ts, off42, off42)
2574 # Too few args.
2575 self.assertRaises(TypeError, meth)
2576
Tim Peters2a44a8d2003-01-23 20:53:10 +00002577 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002578 timestamp = 1000000000
2579 utcdatetime = datetime.utcfromtimestamp(timestamp)
2580 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2581 # But on some flavor of Mac, it's nowhere near that. So we can't have
2582 # any idea here what time that actually is, we can only test that
2583 # relative changes match.
2584 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2585 tz = FixedOffset(utcoffset, "tz", 0)
2586 expected = utcdatetime + utcoffset
2587 got = datetime.fromtimestamp(timestamp, tz)
2588 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002589
Tim Peters2a799bf2002-12-16 20:18:38 +00002590 def test_tzinfo_utcnow(self):
2591 meth = self.theclass.utcnow
2592 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2593 base = meth()
2594 # Try with and without naming the keyword; for whatever reason,
2595 # utcnow() doesn't accept a tzinfo argument.
2596 off42 = FixedOffset(42, "42")
2597 self.assertRaises(TypeError, meth, off42)
2598 self.assertRaises(TypeError, meth, tzinfo=off42)
2599
2600 def test_tzinfo_utcfromtimestamp(self):
2601 import time
2602 meth = self.theclass.utcfromtimestamp
2603 ts = time.time()
2604 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2605 base = meth(ts)
2606 # Try with and without naming the keyword; for whatever reason,
2607 # utcfromtimestamp() doesn't accept a tzinfo argument.
2608 off42 = FixedOffset(42, "42")
2609 self.assertRaises(TypeError, meth, ts, off42)
2610 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2611
2612 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002613 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002614 # DST flag.
2615 class DST(tzinfo):
2616 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002617 if isinstance(dstvalue, int):
2618 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002619 self.dstvalue = dstvalue
2620 def dst(self, dt):
2621 return self.dstvalue
2622
2623 cls = self.theclass
2624 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2625 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2626 t = d.timetuple()
2627 self.assertEqual(1, t.tm_year)
2628 self.assertEqual(1, t.tm_mon)
2629 self.assertEqual(1, t.tm_mday)
2630 self.assertEqual(10, t.tm_hour)
2631 self.assertEqual(20, t.tm_min)
2632 self.assertEqual(30, t.tm_sec)
2633 self.assertEqual(0, t.tm_wday)
2634 self.assertEqual(1, t.tm_yday)
2635 self.assertEqual(flag, t.tm_isdst)
2636
2637 # dst() returns wrong type.
2638 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2639
2640 # dst() at the edge.
2641 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2642 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2643
2644 # dst() out of range.
2645 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2646 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2647
2648 def test_utctimetuple(self):
2649 class DST(tzinfo):
2650 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002651 if isinstance(dstvalue, int):
2652 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002653 self.dstvalue = dstvalue
2654 def dst(self, dt):
2655 return self.dstvalue
2656
2657 cls = self.theclass
2658 # This can't work: DST didn't implement utcoffset.
2659 self.assertRaises(NotImplementedError,
2660 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2661
2662 class UOFS(DST):
2663 def __init__(self, uofs, dofs=None):
2664 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002665 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002666 def utcoffset(self, dt):
2667 return self.uofs
2668
2669 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2670 # in effect for a UTC time.
2671 for dstvalue in -33, 33, 0, None:
2672 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2673 t = d.utctimetuple()
2674 self.assertEqual(d.year, t.tm_year)
2675 self.assertEqual(d.month, t.tm_mon)
2676 self.assertEqual(d.day, t.tm_mday)
2677 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2678 self.assertEqual(13, t.tm_min)
2679 self.assertEqual(d.second, t.tm_sec)
2680 self.assertEqual(d.weekday(), t.tm_wday)
2681 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2682 t.tm_yday)
2683 self.assertEqual(0, t.tm_isdst)
2684
2685 # At the edges, UTC adjustment can normalize into years out-of-range
2686 # for a datetime object. Ensure that a correct timetuple is
2687 # created anyway.
2688 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2689 # That goes back 1 minute less than a full day.
2690 t = tiny.utctimetuple()
2691 self.assertEqual(t.tm_year, MINYEAR-1)
2692 self.assertEqual(t.tm_mon, 12)
2693 self.assertEqual(t.tm_mday, 31)
2694 self.assertEqual(t.tm_hour, 0)
2695 self.assertEqual(t.tm_min, 1)
2696 self.assertEqual(t.tm_sec, 37)
2697 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2698 self.assertEqual(t.tm_isdst, 0)
2699
2700 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2701 # That goes forward 1 minute less than a full day.
2702 t = huge.utctimetuple()
2703 self.assertEqual(t.tm_year, MAXYEAR+1)
2704 self.assertEqual(t.tm_mon, 1)
2705 self.assertEqual(t.tm_mday, 1)
2706 self.assertEqual(t.tm_hour, 23)
2707 self.assertEqual(t.tm_min, 58)
2708 self.assertEqual(t.tm_sec, 37)
2709 self.assertEqual(t.tm_yday, 1)
2710 self.assertEqual(t.tm_isdst, 0)
2711
2712 def test_tzinfo_isoformat(self):
2713 zero = FixedOffset(0, "+00:00")
2714 plus = FixedOffset(220, "+03:40")
2715 minus = FixedOffset(-231, "-03:51")
2716 unknown = FixedOffset(None, "")
2717
2718 cls = self.theclass
2719 datestr = '0001-02-03'
2720 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002721 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002722 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2723 timestr = '04:05:59' + (us and '.987001' or '')
2724 ofsstr = ofs is not None and d.tzname() or ''
2725 tailstr = timestr + ofsstr
2726 iso = d.isoformat()
2727 self.assertEqual(iso, datestr + 'T' + tailstr)
2728 self.assertEqual(iso, d.isoformat('T'))
2729 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2730 self.assertEqual(str(d), datestr + ' ' + tailstr)
2731
Tim Peters12bf3392002-12-24 05:41:27 +00002732 def test_replace(self):
2733 cls = self.theclass
2734 z100 = FixedOffset(100, "+100")
2735 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2736 args = [1, 2, 3, 4, 5, 6, 7, z100]
2737 base = cls(*args)
2738 self.assertEqual(base, base.replace())
2739
2740 i = 0
2741 for name, newval in (("year", 2),
2742 ("month", 3),
2743 ("day", 4),
2744 ("hour", 5),
2745 ("minute", 6),
2746 ("second", 7),
2747 ("microsecond", 8),
2748 ("tzinfo", zm200)):
2749 newargs = args[:]
2750 newargs[i] = newval
2751 expected = cls(*newargs)
2752 got = base.replace(**{name: newval})
2753 self.assertEqual(expected, got)
2754 i += 1
2755
2756 # Ensure we can get rid of a tzinfo.
2757 self.assertEqual(base.tzname(), "+100")
2758 base2 = base.replace(tzinfo=None)
2759 self.failUnless(base2.tzinfo is None)
2760 self.failUnless(base2.tzname() is None)
2761
2762 # Ensure we can add one.
2763 base3 = base2.replace(tzinfo=z100)
2764 self.assertEqual(base, base3)
2765 self.failUnless(base.tzinfo is base3.tzinfo)
2766
2767 # Out of bounds.
2768 base = cls(2000, 2, 29)
2769 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002770
Tim Peters80475bb2002-12-25 07:40:55 +00002771 def test_more_astimezone(self):
2772 # The inherited test_astimezone covered some trivial and error cases.
2773 fnone = FixedOffset(None, "None")
2774 f44m = FixedOffset(44, "44")
2775 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2776
Tim Peters10cadce2003-01-23 19:58:02 +00002777 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002778 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002779 # Replacing with degenerate tzinfo raises an exception.
2780 self.assertRaises(ValueError, dt.astimezone, fnone)
2781 # Ditto with None tz.
2782 self.assertRaises(TypeError, dt.astimezone, None)
2783 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002784 x = dt.astimezone(dt.tzinfo)
2785 self.failUnless(x.tzinfo is f44m)
2786 self.assertEqual(x.date(), dt.date())
2787 self.assertEqual(x.time(), dt.time())
2788
2789 # Replacing with different tzinfo does adjust.
2790 got = dt.astimezone(fm5h)
2791 self.failUnless(got.tzinfo is fm5h)
2792 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2793 expected = dt - dt.utcoffset() # in effect, convert to UTC
2794 expected += fm5h.utcoffset(dt) # and from there to local time
2795 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2796 self.assertEqual(got.date(), expected.date())
2797 self.assertEqual(got.time(), expected.time())
2798 self.assertEqual(got.timetz(), expected.timetz())
2799 self.failUnless(got.tzinfo is expected.tzinfo)
2800 self.assertEqual(got, expected)
2801
Tim Peters4c0db782002-12-26 05:01:19 +00002802 def test_aware_subtract(self):
2803 cls = self.theclass
2804
Tim Peters60c76e42002-12-27 00:41:11 +00002805 # Ensure that utcoffset() is ignored when the operands have the
2806 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002807 class OperandDependentOffset(tzinfo):
2808 def utcoffset(self, t):
2809 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002810 # d0 and d1 equal after adjustment
2811 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002812 else:
Tim Peters397301e2003-01-02 21:28:08 +00002813 # d2 off in the weeds
2814 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002815
2816 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2817 d0 = base.replace(minute=3)
2818 d1 = base.replace(minute=9)
2819 d2 = base.replace(minute=11)
2820 for x in d0, d1, d2:
2821 for y in d0, d1, d2:
2822 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002823 expected = timedelta(minutes=x.minute - y.minute)
2824 self.assertEqual(got, expected)
2825
2826 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2827 # ignored.
2828 base = cls(8, 9, 10, 11, 12, 13, 14)
2829 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2830 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2831 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2832 for x in d0, d1, d2:
2833 for y in d0, d1, d2:
2834 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002835 if (x is d0 or x is d1) and (y is d0 or y is d1):
2836 expected = timedelta(0)
2837 elif x is y is d2:
2838 expected = timedelta(0)
2839 elif x is d2:
2840 expected = timedelta(minutes=(11-59)-0)
2841 else:
2842 assert y is d2
2843 expected = timedelta(minutes=0-(11-59))
2844 self.assertEqual(got, expected)
2845
Tim Peters60c76e42002-12-27 00:41:11 +00002846 def test_mixed_compare(self):
2847 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002848 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002849 self.assertEqual(t1, t2)
2850 t2 = t2.replace(tzinfo=None)
2851 self.assertEqual(t1, t2)
2852 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2853 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002854 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2855 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002856
Tim Peters0bf60bd2003-01-08 20:40:01 +00002857 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002858 class Varies(tzinfo):
2859 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002860 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002861 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002862 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002863 return self.offset
2864
2865 v = Varies()
2866 t1 = t2.replace(tzinfo=v)
2867 t2 = t2.replace(tzinfo=v)
2868 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2869 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2870 self.assertEqual(t1, t2)
2871
2872 # But if they're not identical, it isn't ignored.
2873 t2 = t2.replace(tzinfo=Varies())
2874 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002875
Tim Petersa98924a2003-05-17 05:55:19 +00002876 def test_subclass_datetimetz(self):
2877
2878 class C(self.theclass):
2879 theAnswer = 42
2880
2881 def __new__(cls, *args, **kws):
2882 temp = kws.copy()
2883 extra = temp.pop('extra')
2884 result = self.theclass.__new__(cls, *args, **temp)
2885 result.extra = extra
2886 return result
2887
2888 def newmeth(self, start):
2889 return start + self.hour + self.year
2890
2891 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2892
2893 dt1 = self.theclass(*args)
2894 dt2 = C(*args, **{'extra': 7})
2895
2896 self.assertEqual(dt2.__class__, C)
2897 self.assertEqual(dt2.theAnswer, 42)
2898 self.assertEqual(dt2.extra, 7)
2899 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2900 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2901
Tim Peters621818b2002-12-29 23:44:49 +00002902# Pain to set up DST-aware tzinfo classes.
2903
2904def first_sunday_on_or_after(dt):
2905 days_to_go = 6 - dt.weekday()
2906 if days_to_go:
2907 dt += timedelta(days_to_go)
2908 return dt
2909
2910ZERO = timedelta(0)
2911HOUR = timedelta(hours=1)
2912DAY = timedelta(days=1)
2913# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2914DSTSTART = datetime(1, 4, 1, 2)
2915# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002916# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2917# being standard time on that day, there is no spelling in local time of
2918# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2919DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002920
2921class USTimeZone(tzinfo):
2922
2923 def __init__(self, hours, reprname, stdname, dstname):
2924 self.stdoffset = timedelta(hours=hours)
2925 self.reprname = reprname
2926 self.stdname = stdname
2927 self.dstname = dstname
2928
2929 def __repr__(self):
2930 return self.reprname
2931
2932 def tzname(self, dt):
2933 if self.dst(dt):
2934 return self.dstname
2935 else:
2936 return self.stdname
2937
2938 def utcoffset(self, dt):
2939 return self.stdoffset + self.dst(dt)
2940
2941 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002942 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002943 # An exception instead may be sensible here, in one or more of
2944 # the cases.
2945 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002946 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002947
2948 # Find first Sunday in April.
2949 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2950 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2951
2952 # Find last Sunday in October.
2953 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2954 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2955
Tim Peters621818b2002-12-29 23:44:49 +00002956 # Can't compare naive to aware objects, so strip the timezone from
2957 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002958 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002959 return HOUR
2960 else:
2961 return ZERO
2962
Tim Peters521fc152002-12-31 17:36:56 +00002963Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2964Central = USTimeZone(-6, "Central", "CST", "CDT")
2965Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2966Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002967utc_real = FixedOffset(0, "UTC", 0)
2968# For better test coverage, we want another flavor of UTC that's west of
2969# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002970utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002971
2972class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002973 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002974 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002975 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002976
Tim Peters0bf60bd2003-01-08 20:40:01 +00002977 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002978
Tim Peters521fc152002-12-31 17:36:56 +00002979 # Check a time that's inside DST.
2980 def checkinside(self, dt, tz, utc, dston, dstoff):
2981 self.assertEqual(dt.dst(), HOUR)
2982
2983 # Conversion to our own timezone is always an identity.
2984 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002985
2986 asutc = dt.astimezone(utc)
2987 there_and_back = asutc.astimezone(tz)
2988
2989 # Conversion to UTC and back isn't always an identity here,
2990 # because there are redundant spellings (in local time) of
2991 # UTC time when DST begins: the clock jumps from 1:59:59
2992 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2993 # make sense then. The classes above treat 2:MM:SS as
2994 # daylight time then (it's "after 2am"), really an alias
2995 # for 1:MM:SS standard time. The latter form is what
2996 # conversion back from UTC produces.
2997 if dt.date() == dston.date() and dt.hour == 2:
2998 # We're in the redundant hour, and coming back from
2999 # UTC gives the 1:MM:SS standard-time spelling.
3000 self.assertEqual(there_and_back + HOUR, dt)
3001 # Although during was considered to be in daylight
3002 # time, there_and_back is not.
3003 self.assertEqual(there_and_back.dst(), ZERO)
3004 # They're the same times in UTC.
3005 self.assertEqual(there_and_back.astimezone(utc),
3006 dt.astimezone(utc))
3007 else:
3008 # We're not in the redundant hour.
3009 self.assertEqual(dt, there_and_back)
3010
Tim Peters327098a2003-01-20 22:54:38 +00003011 # Because we have a redundant spelling when DST begins, there is
3012 # (unforunately) an hour when DST ends that can't be spelled at all in
3013 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3014 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3015 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3016 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3017 # expressed in local time. Nevertheless, we want conversion back
3018 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003019 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003020 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003021 if dt.date() == dstoff.date() and dt.hour == 0:
3022 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003023 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003024 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3025 nexthour_utc += HOUR
3026 nexthour_tz = nexthour_utc.astimezone(tz)
3027 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003028 else:
Tim Peters327098a2003-01-20 22:54:38 +00003029 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003030
3031 # Check a time that's outside DST.
3032 def checkoutside(self, dt, tz, utc):
3033 self.assertEqual(dt.dst(), ZERO)
3034
3035 # Conversion to our own timezone is always an identity.
3036 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003037
3038 # Converting to UTC and back is an identity too.
3039 asutc = dt.astimezone(utc)
3040 there_and_back = asutc.astimezone(tz)
3041 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003042
Tim Peters1024bf82002-12-30 17:09:40 +00003043 def convert_between_tz_and_utc(self, tz, utc):
3044 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003045 # Because 1:MM on the day DST ends is taken as being standard time,
3046 # there is no spelling in tz for the last hour of daylight time.
3047 # For purposes of the test, the last hour of DST is 0:MM, which is
3048 # taken as being daylight time (and 1:MM is taken as being standard
3049 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003050 dstoff = self.dstoff.replace(tzinfo=tz)
3051 for delta in (timedelta(weeks=13),
3052 DAY,
3053 HOUR,
3054 timedelta(minutes=1),
3055 timedelta(microseconds=1)):
3056
Tim Peters521fc152002-12-31 17:36:56 +00003057 self.checkinside(dston, tz, utc, dston, dstoff)
3058 for during in dston + delta, dstoff - delta:
3059 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003060
Tim Peters521fc152002-12-31 17:36:56 +00003061 self.checkoutside(dstoff, tz, utc)
3062 for outside in dston - delta, dstoff + delta:
3063 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003064
Tim Peters621818b2002-12-29 23:44:49 +00003065 def test_easy(self):
3066 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003067 self.convert_between_tz_and_utc(Eastern, utc_real)
3068 self.convert_between_tz_and_utc(Pacific, utc_real)
3069 self.convert_between_tz_and_utc(Eastern, utc_fake)
3070 self.convert_between_tz_and_utc(Pacific, utc_fake)
3071 # The next is really dancing near the edge. It works because
3072 # Pacific and Eastern are far enough apart that their "problem
3073 # hours" don't overlap.
3074 self.convert_between_tz_and_utc(Eastern, Pacific)
3075 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003076 # OTOH, these fail! Don't enable them. The difficulty is that
3077 # the edge case tests assume that every hour is representable in
3078 # the "utc" class. This is always true for a fixed-offset tzinfo
3079 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3080 # For these adjacent DST-aware time zones, the range of time offsets
3081 # tested ends up creating hours in the one that aren't representable
3082 # in the other. For the same reason, we would see failures in the
3083 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3084 # offset deltas in convert_between_tz_and_utc().
3085 #
3086 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3087 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003088
Tim Petersf3615152003-01-01 21:51:37 +00003089 def test_tricky(self):
3090 # 22:00 on day before daylight starts.
3091 fourback = self.dston - timedelta(hours=4)
3092 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003093 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003094 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3095 # 2", we should get the 3 spelling.
3096 # If we plug 22:00 the day before into Eastern, it "looks like std
3097 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3098 # to 22:00 lands on 2:00, which makes no sense in local time (the
3099 # local clock jumps from 1 to 3). The point here is to make sure we
3100 # get the 3 spelling.
3101 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003102 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003103 self.assertEqual(expected, got)
3104
3105 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3106 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003107 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003108 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3109 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3110 # spelling.
3111 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003112 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003113 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003114
Tim Petersadf64202003-01-04 06:03:15 +00003115 # Now on the day DST ends, we want "repeat an hour" behavior.
3116 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3117 # EST 23:MM 0:MM 1:MM 2:MM
3118 # EDT 0:MM 1:MM 2:MM 3:MM
3119 # wall 0:MM 1:MM 1:MM 2:MM against these
3120 for utc in utc_real, utc_fake:
3121 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003122 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003123 # Convert that to UTC.
3124 first_std_hour -= tz.utcoffset(None)
3125 # Adjust for possibly fake UTC.
3126 asutc = first_std_hour + utc.utcoffset(None)
3127 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3128 # tz=Eastern.
3129 asutcbase = asutc.replace(tzinfo=utc)
3130 for tzhour in (0, 1, 1, 2):
3131 expectedbase = self.dstoff.replace(hour=tzhour)
3132 for minute in 0, 30, 59:
3133 expected = expectedbase.replace(minute=minute)
3134 asutc = asutcbase.replace(minute=minute)
3135 astz = asutc.astimezone(tz)
3136 self.assertEqual(astz.replace(tzinfo=None), expected)
3137 asutcbase += HOUR
3138
3139
Tim Peters710fb152003-01-02 19:35:54 +00003140 def test_bogus_dst(self):
3141 class ok(tzinfo):
3142 def utcoffset(self, dt): return HOUR
3143 def dst(self, dt): return HOUR
3144
3145 now = self.theclass.now().replace(tzinfo=utc_real)
3146 # Doesn't blow up.
3147 now.astimezone(ok())
3148
3149 # Does blow up.
3150 class notok(ok):
3151 def dst(self, dt): return None
3152 self.assertRaises(ValueError, now.astimezone, notok())
3153
Tim Peters52dcce22003-01-23 16:36:11 +00003154 def test_fromutc(self):
3155 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3156 now = datetime.utcnow().replace(tzinfo=utc_real)
3157 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3158 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3159 enow = Eastern.fromutc(now) # doesn't blow up
3160 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3161 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3162 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3163
3164 # Always converts UTC to standard time.
3165 class FauxUSTimeZone(USTimeZone):
3166 def fromutc(self, dt):
3167 return dt + self.stdoffset
3168 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3169
3170 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3171 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3172 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3173
3174 # Check around DST start.
3175 start = self.dston.replace(hour=4, tzinfo=Eastern)
3176 fstart = start.replace(tzinfo=FEastern)
3177 for wall in 23, 0, 1, 3, 4, 5:
3178 expected = start.replace(hour=wall)
3179 if wall == 23:
3180 expected -= timedelta(days=1)
3181 got = Eastern.fromutc(start)
3182 self.assertEqual(expected, got)
3183
3184 expected = fstart + FEastern.stdoffset
3185 got = FEastern.fromutc(fstart)
3186 self.assertEqual(expected, got)
3187
3188 # Ensure astimezone() calls fromutc() too.
3189 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3190 self.assertEqual(expected, got)
3191
3192 start += HOUR
3193 fstart += HOUR
3194
3195 # Check around DST end.
3196 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3197 fstart = start.replace(tzinfo=FEastern)
3198 for wall in 0, 1, 1, 2, 3, 4:
3199 expected = start.replace(hour=wall)
3200 got = Eastern.fromutc(start)
3201 self.assertEqual(expected, got)
3202
3203 expected = fstart + FEastern.stdoffset
3204 got = FEastern.fromutc(fstart)
3205 self.assertEqual(expected, got)
3206
3207 # Ensure astimezone() calls fromutc() too.
3208 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3209 self.assertEqual(expected, got)
3210
3211 start += HOUR
3212 fstart += HOUR
3213
Tim Peters710fb152003-01-02 19:35:54 +00003214
Tim Peters528ca532004-09-16 01:30:50 +00003215#############################################################################
3216# oddballs
3217
3218class Oddballs(unittest.TestCase):
3219
3220 def test_bug_1028306(self):
3221 # Trying to compare a date to a datetime should act like a mixed-
3222 # type comparison, despite that datetime is a subclass of date.
3223 as_date = date.today()
3224 as_datetime = datetime.combine(as_date, time())
3225 self.assert_(as_date != as_datetime)
3226 self.assert_(as_datetime != as_date)
3227 self.assert_(not as_date == as_datetime)
3228 self.assert_(not as_datetime == as_date)
3229 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3230 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3231 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3232 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3233 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3234 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3235 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3236 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3237
3238 # Neverthelss, comparison should work with the base-class (date)
3239 # projection if use of a date method is forced.
Guido van Rossum19960592006-08-24 17:29:38 +00003240 self.assertEqual(as_date.__eq__(as_datetime), True)
Tim Peters528ca532004-09-16 01:30:50 +00003241 different_day = (as_date.day + 1) % 20 + 1
Guido van Rossum19960592006-08-24 17:29:38 +00003242 as_different = as_datetime.replace(day= different_day)
3243 self.assertEqual(as_date.__eq__(as_different), False)
Tim Peters528ca532004-09-16 01:30:50 +00003244
3245 # And date should compare with other subclasses of date. If a
3246 # subclass wants to stop this, it's up to the subclass to do so.
3247 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3248 self.assertEqual(as_date, date_sc)
3249 self.assertEqual(date_sc, as_date)
3250
3251 # Ditto for datetimes.
3252 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3253 as_date.day, 0, 0, 0)
3254 self.assertEqual(as_datetime, datetime_sc)
3255 self.assertEqual(datetime_sc, as_datetime)
3256
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003257def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003258 allsuites = [unittest.makeSuite(klass, 'test')
3259 for klass in (TestModule,
3260 TestTZInfo,
3261 TestTimeDelta,
3262 TestDateOnly,
3263 TestDate,
3264 TestDateTime,
3265 TestTime,
3266 TestTimeTZ,
3267 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003268 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003269 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003270 )
3271 ]
3272 return unittest.TestSuite(allsuites)
3273
3274def test_main():
3275 import gc
3276 import sys
3277
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003278 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003279 lastrc = None
3280 while True:
3281 test_support.run_suite(thesuite)
3282 if 1: # change to 0, under a debug build, for some leak detection
3283 break
3284 gc.collect()
3285 if gc.garbage:
3286 raise SystemError("gc.garbage not empty after test run: %r" %
3287 gc.garbage)
3288 if hasattr(sys, 'gettotalrefcount'):
3289 thisrc = sys.gettotalrefcount()
3290 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3291 if lastrc:
3292 print >> sys.stderr, 'delta:', thisrc - lastrc
3293 else:
3294 print >> sys.stderr
3295 lastrc = thisrc
3296
3297if __name__ == "__main__":
3298 test_main()