blob: 4f765c10e776f28058b97b3930f021e6f91d8c96 [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
Tim Peters2a799bf2002-12-16 20:18:38 +0000855
856 self.assertRaises(TypeError, t.strftime) # needs an arg
857 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
858 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
859
860 # A naive object replaces %z and %Z w/ empty strings.
861 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
862
863 def test_resolution_info(self):
864 self.assert_(isinstance(self.theclass.min, self.theclass))
865 self.assert_(isinstance(self.theclass.max, self.theclass))
866 self.assert_(isinstance(self.theclass.resolution, timedelta))
867 self.assert_(self.theclass.max > self.theclass.min)
868
869 def test_extreme_timedelta(self):
870 big = self.theclass.max - self.theclass.min
871 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
872 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
873 # n == 315537897599999999 ~= 2**58.13
874 justasbig = timedelta(0, 0, n)
875 self.assertEqual(big, justasbig)
876 self.assertEqual(self.theclass.min + big, self.theclass.max)
877 self.assertEqual(self.theclass.max - big, self.theclass.min)
878
879 def test_timetuple(self):
880 for i in range(7):
881 # January 2, 1956 is a Monday (0)
882 d = self.theclass(1956, 1, 2+i)
883 t = d.timetuple()
884 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
885 # February 1, 1956 is a Wednesday (2)
886 d = self.theclass(1956, 2, 1+i)
887 t = d.timetuple()
888 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
889 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
890 # of the year.
891 d = self.theclass(1956, 3, 1+i)
892 t = d.timetuple()
893 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
894 self.assertEqual(t.tm_year, 1956)
895 self.assertEqual(t.tm_mon, 3)
896 self.assertEqual(t.tm_mday, 1+i)
897 self.assertEqual(t.tm_hour, 0)
898 self.assertEqual(t.tm_min, 0)
899 self.assertEqual(t.tm_sec, 0)
900 self.assertEqual(t.tm_wday, (3+i)%7)
901 self.assertEqual(t.tm_yday, 61+i)
902 self.assertEqual(t.tm_isdst, -1)
903
904 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000905 args = 6, 7, 23
906 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000907 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000908 green = pickler.dumps(orig, proto)
909 derived = unpickler.loads(green)
910 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000911
912 def test_compare(self):
913 t1 = self.theclass(2, 3, 4)
914 t2 = self.theclass(2, 3, 4)
915 self.failUnless(t1 == t2)
916 self.failUnless(t1 <= t2)
917 self.failUnless(t1 >= t2)
918 self.failUnless(not t1 != t2)
919 self.failUnless(not t1 < t2)
920 self.failUnless(not t1 > t2)
921 self.assertEqual(cmp(t1, t2), 0)
922 self.assertEqual(cmp(t2, t1), 0)
923
924 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
925 t2 = self.theclass(*args) # this is larger than t1
926 self.failUnless(t1 < t2)
927 self.failUnless(t2 > t1)
928 self.failUnless(t1 <= t2)
929 self.failUnless(t2 >= t1)
930 self.failUnless(t1 != t2)
931 self.failUnless(t2 != t1)
932 self.failUnless(not t1 == t2)
933 self.failUnless(not t2 == t1)
934 self.failUnless(not t1 > t2)
935 self.failUnless(not t2 < t1)
936 self.failUnless(not t1 >= t2)
937 self.failUnless(not t2 <= t1)
938 self.assertEqual(cmp(t1, t2), -1)
939 self.assertEqual(cmp(t2, t1), 1)
940
Tim Peters68124bb2003-02-08 03:46:31 +0000941 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000942 self.assertEqual(t1 == badarg, False)
943 self.assertEqual(t1 != badarg, True)
944 self.assertEqual(badarg == t1, False)
945 self.assertEqual(badarg != t1, True)
946
Tim Peters2a799bf2002-12-16 20:18:38 +0000947 self.assertRaises(TypeError, lambda: t1 < badarg)
948 self.assertRaises(TypeError, lambda: t1 > badarg)
949 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000950 self.assertRaises(TypeError, lambda: badarg <= t1)
951 self.assertRaises(TypeError, lambda: badarg < t1)
952 self.assertRaises(TypeError, lambda: badarg > t1)
953 self.assertRaises(TypeError, lambda: badarg >= t1)
954
Tim Peters8d81a012003-01-24 22:36:34 +0000955 def test_mixed_compare(self):
956 our = self.theclass(2000, 4, 5)
957 self.assertRaises(TypeError, cmp, our, 1)
958 self.assertRaises(TypeError, cmp, 1, our)
959
960 class AnotherDateTimeClass(object):
961 def __cmp__(self, other):
962 # Return "equal" so calling this can't be confused with
963 # compare-by-address (which never says "equal" for distinct
964 # objects).
965 return 0
966
967 # This still errors, because date and datetime comparison raise
968 # TypeError instead of NotImplemented when they don't know what to
969 # do, in order to stop comparison from falling back to the default
970 # compare-by-address.
971 their = AnotherDateTimeClass()
972 self.assertRaises(TypeError, cmp, our, their)
973 # Oops: The next stab raises TypeError in the C implementation,
974 # but not in the Python implementation of datetime. The difference
975 # is due to that the Python implementation defines __cmp__ but
976 # the C implementation defines tp_richcompare. This is more pain
977 # to fix than it's worth, so commenting out the test.
978 # self.assertEqual(cmp(their, our), 0)
979
980 # But date and datetime comparison return NotImplemented instead if the
981 # other object has a timetuple attr. This gives the other object a
982 # chance to do the comparison.
983 class Comparable(AnotherDateTimeClass):
984 def timetuple(self):
985 return ()
986
987 their = Comparable()
988 self.assertEqual(cmp(our, their), 0)
989 self.assertEqual(cmp(their, our), 0)
990 self.failUnless(our == their)
991 self.failUnless(their == our)
992
Tim Peters2a799bf2002-12-16 20:18:38 +0000993 def test_bool(self):
994 # All dates are considered true.
995 self.failUnless(self.theclass.min)
996 self.failUnless(self.theclass.max)
997
Tim Petersd6844152002-12-22 20:58:42 +0000998 def test_srftime_out_of_range(self):
999 # For nasty technical reasons, we can't handle years before 1900.
1000 cls = self.theclass
1001 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1002 for y in 1, 49, 51, 99, 100, 1000, 1899:
1003 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +00001004
1005 def test_replace(self):
1006 cls = self.theclass
1007 args = [1, 2, 3]
1008 base = cls(*args)
1009 self.assertEqual(base, base.replace())
1010
1011 i = 0
1012 for name, newval in (("year", 2),
1013 ("month", 3),
1014 ("day", 4)):
1015 newargs = args[:]
1016 newargs[i] = newval
1017 expected = cls(*newargs)
1018 got = base.replace(**{name: newval})
1019 self.assertEqual(expected, got)
1020 i += 1
1021
1022 # Out of bounds.
1023 base = cls(2000, 2, 29)
1024 self.assertRaises(ValueError, base.replace, year=2001)
1025
Tim Petersa98924a2003-05-17 05:55:19 +00001026 def test_subclass_date(self):
1027
1028 class C(self.theclass):
1029 theAnswer = 42
1030
1031 def __new__(cls, *args, **kws):
1032 temp = kws.copy()
1033 extra = temp.pop('extra')
1034 result = self.theclass.__new__(cls, *args, **temp)
1035 result.extra = extra
1036 return result
1037
1038 def newmeth(self, start):
1039 return start + self.year + self.month
1040
1041 args = 2003, 4, 14
1042
1043 dt1 = self.theclass(*args)
1044 dt2 = C(*args, **{'extra': 7})
1045
1046 self.assertEqual(dt2.__class__, C)
1047 self.assertEqual(dt2.theAnswer, 42)
1048 self.assertEqual(dt2.extra, 7)
1049 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1050 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1051
Tim Peters604c0132004-06-07 23:04:33 +00001052 def test_pickling_subclass_date(self):
1053
1054 args = 6, 7, 23
1055 orig = SubclassDate(*args)
1056 for pickler, unpickler, proto in pickle_choices:
1057 green = pickler.dumps(orig, proto)
1058 derived = unpickler.loads(green)
1059 self.assertEqual(orig, derived)
1060
Tim Peters3f606292004-03-21 23:38:41 +00001061 def test_backdoor_resistance(self):
1062 # For fast unpickling, the constructor accepts a pickle string.
1063 # This is a low-overhead backdoor. A user can (by intent or
1064 # mistake) pass a string directly, which (if it's the right length)
1065 # will get treated like a pickle, and bypass the normal sanity
1066 # checks in the constructor. This can create insane objects.
1067 # The constructor doesn't want to burn the time to validate all
1068 # fields, but does check the month field. This stops, e.g.,
1069 # datetime.datetime('1995-03-25') from yielding an insane object.
1070 base = '1995-03-25'
1071 if not issubclass(self.theclass, datetime):
1072 base = base[:4]
1073 for month_byte in '9', chr(0), chr(13), '\xff':
1074 self.assertRaises(TypeError, self.theclass,
1075 base[:2] + month_byte + base[3:])
1076 for ord_byte in range(1, 13):
1077 # This shouldn't blow up because of the month byte alone. If
1078 # the implementation changes to do more-careful checking, it may
1079 # blow up because other fields are insane.
1080 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001081
Tim Peters2a799bf2002-12-16 20:18:38 +00001082#############################################################################
1083# datetime tests
1084
Tim Peters604c0132004-06-07 23:04:33 +00001085class SubclassDatetime(datetime):
1086 sub_var = 1
1087
Tim Peters2a799bf2002-12-16 20:18:38 +00001088class TestDateTime(TestDate):
1089
1090 theclass = datetime
1091
1092 def test_basic_attributes(self):
1093 dt = self.theclass(2002, 3, 1, 12, 0)
1094 self.assertEqual(dt.year, 2002)
1095 self.assertEqual(dt.month, 3)
1096 self.assertEqual(dt.day, 1)
1097 self.assertEqual(dt.hour, 12)
1098 self.assertEqual(dt.minute, 0)
1099 self.assertEqual(dt.second, 0)
1100 self.assertEqual(dt.microsecond, 0)
1101
1102 def test_basic_attributes_nonzero(self):
1103 # Make sure all attributes are non-zero so bugs in
1104 # bit-shifting access show up.
1105 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1106 self.assertEqual(dt.year, 2002)
1107 self.assertEqual(dt.month, 3)
1108 self.assertEqual(dt.day, 1)
1109 self.assertEqual(dt.hour, 12)
1110 self.assertEqual(dt.minute, 59)
1111 self.assertEqual(dt.second, 59)
1112 self.assertEqual(dt.microsecond, 8000)
1113
1114 def test_roundtrip(self):
1115 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1116 self.theclass.now()):
1117 # Verify dt -> string -> datetime identity.
1118 s = repr(dt)
1119 self.failUnless(s.startswith('datetime.'))
1120 s = s[9:]
1121 dt2 = eval(s)
1122 self.assertEqual(dt, dt2)
1123
1124 # Verify identity via reconstructing from pieces.
1125 dt2 = self.theclass(dt.year, dt.month, dt.day,
1126 dt.hour, dt.minute, dt.second,
1127 dt.microsecond)
1128 self.assertEqual(dt, dt2)
1129
1130 def test_isoformat(self):
1131 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1132 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1133 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1134 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1135 # str is ISO format with the separator forced to a blank.
1136 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1137
1138 t = self.theclass(2, 3, 2)
1139 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1140 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1141 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1142 # str is ISO format with the separator forced to a blank.
1143 self.assertEqual(str(t), "0002-03-02 00:00:00")
1144
1145 def test_more_ctime(self):
1146 # Test fields that TestDate doesn't touch.
1147 import time
1148
1149 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1150 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1151 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1152 # out. The difference is that t.ctime() produces " 2" for the day,
1153 # but platform ctime() produces "02" for the day. According to
1154 # C99, t.ctime() is correct here.
1155 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1156
1157 # So test a case where that difference doesn't matter.
1158 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1159 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1160
1161 def test_tz_independent_comparing(self):
1162 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1163 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1164 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1165 self.assertEqual(dt1, dt3)
1166 self.assert_(dt2 > dt3)
1167
1168 # Make sure comparison doesn't forget microseconds, and isn't done
1169 # via comparing a float timestamp (an IEEE double doesn't have enough
1170 # precision to span microsecond resolution across years 1 thru 9999,
1171 # so comparing via timestamp necessarily calls some distinct values
1172 # equal).
1173 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1174 us = timedelta(microseconds=1)
1175 dt2 = dt1 + us
1176 self.assertEqual(dt2 - dt1, us)
1177 self.assert_(dt1 < dt2)
1178
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001179 def test_strftime_with_bad_tzname_replace(self):
1180 # verify ok if tzinfo.tzname().replace() returns a non-string
1181 class MyTzInfo(FixedOffset):
1182 def tzname(self, dt):
1183 class MyStr(str):
1184 def replace(self, *args):
1185 return None
1186 return MyStr('name')
1187 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1188 self.assertRaises(TypeError, t.strftime, '%Z')
1189
Tim Peters2a799bf2002-12-16 20:18:38 +00001190 def test_bad_constructor_arguments(self):
1191 # bad years
1192 self.theclass(MINYEAR, 1, 1) # no exception
1193 self.theclass(MAXYEAR, 1, 1) # no exception
1194 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1195 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1196 # bad months
1197 self.theclass(2000, 1, 1) # no exception
1198 self.theclass(2000, 12, 1) # no exception
1199 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1200 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1201 # bad days
1202 self.theclass(2000, 2, 29) # no exception
1203 self.theclass(2004, 2, 29) # no exception
1204 self.theclass(2400, 2, 29) # no exception
1205 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1206 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1207 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1208 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1209 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1210 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1211 # bad hours
1212 self.theclass(2000, 1, 31, 0) # no exception
1213 self.theclass(2000, 1, 31, 23) # no exception
1214 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1215 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1216 # bad minutes
1217 self.theclass(2000, 1, 31, 23, 0) # no exception
1218 self.theclass(2000, 1, 31, 23, 59) # no exception
1219 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1220 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1221 # bad seconds
1222 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1223 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1224 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1225 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1226 # bad microseconds
1227 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1228 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1229 self.assertRaises(ValueError, self.theclass,
1230 2000, 1, 31, 23, 59, 59, -1)
1231 self.assertRaises(ValueError, self.theclass,
1232 2000, 1, 31, 23, 59, 59,
1233 1000000)
1234
1235 def test_hash_equality(self):
1236 d = self.theclass(2000, 12, 31, 23, 30, 17)
1237 e = self.theclass(2000, 12, 31, 23, 30, 17)
1238 self.assertEqual(d, e)
1239 self.assertEqual(hash(d), hash(e))
1240
1241 dic = {d: 1}
1242 dic[e] = 2
1243 self.assertEqual(len(dic), 1)
1244 self.assertEqual(dic[d], 2)
1245 self.assertEqual(dic[e], 2)
1246
1247 d = self.theclass(2001, 1, 1, 0, 5, 17)
1248 e = self.theclass(2001, 1, 1, 0, 5, 17)
1249 self.assertEqual(d, e)
1250 self.assertEqual(hash(d), hash(e))
1251
1252 dic = {d: 1}
1253 dic[e] = 2
1254 self.assertEqual(len(dic), 1)
1255 self.assertEqual(dic[d], 2)
1256 self.assertEqual(dic[e], 2)
1257
1258 def test_computations(self):
1259 a = self.theclass(2002, 1, 31)
1260 b = self.theclass(1956, 1, 31)
1261 diff = a-b
1262 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1263 self.assertEqual(diff.seconds, 0)
1264 self.assertEqual(diff.microseconds, 0)
1265 a = self.theclass(2002, 3, 2, 17, 6)
1266 millisec = timedelta(0, 0, 1000)
1267 hour = timedelta(0, 3600)
1268 day = timedelta(1)
1269 week = timedelta(7)
1270 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1271 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1272 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1273 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1274 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1275 self.assertEqual(a - hour, a + -hour)
1276 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1277 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1278 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1279 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1280 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1281 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1282 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1283 self.assertEqual((a + week) - a, week)
1284 self.assertEqual((a + day) - a, day)
1285 self.assertEqual((a + hour) - a, hour)
1286 self.assertEqual((a + millisec) - a, millisec)
1287 self.assertEqual((a - week) - a, -week)
1288 self.assertEqual((a - day) - a, -day)
1289 self.assertEqual((a - hour) - a, -hour)
1290 self.assertEqual((a - millisec) - a, -millisec)
1291 self.assertEqual(a - (a + week), -week)
1292 self.assertEqual(a - (a + day), -day)
1293 self.assertEqual(a - (a + hour), -hour)
1294 self.assertEqual(a - (a + millisec), -millisec)
1295 self.assertEqual(a - (a - week), week)
1296 self.assertEqual(a - (a - day), day)
1297 self.assertEqual(a - (a - hour), hour)
1298 self.assertEqual(a - (a - millisec), millisec)
1299 self.assertEqual(a + (week + day + hour + millisec),
1300 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1301 self.assertEqual(a + (week + day + hour + millisec),
1302 (((a + week) + day) + hour) + millisec)
1303 self.assertEqual(a - (week + day + hour + millisec),
1304 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1305 self.assertEqual(a - (week + day + hour + millisec),
1306 (((a - week) - day) - hour) - millisec)
1307 # Add/sub ints, longs, floats should be illegal
1308 for i in 1, 1L, 1.0:
1309 self.assertRaises(TypeError, lambda: a+i)
1310 self.assertRaises(TypeError, lambda: a-i)
1311 self.assertRaises(TypeError, lambda: i+a)
1312 self.assertRaises(TypeError, lambda: i-a)
1313
1314 # delta - datetime is senseless.
1315 self.assertRaises(TypeError, lambda: day - a)
1316 # mixing datetime and (delta or datetime) via * or // is senseless
1317 self.assertRaises(TypeError, lambda: day * a)
1318 self.assertRaises(TypeError, lambda: a * day)
1319 self.assertRaises(TypeError, lambda: day // a)
1320 self.assertRaises(TypeError, lambda: a // day)
1321 self.assertRaises(TypeError, lambda: a * a)
1322 self.assertRaises(TypeError, lambda: a // a)
1323 # datetime + datetime is senseless
1324 self.assertRaises(TypeError, lambda: a + a)
1325
1326 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001327 args = 6, 7, 23, 20, 59, 1, 64**2
1328 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001329 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001330 green = pickler.dumps(orig, proto)
1331 derived = unpickler.loads(green)
1332 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001333
Guido van Rossum275666f2003-02-07 21:49:01 +00001334 def test_more_pickling(self):
1335 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1336 s = pickle.dumps(a)
1337 b = pickle.loads(s)
1338 self.assertEqual(b.year, 2003)
1339 self.assertEqual(b.month, 2)
1340 self.assertEqual(b.day, 7)
1341
Tim Peters604c0132004-06-07 23:04:33 +00001342 def test_pickling_subclass_datetime(self):
1343 args = 6, 7, 23, 20, 59, 1, 64**2
1344 orig = SubclassDatetime(*args)
1345 for pickler, unpickler, proto in pickle_choices:
1346 green = pickler.dumps(orig, proto)
1347 derived = unpickler.loads(green)
1348 self.assertEqual(orig, derived)
1349
Tim Peters2a799bf2002-12-16 20:18:38 +00001350 def test_more_compare(self):
1351 # The test_compare() inherited from TestDate covers the error cases.
1352 # We just want to test lexicographic ordering on the members datetime
1353 # has that date lacks.
1354 args = [2000, 11, 29, 20, 58, 16, 999998]
1355 t1 = self.theclass(*args)
1356 t2 = self.theclass(*args)
1357 self.failUnless(t1 == t2)
1358 self.failUnless(t1 <= t2)
1359 self.failUnless(t1 >= t2)
1360 self.failUnless(not t1 != t2)
1361 self.failUnless(not t1 < t2)
1362 self.failUnless(not t1 > t2)
1363 self.assertEqual(cmp(t1, t2), 0)
1364 self.assertEqual(cmp(t2, t1), 0)
1365
1366 for i in range(len(args)):
1367 newargs = args[:]
1368 newargs[i] = args[i] + 1
1369 t2 = self.theclass(*newargs) # this is larger than t1
1370 self.failUnless(t1 < t2)
1371 self.failUnless(t2 > t1)
1372 self.failUnless(t1 <= t2)
1373 self.failUnless(t2 >= t1)
1374 self.failUnless(t1 != t2)
1375 self.failUnless(t2 != t1)
1376 self.failUnless(not t1 == t2)
1377 self.failUnless(not t2 == t1)
1378 self.failUnless(not t1 > t2)
1379 self.failUnless(not t2 < t1)
1380 self.failUnless(not t1 >= t2)
1381 self.failUnless(not t2 <= t1)
1382 self.assertEqual(cmp(t1, t2), -1)
1383 self.assertEqual(cmp(t2, t1), 1)
1384
1385
1386 # A helper for timestamp constructor tests.
1387 def verify_field_equality(self, expected, got):
1388 self.assertEqual(expected.tm_year, got.year)
1389 self.assertEqual(expected.tm_mon, got.month)
1390 self.assertEqual(expected.tm_mday, got.day)
1391 self.assertEqual(expected.tm_hour, got.hour)
1392 self.assertEqual(expected.tm_min, got.minute)
1393 self.assertEqual(expected.tm_sec, got.second)
1394
1395 def test_fromtimestamp(self):
1396 import time
1397
1398 ts = time.time()
1399 expected = time.localtime(ts)
1400 got = self.theclass.fromtimestamp(ts)
1401 self.verify_field_equality(expected, got)
1402
1403 def test_utcfromtimestamp(self):
1404 import time
1405
1406 ts = time.time()
1407 expected = time.gmtime(ts)
1408 got = self.theclass.utcfromtimestamp(ts)
1409 self.verify_field_equality(expected, got)
1410
Thomas Wouters477c8d52006-05-27 19:21:47 +00001411 def test_microsecond_rounding(self):
1412 # Test whether fromtimestamp "rounds up" floats that are less
1413 # than one microsecond smaller than an integer.
1414 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1415 self.theclass.fromtimestamp(1))
1416
Tim Peters1b6f7a92004-06-20 02:50:16 +00001417 def test_insane_fromtimestamp(self):
1418 # It's possible that some platform maps time_t to double,
1419 # and that this test will fail there. This test should
1420 # exempt such platforms (provided they return reasonable
1421 # results!).
1422 for insane in -1e200, 1e200:
1423 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1424 insane)
1425
1426 def test_insane_utcfromtimestamp(self):
1427 # It's possible that some platform maps time_t to double,
1428 # and that this test will fail there. This test should
1429 # exempt such platforms (provided they return reasonable
1430 # results!).
1431 for insane in -1e200, 1e200:
1432 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1433 insane)
1434
Tim Peters2a799bf2002-12-16 20:18:38 +00001435 def test_utcnow(self):
1436 import time
1437
1438 # Call it a success if utcnow() and utcfromtimestamp() are within
1439 # a second of each other.
1440 tolerance = timedelta(seconds=1)
1441 for dummy in range(3):
1442 from_now = self.theclass.utcnow()
1443 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1444 if abs(from_timestamp - from_now) <= tolerance:
1445 break
1446 # Else try again a few times.
1447 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1448
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001449 def test_strptime(self):
1450 import time
1451
1452 string = '2004-12-01 13:02:47'
1453 format = '%Y-%m-%d %H:%M:%S'
1454 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1455 got = self.theclass.strptime(string, format)
1456 self.assertEqual(expected, got)
1457
Tim Peters2a799bf2002-12-16 20:18:38 +00001458 def test_more_timetuple(self):
1459 # This tests fields beyond those tested by the TestDate.test_timetuple.
1460 t = self.theclass(2004, 12, 31, 6, 22, 33)
1461 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1462 self.assertEqual(t.timetuple(),
1463 (t.year, t.month, t.day,
1464 t.hour, t.minute, t.second,
1465 t.weekday(),
1466 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1467 -1))
1468 tt = t.timetuple()
1469 self.assertEqual(tt.tm_year, t.year)
1470 self.assertEqual(tt.tm_mon, t.month)
1471 self.assertEqual(tt.tm_mday, t.day)
1472 self.assertEqual(tt.tm_hour, t.hour)
1473 self.assertEqual(tt.tm_min, t.minute)
1474 self.assertEqual(tt.tm_sec, t.second)
1475 self.assertEqual(tt.tm_wday, t.weekday())
1476 self.assertEqual(tt.tm_yday, t.toordinal() -
1477 date(t.year, 1, 1).toordinal() + 1)
1478 self.assertEqual(tt.tm_isdst, -1)
1479
1480 def test_more_strftime(self):
1481 # This tests fields beyond those tested by the TestDate.test_strftime.
1482 t = self.theclass(2004, 12, 31, 6, 22, 33)
1483 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1484 "12 31 04 33 22 06 366")
1485
1486 def test_extract(self):
1487 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1488 self.assertEqual(dt.date(), date(2002, 3, 4))
1489 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1490
1491 def test_combine(self):
1492 d = date(2002, 3, 4)
1493 t = time(18, 45, 3, 1234)
1494 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1495 combine = self.theclass.combine
1496 dt = combine(d, t)
1497 self.assertEqual(dt, expected)
1498
1499 dt = combine(time=t, date=d)
1500 self.assertEqual(dt, expected)
1501
1502 self.assertEqual(d, dt.date())
1503 self.assertEqual(t, dt.time())
1504 self.assertEqual(dt, combine(dt.date(), dt.time()))
1505
1506 self.assertRaises(TypeError, combine) # need an arg
1507 self.assertRaises(TypeError, combine, d) # need two args
1508 self.assertRaises(TypeError, combine, t, d) # args reversed
1509 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1510 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1511
Tim Peters12bf3392002-12-24 05:41:27 +00001512 def test_replace(self):
1513 cls = self.theclass
1514 args = [1, 2, 3, 4, 5, 6, 7]
1515 base = cls(*args)
1516 self.assertEqual(base, base.replace())
1517
1518 i = 0
1519 for name, newval in (("year", 2),
1520 ("month", 3),
1521 ("day", 4),
1522 ("hour", 5),
1523 ("minute", 6),
1524 ("second", 7),
1525 ("microsecond", 8)):
1526 newargs = args[:]
1527 newargs[i] = newval
1528 expected = cls(*newargs)
1529 got = base.replace(**{name: newval})
1530 self.assertEqual(expected, got)
1531 i += 1
1532
1533 # Out of bounds.
1534 base = cls(2000, 2, 29)
1535 self.assertRaises(ValueError, base.replace, year=2001)
1536
Tim Peters80475bb2002-12-25 07:40:55 +00001537 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001538 # Pretty boring! The TZ test is more interesting here. astimezone()
1539 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001540 dt = self.theclass.now()
1541 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001542 self.assertRaises(TypeError, dt.astimezone) # not enough args
1543 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1544 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001545 self.assertRaises(ValueError, dt.astimezone, f) # naive
1546 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001547
Tim Peters52dcce22003-01-23 16:36:11 +00001548 class Bogus(tzinfo):
1549 def utcoffset(self, dt): return None
1550 def dst(self, dt): return timedelta(0)
1551 bog = Bogus()
1552 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1553
1554 class AlsoBogus(tzinfo):
1555 def utcoffset(self, dt): return timedelta(0)
1556 def dst(self, dt): return None
1557 alsobog = AlsoBogus()
1558 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001559
Tim Petersa98924a2003-05-17 05:55:19 +00001560 def test_subclass_datetime(self):
1561
1562 class C(self.theclass):
1563 theAnswer = 42
1564
1565 def __new__(cls, *args, **kws):
1566 temp = kws.copy()
1567 extra = temp.pop('extra')
1568 result = self.theclass.__new__(cls, *args, **temp)
1569 result.extra = extra
1570 return result
1571
1572 def newmeth(self, start):
1573 return start + self.year + self.month + self.second
1574
1575 args = 2003, 4, 14, 12, 13, 41
1576
1577 dt1 = self.theclass(*args)
1578 dt2 = C(*args, **{'extra': 7})
1579
1580 self.assertEqual(dt2.__class__, C)
1581 self.assertEqual(dt2.theAnswer, 42)
1582 self.assertEqual(dt2.extra, 7)
1583 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1584 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1585 dt1.second - 7)
1586
Tim Peters604c0132004-06-07 23:04:33 +00001587class SubclassTime(time):
1588 sub_var = 1
1589
Tim Peters07534a62003-02-07 22:50:28 +00001590class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001591
1592 theclass = time
1593
1594 def test_basic_attributes(self):
1595 t = self.theclass(12, 0)
1596 self.assertEqual(t.hour, 12)
1597 self.assertEqual(t.minute, 0)
1598 self.assertEqual(t.second, 0)
1599 self.assertEqual(t.microsecond, 0)
1600
1601 def test_basic_attributes_nonzero(self):
1602 # Make sure all attributes are non-zero so bugs in
1603 # bit-shifting access show up.
1604 t = self.theclass(12, 59, 59, 8000)
1605 self.assertEqual(t.hour, 12)
1606 self.assertEqual(t.minute, 59)
1607 self.assertEqual(t.second, 59)
1608 self.assertEqual(t.microsecond, 8000)
1609
1610 def test_roundtrip(self):
1611 t = self.theclass(1, 2, 3, 4)
1612
1613 # Verify t -> string -> time identity.
1614 s = repr(t)
1615 self.failUnless(s.startswith('datetime.'))
1616 s = s[9:]
1617 t2 = eval(s)
1618 self.assertEqual(t, t2)
1619
1620 # Verify identity via reconstructing from pieces.
1621 t2 = self.theclass(t.hour, t.minute, t.second,
1622 t.microsecond)
1623 self.assertEqual(t, t2)
1624
1625 def test_comparing(self):
1626 args = [1, 2, 3, 4]
1627 t1 = self.theclass(*args)
1628 t2 = self.theclass(*args)
1629 self.failUnless(t1 == t2)
1630 self.failUnless(t1 <= t2)
1631 self.failUnless(t1 >= t2)
1632 self.failUnless(not t1 != t2)
1633 self.failUnless(not t1 < t2)
1634 self.failUnless(not t1 > t2)
1635 self.assertEqual(cmp(t1, t2), 0)
1636 self.assertEqual(cmp(t2, t1), 0)
1637
1638 for i in range(len(args)):
1639 newargs = args[:]
1640 newargs[i] = args[i] + 1
1641 t2 = self.theclass(*newargs) # this is larger than t1
1642 self.failUnless(t1 < t2)
1643 self.failUnless(t2 > t1)
1644 self.failUnless(t1 <= t2)
1645 self.failUnless(t2 >= t1)
1646 self.failUnless(t1 != t2)
1647 self.failUnless(t2 != t1)
1648 self.failUnless(not t1 == t2)
1649 self.failUnless(not t2 == t1)
1650 self.failUnless(not t1 > t2)
1651 self.failUnless(not t2 < t1)
1652 self.failUnless(not t1 >= t2)
1653 self.failUnless(not t2 <= t1)
1654 self.assertEqual(cmp(t1, t2), -1)
1655 self.assertEqual(cmp(t2, t1), 1)
1656
Tim Peters68124bb2003-02-08 03:46:31 +00001657 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001658 self.assertEqual(t1 == badarg, False)
1659 self.assertEqual(t1 != badarg, True)
1660 self.assertEqual(badarg == t1, False)
1661 self.assertEqual(badarg != t1, True)
1662
Tim Peters2a799bf2002-12-16 20:18:38 +00001663 self.assertRaises(TypeError, lambda: t1 <= badarg)
1664 self.assertRaises(TypeError, lambda: t1 < badarg)
1665 self.assertRaises(TypeError, lambda: t1 > badarg)
1666 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001667 self.assertRaises(TypeError, lambda: badarg <= t1)
1668 self.assertRaises(TypeError, lambda: badarg < t1)
1669 self.assertRaises(TypeError, lambda: badarg > t1)
1670 self.assertRaises(TypeError, lambda: badarg >= t1)
1671
1672 def test_bad_constructor_arguments(self):
1673 # bad hours
1674 self.theclass(0, 0) # no exception
1675 self.theclass(23, 0) # no exception
1676 self.assertRaises(ValueError, self.theclass, -1, 0)
1677 self.assertRaises(ValueError, self.theclass, 24, 0)
1678 # bad minutes
1679 self.theclass(23, 0) # no exception
1680 self.theclass(23, 59) # no exception
1681 self.assertRaises(ValueError, self.theclass, 23, -1)
1682 self.assertRaises(ValueError, self.theclass, 23, 60)
1683 # bad seconds
1684 self.theclass(23, 59, 0) # no exception
1685 self.theclass(23, 59, 59) # no exception
1686 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1687 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1688 # bad microseconds
1689 self.theclass(23, 59, 59, 0) # no exception
1690 self.theclass(23, 59, 59, 999999) # no exception
1691 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1692 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1693
1694 def test_hash_equality(self):
1695 d = self.theclass(23, 30, 17)
1696 e = self.theclass(23, 30, 17)
1697 self.assertEqual(d, e)
1698 self.assertEqual(hash(d), hash(e))
1699
1700 dic = {d: 1}
1701 dic[e] = 2
1702 self.assertEqual(len(dic), 1)
1703 self.assertEqual(dic[d], 2)
1704 self.assertEqual(dic[e], 2)
1705
1706 d = self.theclass(0, 5, 17)
1707 e = self.theclass(0, 5, 17)
1708 self.assertEqual(d, e)
1709 self.assertEqual(hash(d), hash(e))
1710
1711 dic = {d: 1}
1712 dic[e] = 2
1713 self.assertEqual(len(dic), 1)
1714 self.assertEqual(dic[d], 2)
1715 self.assertEqual(dic[e], 2)
1716
1717 def test_isoformat(self):
1718 t = self.theclass(4, 5, 1, 123)
1719 self.assertEqual(t.isoformat(), "04:05:01.000123")
1720 self.assertEqual(t.isoformat(), str(t))
1721
1722 t = self.theclass()
1723 self.assertEqual(t.isoformat(), "00:00:00")
1724 self.assertEqual(t.isoformat(), str(t))
1725
1726 t = self.theclass(microsecond=1)
1727 self.assertEqual(t.isoformat(), "00:00:00.000001")
1728 self.assertEqual(t.isoformat(), str(t))
1729
1730 t = self.theclass(microsecond=10)
1731 self.assertEqual(t.isoformat(), "00:00:00.000010")
1732 self.assertEqual(t.isoformat(), str(t))
1733
1734 t = self.theclass(microsecond=100)
1735 self.assertEqual(t.isoformat(), "00:00:00.000100")
1736 self.assertEqual(t.isoformat(), str(t))
1737
1738 t = self.theclass(microsecond=1000)
1739 self.assertEqual(t.isoformat(), "00:00:00.001000")
1740 self.assertEqual(t.isoformat(), str(t))
1741
1742 t = self.theclass(microsecond=10000)
1743 self.assertEqual(t.isoformat(), "00:00:00.010000")
1744 self.assertEqual(t.isoformat(), str(t))
1745
1746 t = self.theclass(microsecond=100000)
1747 self.assertEqual(t.isoformat(), "00:00:00.100000")
1748 self.assertEqual(t.isoformat(), str(t))
1749
1750 def test_strftime(self):
1751 t = self.theclass(1, 2, 3, 4)
1752 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1753 # A naive object replaces %z and %Z with empty strings.
1754 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1755
1756 def test_str(self):
1757 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1758 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1759 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1760 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1761 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1762
1763 def test_repr(self):
1764 name = 'datetime.' + self.theclass.__name__
1765 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1766 "%s(1, 2, 3, 4)" % name)
1767 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1768 "%s(10, 2, 3, 4000)" % name)
1769 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1770 "%s(0, 2, 3, 400000)" % name)
1771 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1772 "%s(12, 2, 3)" % name)
1773 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1774 "%s(23, 15)" % name)
1775
1776 def test_resolution_info(self):
1777 self.assert_(isinstance(self.theclass.min, self.theclass))
1778 self.assert_(isinstance(self.theclass.max, self.theclass))
1779 self.assert_(isinstance(self.theclass.resolution, timedelta))
1780 self.assert_(self.theclass.max > self.theclass.min)
1781
1782 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001783 args = 20, 59, 16, 64**2
1784 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001785 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001786 green = pickler.dumps(orig, proto)
1787 derived = unpickler.loads(green)
1788 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001789
Tim Peters604c0132004-06-07 23:04:33 +00001790 def test_pickling_subclass_time(self):
1791 args = 20, 59, 16, 64**2
1792 orig = SubclassTime(*args)
1793 for pickler, unpickler, proto in pickle_choices:
1794 green = pickler.dumps(orig, proto)
1795 derived = unpickler.loads(green)
1796 self.assertEqual(orig, derived)
1797
Tim Peters2a799bf2002-12-16 20:18:38 +00001798 def test_bool(self):
1799 cls = self.theclass
1800 self.failUnless(cls(1))
1801 self.failUnless(cls(0, 1))
1802 self.failUnless(cls(0, 0, 1))
1803 self.failUnless(cls(0, 0, 0, 1))
1804 self.failUnless(not cls(0))
1805 self.failUnless(not cls())
1806
Tim Peters12bf3392002-12-24 05:41:27 +00001807 def test_replace(self):
1808 cls = self.theclass
1809 args = [1, 2, 3, 4]
1810 base = cls(*args)
1811 self.assertEqual(base, base.replace())
1812
1813 i = 0
1814 for name, newval in (("hour", 5),
1815 ("minute", 6),
1816 ("second", 7),
1817 ("microsecond", 8)):
1818 newargs = args[:]
1819 newargs[i] = newval
1820 expected = cls(*newargs)
1821 got = base.replace(**{name: newval})
1822 self.assertEqual(expected, got)
1823 i += 1
1824
1825 # Out of bounds.
1826 base = cls(1)
1827 self.assertRaises(ValueError, base.replace, hour=24)
1828 self.assertRaises(ValueError, base.replace, minute=-1)
1829 self.assertRaises(ValueError, base.replace, second=100)
1830 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1831
Tim Petersa98924a2003-05-17 05:55:19 +00001832 def test_subclass_time(self):
1833
1834 class C(self.theclass):
1835 theAnswer = 42
1836
1837 def __new__(cls, *args, **kws):
1838 temp = kws.copy()
1839 extra = temp.pop('extra')
1840 result = self.theclass.__new__(cls, *args, **temp)
1841 result.extra = extra
1842 return result
1843
1844 def newmeth(self, start):
1845 return start + self.hour + self.second
1846
1847 args = 4, 5, 6
1848
1849 dt1 = self.theclass(*args)
1850 dt2 = C(*args, **{'extra': 7})
1851
1852 self.assertEqual(dt2.__class__, C)
1853 self.assertEqual(dt2.theAnswer, 42)
1854 self.assertEqual(dt2.extra, 7)
1855 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1856 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1857
Armin Rigof4afb212005-11-07 07:15:48 +00001858 def test_backdoor_resistance(self):
1859 # see TestDate.test_backdoor_resistance().
1860 base = '2:59.0'
1861 for hour_byte in ' ', '9', chr(24), '\xff':
1862 self.assertRaises(TypeError, self.theclass,
1863 hour_byte + base[1:])
1864
Tim Peters855fe882002-12-22 03:43:39 +00001865# A mixin for classes with a tzinfo= argument. Subclasses must define
1866# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001867# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001868class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001869
Tim Petersbad8ff02002-12-30 20:52:32 +00001870 def test_argument_passing(self):
1871 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001872 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001873 class introspective(tzinfo):
1874 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001875 def utcoffset(self, dt):
1876 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001877 dst = utcoffset
1878
1879 obj = cls(1, 2, 3, tzinfo=introspective())
1880
Tim Peters0bf60bd2003-01-08 20:40:01 +00001881 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001882 self.assertEqual(obj.tzname(), expected)
1883
Tim Peters0bf60bd2003-01-08 20:40:01 +00001884 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001885 self.assertEqual(obj.utcoffset(), expected)
1886 self.assertEqual(obj.dst(), expected)
1887
Tim Peters855fe882002-12-22 03:43:39 +00001888 def test_bad_tzinfo_classes(self):
1889 cls = self.theclass
1890 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001891
Tim Peters855fe882002-12-22 03:43:39 +00001892 class NiceTry(object):
1893 def __init__(self): pass
1894 def utcoffset(self, dt): pass
1895 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1896
1897 class BetterTry(tzinfo):
1898 def __init__(self): pass
1899 def utcoffset(self, dt): pass
1900 b = BetterTry()
1901 t = cls(1, 1, 1, tzinfo=b)
1902 self.failUnless(t.tzinfo is b)
1903
1904 def test_utc_offset_out_of_bounds(self):
1905 class Edgy(tzinfo):
1906 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001907 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001908 def utcoffset(self, dt):
1909 return self.offset
1910
1911 cls = self.theclass
1912 for offset, legit in ((-1440, False),
1913 (-1439, True),
1914 (1439, True),
1915 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001916 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001917 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001918 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001919 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001920 else:
1921 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001922 if legit:
1923 aofs = abs(offset)
1924 h, m = divmod(aofs, 60)
1925 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001926 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001927 t = t.timetz()
1928 self.assertEqual(str(t), "01:02:03" + tag)
1929 else:
1930 self.assertRaises(ValueError, str, t)
1931
1932 def test_tzinfo_classes(self):
1933 cls = self.theclass
1934 class C1(tzinfo):
1935 def utcoffset(self, dt): return None
1936 def dst(self, dt): return None
1937 def tzname(self, dt): return None
1938 for t in (cls(1, 1, 1),
1939 cls(1, 1, 1, tzinfo=None),
1940 cls(1, 1, 1, tzinfo=C1())):
1941 self.failUnless(t.utcoffset() is None)
1942 self.failUnless(t.dst() is None)
1943 self.failUnless(t.tzname() is None)
1944
Tim Peters855fe882002-12-22 03:43:39 +00001945 class C3(tzinfo):
1946 def utcoffset(self, dt): return timedelta(minutes=-1439)
1947 def dst(self, dt): return timedelta(minutes=1439)
1948 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001949 t = cls(1, 1, 1, tzinfo=C3())
1950 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1951 self.assertEqual(t.dst(), timedelta(minutes=1439))
1952 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001953
1954 # Wrong types.
1955 class C4(tzinfo):
1956 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001957 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001958 def tzname(self, dt): return 0
1959 t = cls(1, 1, 1, tzinfo=C4())
1960 self.assertRaises(TypeError, t.utcoffset)
1961 self.assertRaises(TypeError, t.dst)
1962 self.assertRaises(TypeError, t.tzname)
1963
1964 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001965 class C6(tzinfo):
1966 def utcoffset(self, dt): return timedelta(hours=-24)
1967 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001968 t = cls(1, 1, 1, tzinfo=C6())
1969 self.assertRaises(ValueError, t.utcoffset)
1970 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001971
1972 # Not a whole number of minutes.
1973 class C7(tzinfo):
1974 def utcoffset(self, dt): return timedelta(seconds=61)
1975 def dst(self, dt): return timedelta(microseconds=-81)
1976 t = cls(1, 1, 1, tzinfo=C7())
1977 self.assertRaises(ValueError, t.utcoffset)
1978 self.assertRaises(ValueError, t.dst)
1979
Tim Peters4c0db782002-12-26 05:01:19 +00001980 def test_aware_compare(self):
1981 cls = self.theclass
1982
Tim Peters60c76e42002-12-27 00:41:11 +00001983 # Ensure that utcoffset() gets ignored if the comparands have
1984 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001985 class OperandDependentOffset(tzinfo):
1986 def utcoffset(self, t):
1987 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001988 # d0 and d1 equal after adjustment
1989 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001990 else:
Tim Peters397301e2003-01-02 21:28:08 +00001991 # d2 off in the weeds
1992 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001993
1994 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1995 d0 = base.replace(minute=3)
1996 d1 = base.replace(minute=9)
1997 d2 = base.replace(minute=11)
1998 for x in d0, d1, d2:
1999 for y in d0, d1, d2:
2000 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002001 expected = cmp(x.minute, y.minute)
2002 self.assertEqual(got, expected)
2003
2004 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002005 # Note that a time can't actually have an operand-depedent offset,
2006 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2007 # so skip this test for time.
2008 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002009 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2010 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2011 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2012 for x in d0, d1, d2:
2013 for y in d0, d1, d2:
2014 got = cmp(x, y)
2015 if (x is d0 or x is d1) and (y is d0 or y is d1):
2016 expected = 0
2017 elif x is y is d2:
2018 expected = 0
2019 elif x is d2:
2020 expected = -1
2021 else:
2022 assert y is d2
2023 expected = 1
2024 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002025
Tim Peters855fe882002-12-22 03:43:39 +00002026
Tim Peters0bf60bd2003-01-08 20:40:01 +00002027# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002028class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002029 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002030
2031 def test_empty(self):
2032 t = self.theclass()
2033 self.assertEqual(t.hour, 0)
2034 self.assertEqual(t.minute, 0)
2035 self.assertEqual(t.second, 0)
2036 self.assertEqual(t.microsecond, 0)
2037 self.failUnless(t.tzinfo is None)
2038
Tim Peters2a799bf2002-12-16 20:18:38 +00002039 def test_zones(self):
2040 est = FixedOffset(-300, "EST", 1)
2041 utc = FixedOffset(0, "UTC", -2)
2042 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002043 t1 = time( 7, 47, tzinfo=est)
2044 t2 = time(12, 47, tzinfo=utc)
2045 t3 = time(13, 47, tzinfo=met)
2046 t4 = time(microsecond=40)
2047 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002048
2049 self.assertEqual(t1.tzinfo, est)
2050 self.assertEqual(t2.tzinfo, utc)
2051 self.assertEqual(t3.tzinfo, met)
2052 self.failUnless(t4.tzinfo is None)
2053 self.assertEqual(t5.tzinfo, utc)
2054
Tim Peters855fe882002-12-22 03:43:39 +00002055 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2056 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2057 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002058 self.failUnless(t4.utcoffset() is None)
2059 self.assertRaises(TypeError, t1.utcoffset, "no args")
2060
2061 self.assertEqual(t1.tzname(), "EST")
2062 self.assertEqual(t2.tzname(), "UTC")
2063 self.assertEqual(t3.tzname(), "MET")
2064 self.failUnless(t4.tzname() is None)
2065 self.assertRaises(TypeError, t1.tzname, "no args")
2066
Tim Peters855fe882002-12-22 03:43:39 +00002067 self.assertEqual(t1.dst(), timedelta(minutes=1))
2068 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2069 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002070 self.failUnless(t4.dst() is None)
2071 self.assertRaises(TypeError, t1.dst, "no args")
2072
2073 self.assertEqual(hash(t1), hash(t2))
2074 self.assertEqual(hash(t1), hash(t3))
2075 self.assertEqual(hash(t2), hash(t3))
2076
2077 self.assertEqual(t1, t2)
2078 self.assertEqual(t1, t3)
2079 self.assertEqual(t2, t3)
2080 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2081 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2082 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2083
2084 self.assertEqual(str(t1), "07:47:00-05:00")
2085 self.assertEqual(str(t2), "12:47:00+00:00")
2086 self.assertEqual(str(t3), "13:47:00+01:00")
2087 self.assertEqual(str(t4), "00:00:00.000040")
2088 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2089
2090 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2091 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2092 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2093 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2094 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2095
Tim Peters0bf60bd2003-01-08 20:40:01 +00002096 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002097 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2098 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2099 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2100 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2101 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2102
2103 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2104 "07:47:00 %Z=EST %z=-0500")
2105 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2106 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2107
2108 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002109 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002110 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2111 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2112
Tim Petersb92bb712002-12-21 17:44:07 +00002113 # Check that an invalid tzname result raises an exception.
2114 class Badtzname(tzinfo):
2115 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002116 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002117 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2118 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002119
2120 def test_hash_edge_cases(self):
2121 # Offsets that overflow a basic time.
2122 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2123 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2124 self.assertEqual(hash(t1), hash(t2))
2125
2126 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2127 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2128 self.assertEqual(hash(t1), hash(t2))
2129
Tim Peters2a799bf2002-12-16 20:18:38 +00002130 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002131 # Try one without a tzinfo.
2132 args = 20, 59, 16, 64**2
2133 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002134 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002135 green = pickler.dumps(orig, proto)
2136 derived = unpickler.loads(green)
2137 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002138
2139 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002140 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002141 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002142 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002143 green = pickler.dumps(orig, proto)
2144 derived = unpickler.loads(green)
2145 self.assertEqual(orig, derived)
2146 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2147 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2148 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002149
2150 def test_more_bool(self):
2151 # Test cases with non-None tzinfo.
2152 cls = self.theclass
2153
2154 t = cls(0, tzinfo=FixedOffset(-300, ""))
2155 self.failUnless(t)
2156
2157 t = cls(5, tzinfo=FixedOffset(-300, ""))
2158 self.failUnless(t)
2159
2160 t = cls(5, tzinfo=FixedOffset(300, ""))
2161 self.failUnless(not t)
2162
2163 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2164 self.failUnless(not t)
2165
2166 # Mostly ensuring this doesn't overflow internally.
2167 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2168 self.failUnless(t)
2169
2170 # But this should yield a value error -- the utcoffset is bogus.
2171 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2172 self.assertRaises(ValueError, lambda: bool(t))
2173
2174 # Likewise.
2175 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2176 self.assertRaises(ValueError, lambda: bool(t))
2177
Tim Peters12bf3392002-12-24 05:41:27 +00002178 def test_replace(self):
2179 cls = self.theclass
2180 z100 = FixedOffset(100, "+100")
2181 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2182 args = [1, 2, 3, 4, z100]
2183 base = cls(*args)
2184 self.assertEqual(base, base.replace())
2185
2186 i = 0
2187 for name, newval in (("hour", 5),
2188 ("minute", 6),
2189 ("second", 7),
2190 ("microsecond", 8),
2191 ("tzinfo", zm200)):
2192 newargs = args[:]
2193 newargs[i] = newval
2194 expected = cls(*newargs)
2195 got = base.replace(**{name: newval})
2196 self.assertEqual(expected, got)
2197 i += 1
2198
2199 # Ensure we can get rid of a tzinfo.
2200 self.assertEqual(base.tzname(), "+100")
2201 base2 = base.replace(tzinfo=None)
2202 self.failUnless(base2.tzinfo is None)
2203 self.failUnless(base2.tzname() is None)
2204
2205 # Ensure we can add one.
2206 base3 = base2.replace(tzinfo=z100)
2207 self.assertEqual(base, base3)
2208 self.failUnless(base.tzinfo is base3.tzinfo)
2209
2210 # Out of bounds.
2211 base = cls(1)
2212 self.assertRaises(ValueError, base.replace, hour=24)
2213 self.assertRaises(ValueError, base.replace, minute=-1)
2214 self.assertRaises(ValueError, base.replace, second=100)
2215 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2216
Tim Peters60c76e42002-12-27 00:41:11 +00002217 def test_mixed_compare(self):
2218 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002219 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002220 self.assertEqual(t1, t2)
2221 t2 = t2.replace(tzinfo=None)
2222 self.assertEqual(t1, t2)
2223 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2224 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002225 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2226 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002227
Tim Peters0bf60bd2003-01-08 20:40:01 +00002228 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002229 class Varies(tzinfo):
2230 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002231 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002232 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002233 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002234 return self.offset
2235
2236 v = Varies()
2237 t1 = t2.replace(tzinfo=v)
2238 t2 = t2.replace(tzinfo=v)
2239 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2240 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2241 self.assertEqual(t1, t2)
2242
2243 # But if they're not identical, it isn't ignored.
2244 t2 = t2.replace(tzinfo=Varies())
2245 self.failUnless(t1 < t2) # t1's offset counter still going up
2246
Tim Petersa98924a2003-05-17 05:55:19 +00002247 def test_subclass_timetz(self):
2248
2249 class C(self.theclass):
2250 theAnswer = 42
2251
2252 def __new__(cls, *args, **kws):
2253 temp = kws.copy()
2254 extra = temp.pop('extra')
2255 result = self.theclass.__new__(cls, *args, **temp)
2256 result.extra = extra
2257 return result
2258
2259 def newmeth(self, start):
2260 return start + self.hour + self.second
2261
2262 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2263
2264 dt1 = self.theclass(*args)
2265 dt2 = C(*args, **{'extra': 7})
2266
2267 self.assertEqual(dt2.__class__, C)
2268 self.assertEqual(dt2.theAnswer, 42)
2269 self.assertEqual(dt2.extra, 7)
2270 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2271 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2272
Tim Peters4c0db782002-12-26 05:01:19 +00002273
Tim Peters0bf60bd2003-01-08 20:40:01 +00002274# Testing datetime objects with a non-None tzinfo.
2275
Tim Peters855fe882002-12-22 03:43:39 +00002276class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002277 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002278
2279 def test_trivial(self):
2280 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2281 self.assertEqual(dt.year, 1)
2282 self.assertEqual(dt.month, 2)
2283 self.assertEqual(dt.day, 3)
2284 self.assertEqual(dt.hour, 4)
2285 self.assertEqual(dt.minute, 5)
2286 self.assertEqual(dt.second, 6)
2287 self.assertEqual(dt.microsecond, 7)
2288 self.assertEqual(dt.tzinfo, None)
2289
2290 def test_even_more_compare(self):
2291 # The test_compare() and test_more_compare() inherited from TestDate
2292 # and TestDateTime covered non-tzinfo cases.
2293
2294 # Smallest possible after UTC adjustment.
2295 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2296 # Largest possible after UTC adjustment.
2297 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2298 tzinfo=FixedOffset(-1439, ""))
2299
2300 # Make sure those compare correctly, and w/o overflow.
2301 self.failUnless(t1 < t2)
2302 self.failUnless(t1 != t2)
2303 self.failUnless(t2 > t1)
2304
2305 self.failUnless(t1 == t1)
2306 self.failUnless(t2 == t2)
2307
2308 # Equal afer adjustment.
2309 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2310 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2311 self.assertEqual(t1, t2)
2312
2313 # Change t1 not to subtract a minute, and t1 should be larger.
2314 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2315 self.failUnless(t1 > t2)
2316
2317 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2318 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2319 self.failUnless(t1 < t2)
2320
2321 # Back to the original t1, but make seconds resolve it.
2322 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2323 second=1)
2324 self.failUnless(t1 > t2)
2325
2326 # Likewise, but make microseconds resolve it.
2327 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2328 microsecond=1)
2329 self.failUnless(t1 > t2)
2330
2331 # Make t2 naive and it should fail.
2332 t2 = self.theclass.min
2333 self.assertRaises(TypeError, lambda: t1 == t2)
2334 self.assertEqual(t2, t2)
2335
2336 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2337 class Naive(tzinfo):
2338 def utcoffset(self, dt): return None
2339 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2340 self.assertRaises(TypeError, lambda: t1 == t2)
2341 self.assertEqual(t2, t2)
2342
2343 # OTOH, it's OK to compare two of these mixing the two ways of being
2344 # naive.
2345 t1 = self.theclass(5, 6, 7)
2346 self.assertEqual(t1, t2)
2347
2348 # Try a bogus uctoffset.
2349 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002350 def utcoffset(self, dt):
2351 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002352 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2353 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002354 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002355
Tim Peters2a799bf2002-12-16 20:18:38 +00002356 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002357 # Try one without a tzinfo.
2358 args = 6, 7, 23, 20, 59, 1, 64**2
2359 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002360 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002361 green = pickler.dumps(orig, proto)
2362 derived = unpickler.loads(green)
2363 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002364
2365 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002366 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002367 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002368 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002369 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002370 green = pickler.dumps(orig, proto)
2371 derived = unpickler.loads(green)
2372 self.assertEqual(orig, derived)
2373 self.failUnless(isinstance(derived.tzinfo,
2374 PicklableFixedOffset))
2375 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2376 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002377
2378 def test_extreme_hashes(self):
2379 # If an attempt is made to hash these via subtracting the offset
2380 # then hashing a datetime object, OverflowError results. The
2381 # Python implementation used to blow up here.
2382 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2383 hash(t)
2384 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2385 tzinfo=FixedOffset(-1439, ""))
2386 hash(t)
2387
2388 # OTOH, an OOB offset should blow up.
2389 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2390 self.assertRaises(ValueError, hash, t)
2391
2392 def test_zones(self):
2393 est = FixedOffset(-300, "EST")
2394 utc = FixedOffset(0, "UTC")
2395 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002396 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2397 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2398 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002399 self.assertEqual(t1.tzinfo, est)
2400 self.assertEqual(t2.tzinfo, utc)
2401 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002402 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2403 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2404 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002405 self.assertEqual(t1.tzname(), "EST")
2406 self.assertEqual(t2.tzname(), "UTC")
2407 self.assertEqual(t3.tzname(), "MET")
2408 self.assertEqual(hash(t1), hash(t2))
2409 self.assertEqual(hash(t1), hash(t3))
2410 self.assertEqual(hash(t2), hash(t3))
2411 self.assertEqual(t1, t2)
2412 self.assertEqual(t1, t3)
2413 self.assertEqual(t2, t3)
2414 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2415 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2416 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002417 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002418 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2419 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2420 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2421
2422 def test_combine(self):
2423 met = FixedOffset(60, "MET")
2424 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002425 tz = time(18, 45, 3, 1234, tzinfo=met)
2426 dt = datetime.combine(d, tz)
2427 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002428 tzinfo=met))
2429
2430 def test_extract(self):
2431 met = FixedOffset(60, "MET")
2432 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2433 self.assertEqual(dt.date(), date(2002, 3, 4))
2434 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002435 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002436
2437 def test_tz_aware_arithmetic(self):
2438 import random
2439
2440 now = self.theclass.now()
2441 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002442 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002443 nowaware = self.theclass.combine(now.date(), timeaware)
2444 self.failUnless(nowaware.tzinfo is tz55)
2445 self.assertEqual(nowaware.timetz(), timeaware)
2446
2447 # Can't mix aware and non-aware.
2448 self.assertRaises(TypeError, lambda: now - nowaware)
2449 self.assertRaises(TypeError, lambda: nowaware - now)
2450
Tim Peters0bf60bd2003-01-08 20:40:01 +00002451 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002452 self.assertRaises(TypeError, lambda: now + nowaware)
2453 self.assertRaises(TypeError, lambda: nowaware + now)
2454 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2455
2456 # Subtracting should yield 0.
2457 self.assertEqual(now - now, timedelta(0))
2458 self.assertEqual(nowaware - nowaware, timedelta(0))
2459
2460 # Adding a delta should preserve tzinfo.
2461 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2462 nowawareplus = nowaware + delta
2463 self.failUnless(nowaware.tzinfo is tz55)
2464 nowawareplus2 = delta + nowaware
2465 self.failUnless(nowawareplus2.tzinfo is tz55)
2466 self.assertEqual(nowawareplus, nowawareplus2)
2467
2468 # that - delta should be what we started with, and that - what we
2469 # started with should be delta.
2470 diff = nowawareplus - delta
2471 self.failUnless(diff.tzinfo is tz55)
2472 self.assertEqual(nowaware, diff)
2473 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2474 self.assertEqual(nowawareplus - nowaware, delta)
2475
2476 # Make up a random timezone.
2477 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002478 # Attach it to nowawareplus.
2479 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002480 self.failUnless(nowawareplus.tzinfo is tzr)
2481 # Make sure the difference takes the timezone adjustments into account.
2482 got = nowaware - nowawareplus
2483 # Expected: (nowaware base - nowaware offset) -
2484 # (nowawareplus base - nowawareplus offset) =
2485 # (nowaware base - nowawareplus base) +
2486 # (nowawareplus offset - nowaware offset) =
2487 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002488 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002489 self.assertEqual(got, expected)
2490
2491 # Try max possible difference.
2492 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2493 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2494 tzinfo=FixedOffset(-1439, "max"))
2495 maxdiff = max - min
2496 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2497 timedelta(minutes=2*1439))
2498
2499 def test_tzinfo_now(self):
2500 meth = self.theclass.now
2501 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2502 base = meth()
2503 # Try with and without naming the keyword.
2504 off42 = FixedOffset(42, "42")
2505 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002506 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002507 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002508 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002509 # Bad argument with and w/o naming the keyword.
2510 self.assertRaises(TypeError, meth, 16)
2511 self.assertRaises(TypeError, meth, tzinfo=16)
2512 # Bad keyword name.
2513 self.assertRaises(TypeError, meth, tinfo=off42)
2514 # Too many args.
2515 self.assertRaises(TypeError, meth, off42, off42)
2516
Tim Peters10cadce2003-01-23 19:58:02 +00002517 # We don't know which time zone we're in, and don't have a tzinfo
2518 # class to represent it, so seeing whether a tz argument actually
2519 # does a conversion is tricky.
2520 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2521 utc = FixedOffset(0, "utc", 0)
2522 for dummy in range(3):
2523 now = datetime.now(weirdtz)
2524 self.failUnless(now.tzinfo is weirdtz)
2525 utcnow = datetime.utcnow().replace(tzinfo=utc)
2526 now2 = utcnow.astimezone(weirdtz)
2527 if abs(now - now2) < timedelta(seconds=30):
2528 break
2529 # Else the code is broken, or more than 30 seconds passed between
2530 # calls; assuming the latter, just try again.
2531 else:
2532 # Three strikes and we're out.
2533 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2534
Tim Peters2a799bf2002-12-16 20:18:38 +00002535 def test_tzinfo_fromtimestamp(self):
2536 import time
2537 meth = self.theclass.fromtimestamp
2538 ts = time.time()
2539 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2540 base = meth(ts)
2541 # Try with and without naming the keyword.
2542 off42 = FixedOffset(42, "42")
2543 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002544 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002545 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002546 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002547 # Bad argument with and w/o naming the keyword.
2548 self.assertRaises(TypeError, meth, ts, 16)
2549 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2550 # Bad keyword name.
2551 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2552 # Too many args.
2553 self.assertRaises(TypeError, meth, ts, off42, off42)
2554 # Too few args.
2555 self.assertRaises(TypeError, meth)
2556
Tim Peters2a44a8d2003-01-23 20:53:10 +00002557 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002558 timestamp = 1000000000
2559 utcdatetime = datetime.utcfromtimestamp(timestamp)
2560 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2561 # But on some flavor of Mac, it's nowhere near that. So we can't have
2562 # any idea here what time that actually is, we can only test that
2563 # relative changes match.
2564 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2565 tz = FixedOffset(utcoffset, "tz", 0)
2566 expected = utcdatetime + utcoffset
2567 got = datetime.fromtimestamp(timestamp, tz)
2568 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002569
Tim Peters2a799bf2002-12-16 20:18:38 +00002570 def test_tzinfo_utcnow(self):
2571 meth = self.theclass.utcnow
2572 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2573 base = meth()
2574 # Try with and without naming the keyword; for whatever reason,
2575 # utcnow() doesn't accept a tzinfo argument.
2576 off42 = FixedOffset(42, "42")
2577 self.assertRaises(TypeError, meth, off42)
2578 self.assertRaises(TypeError, meth, tzinfo=off42)
2579
2580 def test_tzinfo_utcfromtimestamp(self):
2581 import time
2582 meth = self.theclass.utcfromtimestamp
2583 ts = time.time()
2584 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2585 base = meth(ts)
2586 # Try with and without naming the keyword; for whatever reason,
2587 # utcfromtimestamp() doesn't accept a tzinfo argument.
2588 off42 = FixedOffset(42, "42")
2589 self.assertRaises(TypeError, meth, ts, off42)
2590 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2591
2592 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002593 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002594 # DST flag.
2595 class DST(tzinfo):
2596 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002597 if isinstance(dstvalue, int):
2598 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002599 self.dstvalue = dstvalue
2600 def dst(self, dt):
2601 return self.dstvalue
2602
2603 cls = self.theclass
2604 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2605 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2606 t = d.timetuple()
2607 self.assertEqual(1, t.tm_year)
2608 self.assertEqual(1, t.tm_mon)
2609 self.assertEqual(1, t.tm_mday)
2610 self.assertEqual(10, t.tm_hour)
2611 self.assertEqual(20, t.tm_min)
2612 self.assertEqual(30, t.tm_sec)
2613 self.assertEqual(0, t.tm_wday)
2614 self.assertEqual(1, t.tm_yday)
2615 self.assertEqual(flag, t.tm_isdst)
2616
2617 # dst() returns wrong type.
2618 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2619
2620 # dst() at the edge.
2621 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2622 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2623
2624 # dst() out of range.
2625 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2626 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2627
2628 def test_utctimetuple(self):
2629 class DST(tzinfo):
2630 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002631 if isinstance(dstvalue, int):
2632 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002633 self.dstvalue = dstvalue
2634 def dst(self, dt):
2635 return self.dstvalue
2636
2637 cls = self.theclass
2638 # This can't work: DST didn't implement utcoffset.
2639 self.assertRaises(NotImplementedError,
2640 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2641
2642 class UOFS(DST):
2643 def __init__(self, uofs, dofs=None):
2644 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002645 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002646 def utcoffset(self, dt):
2647 return self.uofs
2648
2649 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2650 # in effect for a UTC time.
2651 for dstvalue in -33, 33, 0, None:
2652 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2653 t = d.utctimetuple()
2654 self.assertEqual(d.year, t.tm_year)
2655 self.assertEqual(d.month, t.tm_mon)
2656 self.assertEqual(d.day, t.tm_mday)
2657 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2658 self.assertEqual(13, t.tm_min)
2659 self.assertEqual(d.second, t.tm_sec)
2660 self.assertEqual(d.weekday(), t.tm_wday)
2661 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2662 t.tm_yday)
2663 self.assertEqual(0, t.tm_isdst)
2664
2665 # At the edges, UTC adjustment can normalize into years out-of-range
2666 # for a datetime object. Ensure that a correct timetuple is
2667 # created anyway.
2668 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2669 # That goes back 1 minute less than a full day.
2670 t = tiny.utctimetuple()
2671 self.assertEqual(t.tm_year, MINYEAR-1)
2672 self.assertEqual(t.tm_mon, 12)
2673 self.assertEqual(t.tm_mday, 31)
2674 self.assertEqual(t.tm_hour, 0)
2675 self.assertEqual(t.tm_min, 1)
2676 self.assertEqual(t.tm_sec, 37)
2677 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2678 self.assertEqual(t.tm_isdst, 0)
2679
2680 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2681 # That goes forward 1 minute less than a full day.
2682 t = huge.utctimetuple()
2683 self.assertEqual(t.tm_year, MAXYEAR+1)
2684 self.assertEqual(t.tm_mon, 1)
2685 self.assertEqual(t.tm_mday, 1)
2686 self.assertEqual(t.tm_hour, 23)
2687 self.assertEqual(t.tm_min, 58)
2688 self.assertEqual(t.tm_sec, 37)
2689 self.assertEqual(t.tm_yday, 1)
2690 self.assertEqual(t.tm_isdst, 0)
2691
2692 def test_tzinfo_isoformat(self):
2693 zero = FixedOffset(0, "+00:00")
2694 plus = FixedOffset(220, "+03:40")
2695 minus = FixedOffset(-231, "-03:51")
2696 unknown = FixedOffset(None, "")
2697
2698 cls = self.theclass
2699 datestr = '0001-02-03'
2700 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002701 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002702 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2703 timestr = '04:05:59' + (us and '.987001' or '')
2704 ofsstr = ofs is not None and d.tzname() or ''
2705 tailstr = timestr + ofsstr
2706 iso = d.isoformat()
2707 self.assertEqual(iso, datestr + 'T' + tailstr)
2708 self.assertEqual(iso, d.isoformat('T'))
2709 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2710 self.assertEqual(str(d), datestr + ' ' + tailstr)
2711
Tim Peters12bf3392002-12-24 05:41:27 +00002712 def test_replace(self):
2713 cls = self.theclass
2714 z100 = FixedOffset(100, "+100")
2715 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2716 args = [1, 2, 3, 4, 5, 6, 7, z100]
2717 base = cls(*args)
2718 self.assertEqual(base, base.replace())
2719
2720 i = 0
2721 for name, newval in (("year", 2),
2722 ("month", 3),
2723 ("day", 4),
2724 ("hour", 5),
2725 ("minute", 6),
2726 ("second", 7),
2727 ("microsecond", 8),
2728 ("tzinfo", zm200)):
2729 newargs = args[:]
2730 newargs[i] = newval
2731 expected = cls(*newargs)
2732 got = base.replace(**{name: newval})
2733 self.assertEqual(expected, got)
2734 i += 1
2735
2736 # Ensure we can get rid of a tzinfo.
2737 self.assertEqual(base.tzname(), "+100")
2738 base2 = base.replace(tzinfo=None)
2739 self.failUnless(base2.tzinfo is None)
2740 self.failUnless(base2.tzname() is None)
2741
2742 # Ensure we can add one.
2743 base3 = base2.replace(tzinfo=z100)
2744 self.assertEqual(base, base3)
2745 self.failUnless(base.tzinfo is base3.tzinfo)
2746
2747 # Out of bounds.
2748 base = cls(2000, 2, 29)
2749 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002750
Tim Peters80475bb2002-12-25 07:40:55 +00002751 def test_more_astimezone(self):
2752 # The inherited test_astimezone covered some trivial and error cases.
2753 fnone = FixedOffset(None, "None")
2754 f44m = FixedOffset(44, "44")
2755 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2756
Tim Peters10cadce2003-01-23 19:58:02 +00002757 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002758 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002759 # Replacing with degenerate tzinfo raises an exception.
2760 self.assertRaises(ValueError, dt.astimezone, fnone)
2761 # Ditto with None tz.
2762 self.assertRaises(TypeError, dt.astimezone, None)
2763 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002764 x = dt.astimezone(dt.tzinfo)
2765 self.failUnless(x.tzinfo is f44m)
2766 self.assertEqual(x.date(), dt.date())
2767 self.assertEqual(x.time(), dt.time())
2768
2769 # Replacing with different tzinfo does adjust.
2770 got = dt.astimezone(fm5h)
2771 self.failUnless(got.tzinfo is fm5h)
2772 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2773 expected = dt - dt.utcoffset() # in effect, convert to UTC
2774 expected += fm5h.utcoffset(dt) # and from there to local time
2775 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2776 self.assertEqual(got.date(), expected.date())
2777 self.assertEqual(got.time(), expected.time())
2778 self.assertEqual(got.timetz(), expected.timetz())
2779 self.failUnless(got.tzinfo is expected.tzinfo)
2780 self.assertEqual(got, expected)
2781
Tim Peters4c0db782002-12-26 05:01:19 +00002782 def test_aware_subtract(self):
2783 cls = self.theclass
2784
Tim Peters60c76e42002-12-27 00:41:11 +00002785 # Ensure that utcoffset() is ignored when the operands have the
2786 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002787 class OperandDependentOffset(tzinfo):
2788 def utcoffset(self, t):
2789 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002790 # d0 and d1 equal after adjustment
2791 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002792 else:
Tim Peters397301e2003-01-02 21:28:08 +00002793 # d2 off in the weeds
2794 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002795
2796 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2797 d0 = base.replace(minute=3)
2798 d1 = base.replace(minute=9)
2799 d2 = base.replace(minute=11)
2800 for x in d0, d1, d2:
2801 for y in d0, d1, d2:
2802 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002803 expected = timedelta(minutes=x.minute - y.minute)
2804 self.assertEqual(got, expected)
2805
2806 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2807 # ignored.
2808 base = cls(8, 9, 10, 11, 12, 13, 14)
2809 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2810 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2811 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2812 for x in d0, d1, d2:
2813 for y in d0, d1, d2:
2814 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002815 if (x is d0 or x is d1) and (y is d0 or y is d1):
2816 expected = timedelta(0)
2817 elif x is y is d2:
2818 expected = timedelta(0)
2819 elif x is d2:
2820 expected = timedelta(minutes=(11-59)-0)
2821 else:
2822 assert y is d2
2823 expected = timedelta(minutes=0-(11-59))
2824 self.assertEqual(got, expected)
2825
Tim Peters60c76e42002-12-27 00:41:11 +00002826 def test_mixed_compare(self):
2827 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002828 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002829 self.assertEqual(t1, t2)
2830 t2 = t2.replace(tzinfo=None)
2831 self.assertEqual(t1, t2)
2832 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2833 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002834 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2835 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002836
Tim Peters0bf60bd2003-01-08 20:40:01 +00002837 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002838 class Varies(tzinfo):
2839 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002840 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002841 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002842 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002843 return self.offset
2844
2845 v = Varies()
2846 t1 = t2.replace(tzinfo=v)
2847 t2 = t2.replace(tzinfo=v)
2848 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2849 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2850 self.assertEqual(t1, t2)
2851
2852 # But if they're not identical, it isn't ignored.
2853 t2 = t2.replace(tzinfo=Varies())
2854 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002855
Tim Petersa98924a2003-05-17 05:55:19 +00002856 def test_subclass_datetimetz(self):
2857
2858 class C(self.theclass):
2859 theAnswer = 42
2860
2861 def __new__(cls, *args, **kws):
2862 temp = kws.copy()
2863 extra = temp.pop('extra')
2864 result = self.theclass.__new__(cls, *args, **temp)
2865 result.extra = extra
2866 return result
2867
2868 def newmeth(self, start):
2869 return start + self.hour + self.year
2870
2871 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2872
2873 dt1 = self.theclass(*args)
2874 dt2 = C(*args, **{'extra': 7})
2875
2876 self.assertEqual(dt2.__class__, C)
2877 self.assertEqual(dt2.theAnswer, 42)
2878 self.assertEqual(dt2.extra, 7)
2879 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2880 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2881
Tim Peters621818b2002-12-29 23:44:49 +00002882# Pain to set up DST-aware tzinfo classes.
2883
2884def first_sunday_on_or_after(dt):
2885 days_to_go = 6 - dt.weekday()
2886 if days_to_go:
2887 dt += timedelta(days_to_go)
2888 return dt
2889
2890ZERO = timedelta(0)
2891HOUR = timedelta(hours=1)
2892DAY = timedelta(days=1)
2893# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2894DSTSTART = datetime(1, 4, 1, 2)
2895# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002896# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2897# being standard time on that day, there is no spelling in local time of
2898# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2899DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002900
2901class USTimeZone(tzinfo):
2902
2903 def __init__(self, hours, reprname, stdname, dstname):
2904 self.stdoffset = timedelta(hours=hours)
2905 self.reprname = reprname
2906 self.stdname = stdname
2907 self.dstname = dstname
2908
2909 def __repr__(self):
2910 return self.reprname
2911
2912 def tzname(self, dt):
2913 if self.dst(dt):
2914 return self.dstname
2915 else:
2916 return self.stdname
2917
2918 def utcoffset(self, dt):
2919 return self.stdoffset + self.dst(dt)
2920
2921 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002922 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002923 # An exception instead may be sensible here, in one or more of
2924 # the cases.
2925 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002926 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002927
2928 # Find first Sunday in April.
2929 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2930 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2931
2932 # Find last Sunday in October.
2933 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2934 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2935
Tim Peters621818b2002-12-29 23:44:49 +00002936 # Can't compare naive to aware objects, so strip the timezone from
2937 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002938 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002939 return HOUR
2940 else:
2941 return ZERO
2942
Tim Peters521fc152002-12-31 17:36:56 +00002943Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2944Central = USTimeZone(-6, "Central", "CST", "CDT")
2945Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2946Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002947utc_real = FixedOffset(0, "UTC", 0)
2948# For better test coverage, we want another flavor of UTC that's west of
2949# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002950utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002951
2952class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002953 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002954 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002955 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002956
Tim Peters0bf60bd2003-01-08 20:40:01 +00002957 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002958
Tim Peters521fc152002-12-31 17:36:56 +00002959 # Check a time that's inside DST.
2960 def checkinside(self, dt, tz, utc, dston, dstoff):
2961 self.assertEqual(dt.dst(), HOUR)
2962
2963 # Conversion to our own timezone is always an identity.
2964 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002965
2966 asutc = dt.astimezone(utc)
2967 there_and_back = asutc.astimezone(tz)
2968
2969 # Conversion to UTC and back isn't always an identity here,
2970 # because there are redundant spellings (in local time) of
2971 # UTC time when DST begins: the clock jumps from 1:59:59
2972 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2973 # make sense then. The classes above treat 2:MM:SS as
2974 # daylight time then (it's "after 2am"), really an alias
2975 # for 1:MM:SS standard time. The latter form is what
2976 # conversion back from UTC produces.
2977 if dt.date() == dston.date() and dt.hour == 2:
2978 # We're in the redundant hour, and coming back from
2979 # UTC gives the 1:MM:SS standard-time spelling.
2980 self.assertEqual(there_and_back + HOUR, dt)
2981 # Although during was considered to be in daylight
2982 # time, there_and_back is not.
2983 self.assertEqual(there_and_back.dst(), ZERO)
2984 # They're the same times in UTC.
2985 self.assertEqual(there_and_back.astimezone(utc),
2986 dt.astimezone(utc))
2987 else:
2988 # We're not in the redundant hour.
2989 self.assertEqual(dt, there_and_back)
2990
Tim Peters327098a2003-01-20 22:54:38 +00002991 # Because we have a redundant spelling when DST begins, there is
2992 # (unforunately) an hour when DST ends that can't be spelled at all in
2993 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2994 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2995 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2996 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2997 # expressed in local time. Nevertheless, we want conversion back
2998 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002999 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003000 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003001 if dt.date() == dstoff.date() and dt.hour == 0:
3002 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003003 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003004 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3005 nexthour_utc += HOUR
3006 nexthour_tz = nexthour_utc.astimezone(tz)
3007 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003008 else:
Tim Peters327098a2003-01-20 22:54:38 +00003009 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003010
3011 # Check a time that's outside DST.
3012 def checkoutside(self, dt, tz, utc):
3013 self.assertEqual(dt.dst(), ZERO)
3014
3015 # Conversion to our own timezone is always an identity.
3016 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003017
3018 # Converting to UTC and back is an identity too.
3019 asutc = dt.astimezone(utc)
3020 there_and_back = asutc.astimezone(tz)
3021 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003022
Tim Peters1024bf82002-12-30 17:09:40 +00003023 def convert_between_tz_and_utc(self, tz, utc):
3024 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003025 # Because 1:MM on the day DST ends is taken as being standard time,
3026 # there is no spelling in tz for the last hour of daylight time.
3027 # For purposes of the test, the last hour of DST is 0:MM, which is
3028 # taken as being daylight time (and 1:MM is taken as being standard
3029 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003030 dstoff = self.dstoff.replace(tzinfo=tz)
3031 for delta in (timedelta(weeks=13),
3032 DAY,
3033 HOUR,
3034 timedelta(minutes=1),
3035 timedelta(microseconds=1)):
3036
Tim Peters521fc152002-12-31 17:36:56 +00003037 self.checkinside(dston, tz, utc, dston, dstoff)
3038 for during in dston + delta, dstoff - delta:
3039 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003040
Tim Peters521fc152002-12-31 17:36:56 +00003041 self.checkoutside(dstoff, tz, utc)
3042 for outside in dston - delta, dstoff + delta:
3043 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003044
Tim Peters621818b2002-12-29 23:44:49 +00003045 def test_easy(self):
3046 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003047 self.convert_between_tz_and_utc(Eastern, utc_real)
3048 self.convert_between_tz_and_utc(Pacific, utc_real)
3049 self.convert_between_tz_and_utc(Eastern, utc_fake)
3050 self.convert_between_tz_and_utc(Pacific, utc_fake)
3051 # The next is really dancing near the edge. It works because
3052 # Pacific and Eastern are far enough apart that their "problem
3053 # hours" don't overlap.
3054 self.convert_between_tz_and_utc(Eastern, Pacific)
3055 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003056 # OTOH, these fail! Don't enable them. The difficulty is that
3057 # the edge case tests assume that every hour is representable in
3058 # the "utc" class. This is always true for a fixed-offset tzinfo
3059 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3060 # For these adjacent DST-aware time zones, the range of time offsets
3061 # tested ends up creating hours in the one that aren't representable
3062 # in the other. For the same reason, we would see failures in the
3063 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3064 # offset deltas in convert_between_tz_and_utc().
3065 #
3066 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3067 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003068
Tim Petersf3615152003-01-01 21:51:37 +00003069 def test_tricky(self):
3070 # 22:00 on day before daylight starts.
3071 fourback = self.dston - timedelta(hours=4)
3072 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003073 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003074 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3075 # 2", we should get the 3 spelling.
3076 # If we plug 22:00 the day before into Eastern, it "looks like std
3077 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3078 # to 22:00 lands on 2:00, which makes no sense in local time (the
3079 # local clock jumps from 1 to 3). The point here is to make sure we
3080 # get the 3 spelling.
3081 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003082 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003083 self.assertEqual(expected, got)
3084
3085 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3086 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003087 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003088 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3089 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3090 # spelling.
3091 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003092 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003093 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003094
Tim Petersadf64202003-01-04 06:03:15 +00003095 # Now on the day DST ends, we want "repeat an hour" behavior.
3096 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3097 # EST 23:MM 0:MM 1:MM 2:MM
3098 # EDT 0:MM 1:MM 2:MM 3:MM
3099 # wall 0:MM 1:MM 1:MM 2:MM against these
3100 for utc in utc_real, utc_fake:
3101 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003102 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003103 # Convert that to UTC.
3104 first_std_hour -= tz.utcoffset(None)
3105 # Adjust for possibly fake UTC.
3106 asutc = first_std_hour + utc.utcoffset(None)
3107 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3108 # tz=Eastern.
3109 asutcbase = asutc.replace(tzinfo=utc)
3110 for tzhour in (0, 1, 1, 2):
3111 expectedbase = self.dstoff.replace(hour=tzhour)
3112 for minute in 0, 30, 59:
3113 expected = expectedbase.replace(minute=minute)
3114 asutc = asutcbase.replace(minute=minute)
3115 astz = asutc.astimezone(tz)
3116 self.assertEqual(astz.replace(tzinfo=None), expected)
3117 asutcbase += HOUR
3118
3119
Tim Peters710fb152003-01-02 19:35:54 +00003120 def test_bogus_dst(self):
3121 class ok(tzinfo):
3122 def utcoffset(self, dt): return HOUR
3123 def dst(self, dt): return HOUR
3124
3125 now = self.theclass.now().replace(tzinfo=utc_real)
3126 # Doesn't blow up.
3127 now.astimezone(ok())
3128
3129 # Does blow up.
3130 class notok(ok):
3131 def dst(self, dt): return None
3132 self.assertRaises(ValueError, now.astimezone, notok())
3133
Tim Peters52dcce22003-01-23 16:36:11 +00003134 def test_fromutc(self):
3135 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3136 now = datetime.utcnow().replace(tzinfo=utc_real)
3137 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3138 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3139 enow = Eastern.fromutc(now) # doesn't blow up
3140 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3141 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3142 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3143
3144 # Always converts UTC to standard time.
3145 class FauxUSTimeZone(USTimeZone):
3146 def fromutc(self, dt):
3147 return dt + self.stdoffset
3148 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3149
3150 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3151 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3152 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3153
3154 # Check around DST start.
3155 start = self.dston.replace(hour=4, tzinfo=Eastern)
3156 fstart = start.replace(tzinfo=FEastern)
3157 for wall in 23, 0, 1, 3, 4, 5:
3158 expected = start.replace(hour=wall)
3159 if wall == 23:
3160 expected -= timedelta(days=1)
3161 got = Eastern.fromutc(start)
3162 self.assertEqual(expected, got)
3163
3164 expected = fstart + FEastern.stdoffset
3165 got = FEastern.fromutc(fstart)
3166 self.assertEqual(expected, got)
3167
3168 # Ensure astimezone() calls fromutc() too.
3169 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3170 self.assertEqual(expected, got)
3171
3172 start += HOUR
3173 fstart += HOUR
3174
3175 # Check around DST end.
3176 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3177 fstart = start.replace(tzinfo=FEastern)
3178 for wall in 0, 1, 1, 2, 3, 4:
3179 expected = start.replace(hour=wall)
3180 got = Eastern.fromutc(start)
3181 self.assertEqual(expected, got)
3182
3183 expected = fstart + FEastern.stdoffset
3184 got = FEastern.fromutc(fstart)
3185 self.assertEqual(expected, got)
3186
3187 # Ensure astimezone() calls fromutc() too.
3188 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3189 self.assertEqual(expected, got)
3190
3191 start += HOUR
3192 fstart += HOUR
3193
Tim Peters710fb152003-01-02 19:35:54 +00003194
Tim Peters528ca532004-09-16 01:30:50 +00003195#############################################################################
3196# oddballs
3197
3198class Oddballs(unittest.TestCase):
3199
3200 def test_bug_1028306(self):
3201 # Trying to compare a date to a datetime should act like a mixed-
3202 # type comparison, despite that datetime is a subclass of date.
3203 as_date = date.today()
3204 as_datetime = datetime.combine(as_date, time())
3205 self.assert_(as_date != as_datetime)
3206 self.assert_(as_datetime != as_date)
3207 self.assert_(not as_date == as_datetime)
3208 self.assert_(not as_datetime == as_date)
3209 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3210 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3211 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3212 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3213 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3214 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3215 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3216 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3217
3218 # Neverthelss, comparison should work with the base-class (date)
3219 # projection if use of a date method is forced.
3220 self.assert_(as_date.__eq__(as_datetime))
3221 different_day = (as_date.day + 1) % 20 + 1
3222 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3223 different_day)))
3224
3225 # And date should compare with other subclasses of date. If a
3226 # subclass wants to stop this, it's up to the subclass to do so.
3227 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3228 self.assertEqual(as_date, date_sc)
3229 self.assertEqual(date_sc, as_date)
3230
3231 # Ditto for datetimes.
3232 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3233 as_date.day, 0, 0, 0)
3234 self.assertEqual(as_datetime, datetime_sc)
3235 self.assertEqual(datetime_sc, as_datetime)
3236
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003237def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003238 allsuites = [unittest.makeSuite(klass, 'test')
3239 for klass in (TestModule,
3240 TestTZInfo,
3241 TestTimeDelta,
3242 TestDateOnly,
3243 TestDate,
3244 TestDateTime,
3245 TestTime,
3246 TestTimeTZ,
3247 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003248 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003249 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003250 )
3251 ]
3252 return unittest.TestSuite(allsuites)
3253
3254def test_main():
3255 import gc
3256 import sys
3257
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003258 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003259 lastrc = None
3260 while True:
3261 test_support.run_suite(thesuite)
3262 if 1: # change to 0, under a debug build, for some leak detection
3263 break
3264 gc.collect()
3265 if gc.garbage:
3266 raise SystemError("gc.garbage not empty after test run: %r" %
3267 gc.garbage)
3268 if hasattr(sys, 'gettotalrefcount'):
3269 thisrc = sys.gettotalrefcount()
3270 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3271 if lastrc:
3272 print >> sys.stderr, 'delta:', thisrc - lastrc
3273 else:
3274 print >> sys.stderr
3275 lastrc = thisrc
3276
3277if __name__ == "__main__":
3278 test_main()