blob: c4978f3777768d867ba7a0e46b2db5c4bdca071a [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters68124bb2003-02-08 03:46:31 +000025# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000028
Tim Peters2a799bf2002-12-16 20:18:38 +000029
30#############################################################################
31# module tests
32
33class TestModule(unittest.TestCase):
34
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
39
40#############################################################################
41# tzinfo tests
42
43class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000045 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000049 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
60
Tim Petersfb8472c2002-12-21 05:04:42 +000061class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
64
Tim Peters2a799bf2002-12-16 20:18:38 +000065class TestTZInfo(unittest.TestCase):
66
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
76
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
85
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
90
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000095 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000096 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000097 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000098
99 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000105 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000109
110 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000111 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000114 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000115 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000118 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000125
126#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000127# Base clase for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
132
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
135
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
138
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
143
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
146
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
149
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
152
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
157
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
162
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
165
166#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000167# timedelta tests
168
Tim Peters07534a62003-02-07 22:50:28 +0000169class TestTimeDelta(HarmlessMixedComparison):
170
171 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000172
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
176
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
189
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
197
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
201
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
236
237 def test_disallowed_computations(self):
238 a = timedelta(42)
239
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
246
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
255
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
261
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
268
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
278
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
288
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
293
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
298
299 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000300 args = 12, 34, 56
301 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000302 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000306
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
318
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
335
Tim Peters68124bb2003-02-08 03:46:31 +0000336 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
341
Tim Peters2a799bf2002-12-16 20:18:38 +0000342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
350
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
354
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
359
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
364
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
367
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
371
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
377
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
384
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
388
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
397
398 def test_overflow(self):
399 tiny = timedelta.resolution
400
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
405
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
410
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
412
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
416
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
422
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
429
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
433
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
438
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
445
446#############################################################################
447# date tests
448
449class TestDateOnly(unittest.TestCase):
450 # Tests here won't pass if also run on datetime objects, so don't
451 # subclass this to test datetimes too.
452
453 def test_delta_non_days_ignored(self):
454 dt = date(2000, 1, 2)
455 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
456 microseconds=5)
457 days = timedelta(delta.days)
458 self.assertEqual(days, timedelta(1))
459
460 dt2 = dt + delta
461 self.assertEqual(dt2, dt + days)
462
463 dt2 = delta + dt
464 self.assertEqual(dt2, dt + days)
465
466 dt2 = dt - delta
467 self.assertEqual(dt2, dt - days)
468
469 delta = -delta
470 days = timedelta(delta.days)
471 self.assertEqual(days, timedelta(-2))
472
473 dt2 = dt + delta
474 self.assertEqual(dt2, dt + days)
475
476 dt2 = delta + dt
477 self.assertEqual(dt2, dt + days)
478
479 dt2 = dt - delta
480 self.assertEqual(dt2, dt - days)
481
Tim Peters07534a62003-02-07 22:50:28 +0000482class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000483 # Tests here should pass for both dates and datetimes, except for a
484 # few tests that TestDateTime overrides.
485
486 theclass = date
487
488 def test_basic_attributes(self):
489 dt = self.theclass(2002, 3, 1)
490 self.assertEqual(dt.year, 2002)
491 self.assertEqual(dt.month, 3)
492 self.assertEqual(dt.day, 1)
493
494 def test_roundtrip(self):
495 for dt in (self.theclass(1, 2, 3),
496 self.theclass.today()):
497 # Verify dt -> string -> date identity.
498 s = repr(dt)
499 self.failUnless(s.startswith('datetime.'))
500 s = s[9:]
501 dt2 = eval(s)
502 self.assertEqual(dt, dt2)
503
504 # Verify identity via reconstructing from pieces.
505 dt2 = self.theclass(dt.year, dt.month, dt.day)
506 self.assertEqual(dt, dt2)
507
508 def test_ordinal_conversions(self):
509 # Check some fixed values.
510 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
511 (1, 12, 31, 365),
512 (2, 1, 1, 366),
513 # first example from "Calendrical Calculations"
514 (1945, 11, 12, 710347)]:
515 d = self.theclass(y, m, d)
516 self.assertEqual(n, d.toordinal())
517 fromord = self.theclass.fromordinal(n)
518 self.assertEqual(d, fromord)
519 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000520 # if we're checking something fancier than a date, verify
521 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000522 self.assertEqual(fromord.hour, 0)
523 self.assertEqual(fromord.minute, 0)
524 self.assertEqual(fromord.second, 0)
525 self.assertEqual(fromord.microsecond, 0)
526
Tim Peters0bf60bd2003-01-08 20:40:01 +0000527 # Check first and last days of year spottily across the whole
528 # range of years supported.
529 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000530 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
531 d = self.theclass(year, 1, 1)
532 n = d.toordinal()
533 d2 = self.theclass.fromordinal(n)
534 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000535 # Verify that moving back a day gets to the end of year-1.
536 if year > 1:
537 d = self.theclass.fromordinal(n-1)
538 d2 = self.theclass(year-1, 12, 31)
539 self.assertEqual(d, d2)
540 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000541
542 # Test every day in a leap-year and a non-leap year.
543 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
544 for year, isleap in (2000, True), (2002, False):
545 n = self.theclass(year, 1, 1).toordinal()
546 for month, maxday in zip(range(1, 13), dim):
547 if month == 2 and isleap:
548 maxday += 1
549 for day in range(1, maxday+1):
550 d = self.theclass(year, month, day)
551 self.assertEqual(d.toordinal(), n)
552 self.assertEqual(d, self.theclass.fromordinal(n))
553 n += 1
554
555 def test_extreme_ordinals(self):
556 a = self.theclass.min
557 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
558 aord = a.toordinal()
559 b = a.fromordinal(aord)
560 self.assertEqual(a, b)
561
562 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
563
564 b = a + timedelta(days=1)
565 self.assertEqual(b.toordinal(), aord + 1)
566 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
567
568 a = self.theclass.max
569 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
570 aord = a.toordinal()
571 b = a.fromordinal(aord)
572 self.assertEqual(a, b)
573
574 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
575
576 b = a - timedelta(days=1)
577 self.assertEqual(b.toordinal(), aord - 1)
578 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
579
580 def test_bad_constructor_arguments(self):
581 # bad years
582 self.theclass(MINYEAR, 1, 1) # no exception
583 self.theclass(MAXYEAR, 1, 1) # no exception
584 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
585 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
586 # bad months
587 self.theclass(2000, 1, 1) # no exception
588 self.theclass(2000, 12, 1) # no exception
589 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
590 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
591 # bad days
592 self.theclass(2000, 2, 29) # no exception
593 self.theclass(2004, 2, 29) # no exception
594 self.theclass(2400, 2, 29) # no exception
595 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
596 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
597 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
598 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
599 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
600 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
601
602 def test_hash_equality(self):
603 d = self.theclass(2000, 12, 31)
604 # same thing
605 e = self.theclass(2000, 12, 31)
606 self.assertEqual(d, e)
607 self.assertEqual(hash(d), hash(e))
608
609 dic = {d: 1}
610 dic[e] = 2
611 self.assertEqual(len(dic), 1)
612 self.assertEqual(dic[d], 2)
613 self.assertEqual(dic[e], 2)
614
615 d = self.theclass(2001, 1, 1)
616 # same thing
617 e = self.theclass(2001, 1, 1)
618 self.assertEqual(d, e)
619 self.assertEqual(hash(d), hash(e))
620
621 dic = {d: 1}
622 dic[e] = 2
623 self.assertEqual(len(dic), 1)
624 self.assertEqual(dic[d], 2)
625 self.assertEqual(dic[e], 2)
626
627 def test_computations(self):
628 a = self.theclass(2002, 1, 31)
629 b = self.theclass(1956, 1, 31)
630
631 diff = a-b
632 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
633 self.assertEqual(diff.seconds, 0)
634 self.assertEqual(diff.microseconds, 0)
635
636 day = timedelta(1)
637 week = timedelta(7)
638 a = self.theclass(2002, 3, 2)
639 self.assertEqual(a + day, self.theclass(2002, 3, 3))
640 self.assertEqual(day + a, self.theclass(2002, 3, 3))
641 self.assertEqual(a - day, self.theclass(2002, 3, 1))
642 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
643 self.assertEqual(a + week, self.theclass(2002, 3, 9))
644 self.assertEqual(a - week, self.theclass(2002, 2, 23))
645 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
646 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
647 self.assertEqual((a + week) - a, week)
648 self.assertEqual((a + day) - a, day)
649 self.assertEqual((a - week) - a, -week)
650 self.assertEqual((a - day) - a, -day)
651 self.assertEqual(a - (a + week), -week)
652 self.assertEqual(a - (a + day), -day)
653 self.assertEqual(a - (a - week), week)
654 self.assertEqual(a - (a - day), day)
655
656 # Add/sub ints, longs, floats should be illegal
657 for i in 1, 1L, 1.0:
658 self.assertRaises(TypeError, lambda: a+i)
659 self.assertRaises(TypeError, lambda: a-i)
660 self.assertRaises(TypeError, lambda: i+a)
661 self.assertRaises(TypeError, lambda: i-a)
662
663 # delta - date is senseless.
664 self.assertRaises(TypeError, lambda: day - a)
665 # mixing date and (delta or date) via * or // is senseless
666 self.assertRaises(TypeError, lambda: day * a)
667 self.assertRaises(TypeError, lambda: a * day)
668 self.assertRaises(TypeError, lambda: day // a)
669 self.assertRaises(TypeError, lambda: a // day)
670 self.assertRaises(TypeError, lambda: a * a)
671 self.assertRaises(TypeError, lambda: a // a)
672 # date + date is senseless
673 self.assertRaises(TypeError, lambda: a + a)
674
675 def test_overflow(self):
676 tiny = self.theclass.resolution
677
678 dt = self.theclass.min + tiny
679 dt -= tiny # no problem
680 self.assertRaises(OverflowError, dt.__sub__, tiny)
681 self.assertRaises(OverflowError, dt.__add__, -tiny)
682
683 dt = self.theclass.max - tiny
684 dt += tiny # no problem
685 self.assertRaises(OverflowError, dt.__add__, tiny)
686 self.assertRaises(OverflowError, dt.__sub__, -tiny)
687
688 def test_fromtimestamp(self):
689 import time
690
691 # Try an arbitrary fixed value.
692 year, month, day = 1999, 9, 19
693 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
694 d = self.theclass.fromtimestamp(ts)
695 self.assertEqual(d.year, year)
696 self.assertEqual(d.month, month)
697 self.assertEqual(d.day, day)
698
699 def test_today(self):
700 import time
701
702 # We claim that today() is like fromtimestamp(time.time()), so
703 # prove it.
704 for dummy in range(3):
705 today = self.theclass.today()
706 ts = time.time()
707 todayagain = self.theclass.fromtimestamp(ts)
708 if today == todayagain:
709 break
710 # There are several legit reasons that could fail:
711 # 1. It recently became midnight, between the today() and the
712 # time() calls.
713 # 2. The platform time() has such fine resolution that we'll
714 # never get the same value twice.
715 # 3. The platform time() has poor resolution, and we just
716 # happened to call today() right before a resolution quantum
717 # boundary.
718 # 4. The system clock got fiddled between calls.
719 # In any case, wait a little while and try again.
720 time.sleep(0.1)
721
722 # It worked or it didn't. If it didn't, assume it's reason #2, and
723 # let the test pass if they're within half a second of each other.
724 self.failUnless(today == todayagain or
725 abs(todayagain - today) < timedelta(seconds=0.5))
726
727 def test_weekday(self):
728 for i in range(7):
729 # March 4, 2002 is a Monday
730 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
731 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
732 # January 2, 1956 is a Monday
733 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
734 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
735
736 def test_isocalendar(self):
737 # Check examples from
738 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
739 for i in range(7):
740 d = self.theclass(2003, 12, 22+i)
741 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
742 d = self.theclass(2003, 12, 29) + timedelta(i)
743 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
744 d = self.theclass(2004, 1, 5+i)
745 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
746 d = self.theclass(2009, 12, 21+i)
747 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
748 d = self.theclass(2009, 12, 28) + timedelta(i)
749 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
750 d = self.theclass(2010, 1, 4+i)
751 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
752
753 def test_iso_long_years(self):
754 # Calculate long ISO years and compare to table from
755 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
756 ISO_LONG_YEARS_TABLE = """
757 4 32 60 88
758 9 37 65 93
759 15 43 71 99
760 20 48 76
761 26 54 82
762
763 105 133 161 189
764 111 139 167 195
765 116 144 172
766 122 150 178
767 128 156 184
768
769 201 229 257 285
770 207 235 263 291
771 212 240 268 296
772 218 246 274
773 224 252 280
774
775 303 331 359 387
776 308 336 364 392
777 314 342 370 398
778 320 348 376
779 325 353 381
780 """
781 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
782 iso_long_years.sort()
783 L = []
784 for i in range(400):
785 d = self.theclass(2000+i, 12, 31)
786 d1 = self.theclass(1600+i, 12, 31)
787 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
788 if d.isocalendar()[1] == 53:
789 L.append(i)
790 self.assertEqual(L, iso_long_years)
791
792 def test_isoformat(self):
793 t = self.theclass(2, 3, 2)
794 self.assertEqual(t.isoformat(), "0002-03-02")
795
796 def test_ctime(self):
797 t = self.theclass(2002, 3, 2)
798 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
799
800 def test_strftime(self):
801 t = self.theclass(2005, 3, 2)
802 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
803
804 self.assertRaises(TypeError, t.strftime) # needs an arg
805 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
806 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
807
808 # A naive object replaces %z and %Z w/ empty strings.
809 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
810
811 def test_resolution_info(self):
812 self.assert_(isinstance(self.theclass.min, self.theclass))
813 self.assert_(isinstance(self.theclass.max, self.theclass))
814 self.assert_(isinstance(self.theclass.resolution, timedelta))
815 self.assert_(self.theclass.max > self.theclass.min)
816
817 def test_extreme_timedelta(self):
818 big = self.theclass.max - self.theclass.min
819 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
820 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
821 # n == 315537897599999999 ~= 2**58.13
822 justasbig = timedelta(0, 0, n)
823 self.assertEqual(big, justasbig)
824 self.assertEqual(self.theclass.min + big, self.theclass.max)
825 self.assertEqual(self.theclass.max - big, self.theclass.min)
826
827 def test_timetuple(self):
828 for i in range(7):
829 # January 2, 1956 is a Monday (0)
830 d = self.theclass(1956, 1, 2+i)
831 t = d.timetuple()
832 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
833 # February 1, 1956 is a Wednesday (2)
834 d = self.theclass(1956, 2, 1+i)
835 t = d.timetuple()
836 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
837 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
838 # of the year.
839 d = self.theclass(1956, 3, 1+i)
840 t = d.timetuple()
841 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
842 self.assertEqual(t.tm_year, 1956)
843 self.assertEqual(t.tm_mon, 3)
844 self.assertEqual(t.tm_mday, 1+i)
845 self.assertEqual(t.tm_hour, 0)
846 self.assertEqual(t.tm_min, 0)
847 self.assertEqual(t.tm_sec, 0)
848 self.assertEqual(t.tm_wday, (3+i)%7)
849 self.assertEqual(t.tm_yday, 61+i)
850 self.assertEqual(t.tm_isdst, -1)
851
852 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000853 args = 6, 7, 23
854 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000855 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000856 green = pickler.dumps(orig, proto)
857 derived = unpickler.loads(green)
858 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000859
860 def test_compare(self):
861 t1 = self.theclass(2, 3, 4)
862 t2 = self.theclass(2, 3, 4)
863 self.failUnless(t1 == t2)
864 self.failUnless(t1 <= t2)
865 self.failUnless(t1 >= t2)
866 self.failUnless(not t1 != t2)
867 self.failUnless(not t1 < t2)
868 self.failUnless(not t1 > t2)
869 self.assertEqual(cmp(t1, t2), 0)
870 self.assertEqual(cmp(t2, t1), 0)
871
872 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
873 t2 = self.theclass(*args) # this is larger than t1
874 self.failUnless(t1 < t2)
875 self.failUnless(t2 > t1)
876 self.failUnless(t1 <= t2)
877 self.failUnless(t2 >= t1)
878 self.failUnless(t1 != t2)
879 self.failUnless(t2 != t1)
880 self.failUnless(not t1 == t2)
881 self.failUnless(not t2 == t1)
882 self.failUnless(not t1 > t2)
883 self.failUnless(not t2 < t1)
884 self.failUnless(not t1 >= t2)
885 self.failUnless(not t2 <= t1)
886 self.assertEqual(cmp(t1, t2), -1)
887 self.assertEqual(cmp(t2, t1), 1)
888
Tim Peters68124bb2003-02-08 03:46:31 +0000889 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000890 self.assertEqual(t1 == badarg, False)
891 self.assertEqual(t1 != badarg, True)
892 self.assertEqual(badarg == t1, False)
893 self.assertEqual(badarg != t1, True)
894
Tim Peters2a799bf2002-12-16 20:18:38 +0000895 self.assertRaises(TypeError, lambda: t1 < badarg)
896 self.assertRaises(TypeError, lambda: t1 > badarg)
897 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000898 self.assertRaises(TypeError, lambda: badarg <= t1)
899 self.assertRaises(TypeError, lambda: badarg < t1)
900 self.assertRaises(TypeError, lambda: badarg > t1)
901 self.assertRaises(TypeError, lambda: badarg >= t1)
902
Tim Peters8d81a012003-01-24 22:36:34 +0000903 def test_mixed_compare(self):
904 our = self.theclass(2000, 4, 5)
905 self.assertRaises(TypeError, cmp, our, 1)
906 self.assertRaises(TypeError, cmp, 1, our)
907
908 class AnotherDateTimeClass(object):
909 def __cmp__(self, other):
910 # Return "equal" so calling this can't be confused with
911 # compare-by-address (which never says "equal" for distinct
912 # objects).
913 return 0
914
915 # This still errors, because date and datetime comparison raise
916 # TypeError instead of NotImplemented when they don't know what to
917 # do, in order to stop comparison from falling back to the default
918 # compare-by-address.
919 their = AnotherDateTimeClass()
920 self.assertRaises(TypeError, cmp, our, their)
921 # Oops: The next stab raises TypeError in the C implementation,
922 # but not in the Python implementation of datetime. The difference
923 # is due to that the Python implementation defines __cmp__ but
924 # the C implementation defines tp_richcompare. This is more pain
925 # to fix than it's worth, so commenting out the test.
926 # self.assertEqual(cmp(their, our), 0)
927
928 # But date and datetime comparison return NotImplemented instead if the
929 # other object has a timetuple attr. This gives the other object a
930 # chance to do the comparison.
931 class Comparable(AnotherDateTimeClass):
932 def timetuple(self):
933 return ()
934
935 their = Comparable()
936 self.assertEqual(cmp(our, their), 0)
937 self.assertEqual(cmp(their, our), 0)
938 self.failUnless(our == their)
939 self.failUnless(their == our)
940
Tim Peters2a799bf2002-12-16 20:18:38 +0000941 def test_bool(self):
942 # All dates are considered true.
943 self.failUnless(self.theclass.min)
944 self.failUnless(self.theclass.max)
945
Tim Petersd6844152002-12-22 20:58:42 +0000946 def test_srftime_out_of_range(self):
947 # For nasty technical reasons, we can't handle years before 1900.
948 cls = self.theclass
949 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
950 for y in 1, 49, 51, 99, 100, 1000, 1899:
951 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000952
953 def test_replace(self):
954 cls = self.theclass
955 args = [1, 2, 3]
956 base = cls(*args)
957 self.assertEqual(base, base.replace())
958
959 i = 0
960 for name, newval in (("year", 2),
961 ("month", 3),
962 ("day", 4)):
963 newargs = args[:]
964 newargs[i] = newval
965 expected = cls(*newargs)
966 got = base.replace(**{name: newval})
967 self.assertEqual(expected, got)
968 i += 1
969
970 # Out of bounds.
971 base = cls(2000, 2, 29)
972 self.assertRaises(ValueError, base.replace, year=2001)
973
Tim Petersa98924a2003-05-17 05:55:19 +0000974 def test_subclass_date(self):
975
976 class C(self.theclass):
977 theAnswer = 42
978
979 def __new__(cls, *args, **kws):
980 temp = kws.copy()
981 extra = temp.pop('extra')
982 result = self.theclass.__new__(cls, *args, **temp)
983 result.extra = extra
984 return result
985
986 def newmeth(self, start):
987 return start + self.year + self.month
988
989 args = 2003, 4, 14
990
991 dt1 = self.theclass(*args)
992 dt2 = C(*args, **{'extra': 7})
993
994 self.assertEqual(dt2.__class__, C)
995 self.assertEqual(dt2.theAnswer, 42)
996 self.assertEqual(dt2.extra, 7)
997 self.assertEqual(dt1.toordinal(), dt2.toordinal())
998 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
999
Tim Peterseb1a4962003-05-17 02:25:20 +00001000
Tim Peters2a799bf2002-12-16 20:18:38 +00001001#############################################################################
1002# datetime tests
1003
1004class TestDateTime(TestDate):
1005
1006 theclass = datetime
1007
1008 def test_basic_attributes(self):
1009 dt = self.theclass(2002, 3, 1, 12, 0)
1010 self.assertEqual(dt.year, 2002)
1011 self.assertEqual(dt.month, 3)
1012 self.assertEqual(dt.day, 1)
1013 self.assertEqual(dt.hour, 12)
1014 self.assertEqual(dt.minute, 0)
1015 self.assertEqual(dt.second, 0)
1016 self.assertEqual(dt.microsecond, 0)
1017
1018 def test_basic_attributes_nonzero(self):
1019 # Make sure all attributes are non-zero so bugs in
1020 # bit-shifting access show up.
1021 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1022 self.assertEqual(dt.year, 2002)
1023 self.assertEqual(dt.month, 3)
1024 self.assertEqual(dt.day, 1)
1025 self.assertEqual(dt.hour, 12)
1026 self.assertEqual(dt.minute, 59)
1027 self.assertEqual(dt.second, 59)
1028 self.assertEqual(dt.microsecond, 8000)
1029
1030 def test_roundtrip(self):
1031 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1032 self.theclass.now()):
1033 # Verify dt -> string -> datetime identity.
1034 s = repr(dt)
1035 self.failUnless(s.startswith('datetime.'))
1036 s = s[9:]
1037 dt2 = eval(s)
1038 self.assertEqual(dt, dt2)
1039
1040 # Verify identity via reconstructing from pieces.
1041 dt2 = self.theclass(dt.year, dt.month, dt.day,
1042 dt.hour, dt.minute, dt.second,
1043 dt.microsecond)
1044 self.assertEqual(dt, dt2)
1045
1046 def test_isoformat(self):
1047 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1048 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1049 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1050 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1051 # str is ISO format with the separator forced to a blank.
1052 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1053
1054 t = self.theclass(2, 3, 2)
1055 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1056 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1057 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1058 # str is ISO format with the separator forced to a blank.
1059 self.assertEqual(str(t), "0002-03-02 00:00:00")
1060
1061 def test_more_ctime(self):
1062 # Test fields that TestDate doesn't touch.
1063 import time
1064
1065 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1066 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1067 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1068 # out. The difference is that t.ctime() produces " 2" for the day,
1069 # but platform ctime() produces "02" for the day. According to
1070 # C99, t.ctime() is correct here.
1071 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1072
1073 # So test a case where that difference doesn't matter.
1074 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1075 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1076
1077 def test_tz_independent_comparing(self):
1078 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1079 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1080 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1081 self.assertEqual(dt1, dt3)
1082 self.assert_(dt2 > dt3)
1083
1084 # Make sure comparison doesn't forget microseconds, and isn't done
1085 # via comparing a float timestamp (an IEEE double doesn't have enough
1086 # precision to span microsecond resolution across years 1 thru 9999,
1087 # so comparing via timestamp necessarily calls some distinct values
1088 # equal).
1089 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1090 us = timedelta(microseconds=1)
1091 dt2 = dt1 + us
1092 self.assertEqual(dt2 - dt1, us)
1093 self.assert_(dt1 < dt2)
1094
1095 def test_bad_constructor_arguments(self):
1096 # bad years
1097 self.theclass(MINYEAR, 1, 1) # no exception
1098 self.theclass(MAXYEAR, 1, 1) # no exception
1099 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1100 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1101 # bad months
1102 self.theclass(2000, 1, 1) # no exception
1103 self.theclass(2000, 12, 1) # no exception
1104 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1105 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1106 # bad days
1107 self.theclass(2000, 2, 29) # no exception
1108 self.theclass(2004, 2, 29) # no exception
1109 self.theclass(2400, 2, 29) # no exception
1110 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1111 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1112 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1113 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1114 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1115 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1116 # bad hours
1117 self.theclass(2000, 1, 31, 0) # no exception
1118 self.theclass(2000, 1, 31, 23) # no exception
1119 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1120 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1121 # bad minutes
1122 self.theclass(2000, 1, 31, 23, 0) # no exception
1123 self.theclass(2000, 1, 31, 23, 59) # no exception
1124 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1125 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1126 # bad seconds
1127 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1128 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1129 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1130 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1131 # bad microseconds
1132 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1133 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1134 self.assertRaises(ValueError, self.theclass,
1135 2000, 1, 31, 23, 59, 59, -1)
1136 self.assertRaises(ValueError, self.theclass,
1137 2000, 1, 31, 23, 59, 59,
1138 1000000)
1139
1140 def test_hash_equality(self):
1141 d = self.theclass(2000, 12, 31, 23, 30, 17)
1142 e = self.theclass(2000, 12, 31, 23, 30, 17)
1143 self.assertEqual(d, e)
1144 self.assertEqual(hash(d), hash(e))
1145
1146 dic = {d: 1}
1147 dic[e] = 2
1148 self.assertEqual(len(dic), 1)
1149 self.assertEqual(dic[d], 2)
1150 self.assertEqual(dic[e], 2)
1151
1152 d = self.theclass(2001, 1, 1, 0, 5, 17)
1153 e = self.theclass(2001, 1, 1, 0, 5, 17)
1154 self.assertEqual(d, e)
1155 self.assertEqual(hash(d), hash(e))
1156
1157 dic = {d: 1}
1158 dic[e] = 2
1159 self.assertEqual(len(dic), 1)
1160 self.assertEqual(dic[d], 2)
1161 self.assertEqual(dic[e], 2)
1162
1163 def test_computations(self):
1164 a = self.theclass(2002, 1, 31)
1165 b = self.theclass(1956, 1, 31)
1166 diff = a-b
1167 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1168 self.assertEqual(diff.seconds, 0)
1169 self.assertEqual(diff.microseconds, 0)
1170 a = self.theclass(2002, 3, 2, 17, 6)
1171 millisec = timedelta(0, 0, 1000)
1172 hour = timedelta(0, 3600)
1173 day = timedelta(1)
1174 week = timedelta(7)
1175 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1176 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1177 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1178 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1179 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1180 self.assertEqual(a - hour, a + -hour)
1181 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1182 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1183 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1184 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1185 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1186 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1187 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1188 self.assertEqual((a + week) - a, week)
1189 self.assertEqual((a + day) - a, day)
1190 self.assertEqual((a + hour) - a, hour)
1191 self.assertEqual((a + millisec) - a, millisec)
1192 self.assertEqual((a - week) - a, -week)
1193 self.assertEqual((a - day) - a, -day)
1194 self.assertEqual((a - hour) - a, -hour)
1195 self.assertEqual((a - millisec) - a, -millisec)
1196 self.assertEqual(a - (a + week), -week)
1197 self.assertEqual(a - (a + day), -day)
1198 self.assertEqual(a - (a + hour), -hour)
1199 self.assertEqual(a - (a + millisec), -millisec)
1200 self.assertEqual(a - (a - week), week)
1201 self.assertEqual(a - (a - day), day)
1202 self.assertEqual(a - (a - hour), hour)
1203 self.assertEqual(a - (a - millisec), millisec)
1204 self.assertEqual(a + (week + day + hour + millisec),
1205 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1206 self.assertEqual(a + (week + day + hour + millisec),
1207 (((a + week) + day) + hour) + millisec)
1208 self.assertEqual(a - (week + day + hour + millisec),
1209 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1210 self.assertEqual(a - (week + day + hour + millisec),
1211 (((a - week) - day) - hour) - millisec)
1212 # Add/sub ints, longs, floats should be illegal
1213 for i in 1, 1L, 1.0:
1214 self.assertRaises(TypeError, lambda: a+i)
1215 self.assertRaises(TypeError, lambda: a-i)
1216 self.assertRaises(TypeError, lambda: i+a)
1217 self.assertRaises(TypeError, lambda: i-a)
1218
1219 # delta - datetime is senseless.
1220 self.assertRaises(TypeError, lambda: day - a)
1221 # mixing datetime and (delta or datetime) via * or // is senseless
1222 self.assertRaises(TypeError, lambda: day * a)
1223 self.assertRaises(TypeError, lambda: a * day)
1224 self.assertRaises(TypeError, lambda: day // a)
1225 self.assertRaises(TypeError, lambda: a // day)
1226 self.assertRaises(TypeError, lambda: a * a)
1227 self.assertRaises(TypeError, lambda: a // a)
1228 # datetime + datetime is senseless
1229 self.assertRaises(TypeError, lambda: a + a)
1230
1231 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001232 args = 6, 7, 23, 20, 59, 1, 64**2
1233 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001234 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001235 green = pickler.dumps(orig, proto)
1236 derived = unpickler.loads(green)
1237 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001238
Guido van Rossum275666f2003-02-07 21:49:01 +00001239 def test_more_pickling(self):
1240 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1241 s = pickle.dumps(a)
1242 b = pickle.loads(s)
1243 self.assertEqual(b.year, 2003)
1244 self.assertEqual(b.month, 2)
1245 self.assertEqual(b.day, 7)
1246
Tim Peters2a799bf2002-12-16 20:18:38 +00001247 def test_more_compare(self):
1248 # The test_compare() inherited from TestDate covers the error cases.
1249 # We just want to test lexicographic ordering on the members datetime
1250 # has that date lacks.
1251 args = [2000, 11, 29, 20, 58, 16, 999998]
1252 t1 = self.theclass(*args)
1253 t2 = self.theclass(*args)
1254 self.failUnless(t1 == t2)
1255 self.failUnless(t1 <= t2)
1256 self.failUnless(t1 >= t2)
1257 self.failUnless(not t1 != t2)
1258 self.failUnless(not t1 < t2)
1259 self.failUnless(not t1 > t2)
1260 self.assertEqual(cmp(t1, t2), 0)
1261 self.assertEqual(cmp(t2, t1), 0)
1262
1263 for i in range(len(args)):
1264 newargs = args[:]
1265 newargs[i] = args[i] + 1
1266 t2 = self.theclass(*newargs) # this is larger than t1
1267 self.failUnless(t1 < t2)
1268 self.failUnless(t2 > t1)
1269 self.failUnless(t1 <= t2)
1270 self.failUnless(t2 >= t1)
1271 self.failUnless(t1 != t2)
1272 self.failUnless(t2 != t1)
1273 self.failUnless(not t1 == t2)
1274 self.failUnless(not t2 == t1)
1275 self.failUnless(not t1 > t2)
1276 self.failUnless(not t2 < t1)
1277 self.failUnless(not t1 >= t2)
1278 self.failUnless(not t2 <= t1)
1279 self.assertEqual(cmp(t1, t2), -1)
1280 self.assertEqual(cmp(t2, t1), 1)
1281
1282
1283 # A helper for timestamp constructor tests.
1284 def verify_field_equality(self, expected, got):
1285 self.assertEqual(expected.tm_year, got.year)
1286 self.assertEqual(expected.tm_mon, got.month)
1287 self.assertEqual(expected.tm_mday, got.day)
1288 self.assertEqual(expected.tm_hour, got.hour)
1289 self.assertEqual(expected.tm_min, got.minute)
1290 self.assertEqual(expected.tm_sec, got.second)
1291
1292 def test_fromtimestamp(self):
1293 import time
1294
1295 ts = time.time()
1296 expected = time.localtime(ts)
1297 got = self.theclass.fromtimestamp(ts)
1298 self.verify_field_equality(expected, got)
1299
1300 def test_utcfromtimestamp(self):
1301 import time
1302
1303 ts = time.time()
1304 expected = time.gmtime(ts)
1305 got = self.theclass.utcfromtimestamp(ts)
1306 self.verify_field_equality(expected, got)
1307
1308 def test_utcnow(self):
1309 import time
1310
1311 # Call it a success if utcnow() and utcfromtimestamp() are within
1312 # a second of each other.
1313 tolerance = timedelta(seconds=1)
1314 for dummy in range(3):
1315 from_now = self.theclass.utcnow()
1316 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1317 if abs(from_timestamp - from_now) <= tolerance:
1318 break
1319 # Else try again a few times.
1320 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1321
1322 def test_more_timetuple(self):
1323 # This tests fields beyond those tested by the TestDate.test_timetuple.
1324 t = self.theclass(2004, 12, 31, 6, 22, 33)
1325 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1326 self.assertEqual(t.timetuple(),
1327 (t.year, t.month, t.day,
1328 t.hour, t.minute, t.second,
1329 t.weekday(),
1330 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1331 -1))
1332 tt = t.timetuple()
1333 self.assertEqual(tt.tm_year, t.year)
1334 self.assertEqual(tt.tm_mon, t.month)
1335 self.assertEqual(tt.tm_mday, t.day)
1336 self.assertEqual(tt.tm_hour, t.hour)
1337 self.assertEqual(tt.tm_min, t.minute)
1338 self.assertEqual(tt.tm_sec, t.second)
1339 self.assertEqual(tt.tm_wday, t.weekday())
1340 self.assertEqual(tt.tm_yday, t.toordinal() -
1341 date(t.year, 1, 1).toordinal() + 1)
1342 self.assertEqual(tt.tm_isdst, -1)
1343
1344 def test_more_strftime(self):
1345 # This tests fields beyond those tested by the TestDate.test_strftime.
1346 t = self.theclass(2004, 12, 31, 6, 22, 33)
1347 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1348 "12 31 04 33 22 06 366")
1349
1350 def test_extract(self):
1351 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1352 self.assertEqual(dt.date(), date(2002, 3, 4))
1353 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1354
1355 def test_combine(self):
1356 d = date(2002, 3, 4)
1357 t = time(18, 45, 3, 1234)
1358 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1359 combine = self.theclass.combine
1360 dt = combine(d, t)
1361 self.assertEqual(dt, expected)
1362
1363 dt = combine(time=t, date=d)
1364 self.assertEqual(dt, expected)
1365
1366 self.assertEqual(d, dt.date())
1367 self.assertEqual(t, dt.time())
1368 self.assertEqual(dt, combine(dt.date(), dt.time()))
1369
1370 self.assertRaises(TypeError, combine) # need an arg
1371 self.assertRaises(TypeError, combine, d) # need two args
1372 self.assertRaises(TypeError, combine, t, d) # args reversed
1373 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1374 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1375
Tim Peters12bf3392002-12-24 05:41:27 +00001376 def test_replace(self):
1377 cls = self.theclass
1378 args = [1, 2, 3, 4, 5, 6, 7]
1379 base = cls(*args)
1380 self.assertEqual(base, base.replace())
1381
1382 i = 0
1383 for name, newval in (("year", 2),
1384 ("month", 3),
1385 ("day", 4),
1386 ("hour", 5),
1387 ("minute", 6),
1388 ("second", 7),
1389 ("microsecond", 8)):
1390 newargs = args[:]
1391 newargs[i] = newval
1392 expected = cls(*newargs)
1393 got = base.replace(**{name: newval})
1394 self.assertEqual(expected, got)
1395 i += 1
1396
1397 # Out of bounds.
1398 base = cls(2000, 2, 29)
1399 self.assertRaises(ValueError, base.replace, year=2001)
1400
Tim Peters80475bb2002-12-25 07:40:55 +00001401 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001402 # Pretty boring! The TZ test is more interesting here. astimezone()
1403 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001404 dt = self.theclass.now()
1405 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001406 self.assertRaises(TypeError, dt.astimezone) # not enough args
1407 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1408 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001409 self.assertRaises(ValueError, dt.astimezone, f) # naive
1410 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001411
Tim Peters52dcce22003-01-23 16:36:11 +00001412 class Bogus(tzinfo):
1413 def utcoffset(self, dt): return None
1414 def dst(self, dt): return timedelta(0)
1415 bog = Bogus()
1416 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1417
1418 class AlsoBogus(tzinfo):
1419 def utcoffset(self, dt): return timedelta(0)
1420 def dst(self, dt): return None
1421 alsobog = AlsoBogus()
1422 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001423
Tim Petersa98924a2003-05-17 05:55:19 +00001424 def test_subclass_datetime(self):
1425
1426 class C(self.theclass):
1427 theAnswer = 42
1428
1429 def __new__(cls, *args, **kws):
1430 temp = kws.copy()
1431 extra = temp.pop('extra')
1432 result = self.theclass.__new__(cls, *args, **temp)
1433 result.extra = extra
1434 return result
1435
1436 def newmeth(self, start):
1437 return start + self.year + self.month + self.second
1438
1439 args = 2003, 4, 14, 12, 13, 41
1440
1441 dt1 = self.theclass(*args)
1442 dt2 = C(*args, **{'extra': 7})
1443
1444 self.assertEqual(dt2.__class__, C)
1445 self.assertEqual(dt2.theAnswer, 42)
1446 self.assertEqual(dt2.extra, 7)
1447 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1448 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1449 dt1.second - 7)
1450
Tim Peters07534a62003-02-07 22:50:28 +00001451class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001452
1453 theclass = time
1454
1455 def test_basic_attributes(self):
1456 t = self.theclass(12, 0)
1457 self.assertEqual(t.hour, 12)
1458 self.assertEqual(t.minute, 0)
1459 self.assertEqual(t.second, 0)
1460 self.assertEqual(t.microsecond, 0)
1461
1462 def test_basic_attributes_nonzero(self):
1463 # Make sure all attributes are non-zero so bugs in
1464 # bit-shifting access show up.
1465 t = self.theclass(12, 59, 59, 8000)
1466 self.assertEqual(t.hour, 12)
1467 self.assertEqual(t.minute, 59)
1468 self.assertEqual(t.second, 59)
1469 self.assertEqual(t.microsecond, 8000)
1470
1471 def test_roundtrip(self):
1472 t = self.theclass(1, 2, 3, 4)
1473
1474 # Verify t -> string -> time identity.
1475 s = repr(t)
1476 self.failUnless(s.startswith('datetime.'))
1477 s = s[9:]
1478 t2 = eval(s)
1479 self.assertEqual(t, t2)
1480
1481 # Verify identity via reconstructing from pieces.
1482 t2 = self.theclass(t.hour, t.minute, t.second,
1483 t.microsecond)
1484 self.assertEqual(t, t2)
1485
1486 def test_comparing(self):
1487 args = [1, 2, 3, 4]
1488 t1 = self.theclass(*args)
1489 t2 = self.theclass(*args)
1490 self.failUnless(t1 == t2)
1491 self.failUnless(t1 <= t2)
1492 self.failUnless(t1 >= t2)
1493 self.failUnless(not t1 != t2)
1494 self.failUnless(not t1 < t2)
1495 self.failUnless(not t1 > t2)
1496 self.assertEqual(cmp(t1, t2), 0)
1497 self.assertEqual(cmp(t2, t1), 0)
1498
1499 for i in range(len(args)):
1500 newargs = args[:]
1501 newargs[i] = args[i] + 1
1502 t2 = self.theclass(*newargs) # this is larger than t1
1503 self.failUnless(t1 < t2)
1504 self.failUnless(t2 > t1)
1505 self.failUnless(t1 <= t2)
1506 self.failUnless(t2 >= t1)
1507 self.failUnless(t1 != t2)
1508 self.failUnless(t2 != t1)
1509 self.failUnless(not t1 == t2)
1510 self.failUnless(not t2 == t1)
1511 self.failUnless(not t1 > t2)
1512 self.failUnless(not t2 < t1)
1513 self.failUnless(not t1 >= t2)
1514 self.failUnless(not t2 <= t1)
1515 self.assertEqual(cmp(t1, t2), -1)
1516 self.assertEqual(cmp(t2, t1), 1)
1517
Tim Peters68124bb2003-02-08 03:46:31 +00001518 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001519 self.assertEqual(t1 == badarg, False)
1520 self.assertEqual(t1 != badarg, True)
1521 self.assertEqual(badarg == t1, False)
1522 self.assertEqual(badarg != t1, True)
1523
Tim Peters2a799bf2002-12-16 20:18:38 +00001524 self.assertRaises(TypeError, lambda: t1 <= badarg)
1525 self.assertRaises(TypeError, lambda: t1 < badarg)
1526 self.assertRaises(TypeError, lambda: t1 > badarg)
1527 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001528 self.assertRaises(TypeError, lambda: badarg <= t1)
1529 self.assertRaises(TypeError, lambda: badarg < t1)
1530 self.assertRaises(TypeError, lambda: badarg > t1)
1531 self.assertRaises(TypeError, lambda: badarg >= t1)
1532
1533 def test_bad_constructor_arguments(self):
1534 # bad hours
1535 self.theclass(0, 0) # no exception
1536 self.theclass(23, 0) # no exception
1537 self.assertRaises(ValueError, self.theclass, -1, 0)
1538 self.assertRaises(ValueError, self.theclass, 24, 0)
1539 # bad minutes
1540 self.theclass(23, 0) # no exception
1541 self.theclass(23, 59) # no exception
1542 self.assertRaises(ValueError, self.theclass, 23, -1)
1543 self.assertRaises(ValueError, self.theclass, 23, 60)
1544 # bad seconds
1545 self.theclass(23, 59, 0) # no exception
1546 self.theclass(23, 59, 59) # no exception
1547 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1548 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1549 # bad microseconds
1550 self.theclass(23, 59, 59, 0) # no exception
1551 self.theclass(23, 59, 59, 999999) # no exception
1552 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1553 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1554
1555 def test_hash_equality(self):
1556 d = self.theclass(23, 30, 17)
1557 e = self.theclass(23, 30, 17)
1558 self.assertEqual(d, e)
1559 self.assertEqual(hash(d), hash(e))
1560
1561 dic = {d: 1}
1562 dic[e] = 2
1563 self.assertEqual(len(dic), 1)
1564 self.assertEqual(dic[d], 2)
1565 self.assertEqual(dic[e], 2)
1566
1567 d = self.theclass(0, 5, 17)
1568 e = self.theclass(0, 5, 17)
1569 self.assertEqual(d, e)
1570 self.assertEqual(hash(d), hash(e))
1571
1572 dic = {d: 1}
1573 dic[e] = 2
1574 self.assertEqual(len(dic), 1)
1575 self.assertEqual(dic[d], 2)
1576 self.assertEqual(dic[e], 2)
1577
1578 def test_isoformat(self):
1579 t = self.theclass(4, 5, 1, 123)
1580 self.assertEqual(t.isoformat(), "04:05:01.000123")
1581 self.assertEqual(t.isoformat(), str(t))
1582
1583 t = self.theclass()
1584 self.assertEqual(t.isoformat(), "00:00:00")
1585 self.assertEqual(t.isoformat(), str(t))
1586
1587 t = self.theclass(microsecond=1)
1588 self.assertEqual(t.isoformat(), "00:00:00.000001")
1589 self.assertEqual(t.isoformat(), str(t))
1590
1591 t = self.theclass(microsecond=10)
1592 self.assertEqual(t.isoformat(), "00:00:00.000010")
1593 self.assertEqual(t.isoformat(), str(t))
1594
1595 t = self.theclass(microsecond=100)
1596 self.assertEqual(t.isoformat(), "00:00:00.000100")
1597 self.assertEqual(t.isoformat(), str(t))
1598
1599 t = self.theclass(microsecond=1000)
1600 self.assertEqual(t.isoformat(), "00:00:00.001000")
1601 self.assertEqual(t.isoformat(), str(t))
1602
1603 t = self.theclass(microsecond=10000)
1604 self.assertEqual(t.isoformat(), "00:00:00.010000")
1605 self.assertEqual(t.isoformat(), str(t))
1606
1607 t = self.theclass(microsecond=100000)
1608 self.assertEqual(t.isoformat(), "00:00:00.100000")
1609 self.assertEqual(t.isoformat(), str(t))
1610
1611 def test_strftime(self):
1612 t = self.theclass(1, 2, 3, 4)
1613 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1614 # A naive object replaces %z and %Z with empty strings.
1615 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1616
1617 def test_str(self):
1618 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1619 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1620 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1621 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1622 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1623
1624 def test_repr(self):
1625 name = 'datetime.' + self.theclass.__name__
1626 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1627 "%s(1, 2, 3, 4)" % name)
1628 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1629 "%s(10, 2, 3, 4000)" % name)
1630 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1631 "%s(0, 2, 3, 400000)" % name)
1632 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1633 "%s(12, 2, 3)" % name)
1634 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1635 "%s(23, 15)" % name)
1636
1637 def test_resolution_info(self):
1638 self.assert_(isinstance(self.theclass.min, self.theclass))
1639 self.assert_(isinstance(self.theclass.max, self.theclass))
1640 self.assert_(isinstance(self.theclass.resolution, timedelta))
1641 self.assert_(self.theclass.max > self.theclass.min)
1642
1643 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001644 args = 20, 59, 16, 64**2
1645 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001646 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001647 green = pickler.dumps(orig, proto)
1648 derived = unpickler.loads(green)
1649 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001650
1651 def test_bool(self):
1652 cls = self.theclass
1653 self.failUnless(cls(1))
1654 self.failUnless(cls(0, 1))
1655 self.failUnless(cls(0, 0, 1))
1656 self.failUnless(cls(0, 0, 0, 1))
1657 self.failUnless(not cls(0))
1658 self.failUnless(not cls())
1659
Tim Peters12bf3392002-12-24 05:41:27 +00001660 def test_replace(self):
1661 cls = self.theclass
1662 args = [1, 2, 3, 4]
1663 base = cls(*args)
1664 self.assertEqual(base, base.replace())
1665
1666 i = 0
1667 for name, newval in (("hour", 5),
1668 ("minute", 6),
1669 ("second", 7),
1670 ("microsecond", 8)):
1671 newargs = args[:]
1672 newargs[i] = newval
1673 expected = cls(*newargs)
1674 got = base.replace(**{name: newval})
1675 self.assertEqual(expected, got)
1676 i += 1
1677
1678 # Out of bounds.
1679 base = cls(1)
1680 self.assertRaises(ValueError, base.replace, hour=24)
1681 self.assertRaises(ValueError, base.replace, minute=-1)
1682 self.assertRaises(ValueError, base.replace, second=100)
1683 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1684
Tim Petersa98924a2003-05-17 05:55:19 +00001685 def test_subclass_time(self):
1686
1687 class C(self.theclass):
1688 theAnswer = 42
1689
1690 def __new__(cls, *args, **kws):
1691 temp = kws.copy()
1692 extra = temp.pop('extra')
1693 result = self.theclass.__new__(cls, *args, **temp)
1694 result.extra = extra
1695 return result
1696
1697 def newmeth(self, start):
1698 return start + self.hour + self.second
1699
1700 args = 4, 5, 6
1701
1702 dt1 = self.theclass(*args)
1703 dt2 = C(*args, **{'extra': 7})
1704
1705 self.assertEqual(dt2.__class__, C)
1706 self.assertEqual(dt2.theAnswer, 42)
1707 self.assertEqual(dt2.extra, 7)
1708 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1709 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1710
Tim Peters855fe882002-12-22 03:43:39 +00001711# A mixin for classes with a tzinfo= argument. Subclasses must define
1712# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001713# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001714class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001715
Tim Petersbad8ff02002-12-30 20:52:32 +00001716 def test_argument_passing(self):
1717 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001718 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001719 class introspective(tzinfo):
1720 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001721 def utcoffset(self, dt):
1722 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001723 dst = utcoffset
1724
1725 obj = cls(1, 2, 3, tzinfo=introspective())
1726
Tim Peters0bf60bd2003-01-08 20:40:01 +00001727 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001728 self.assertEqual(obj.tzname(), expected)
1729
Tim Peters0bf60bd2003-01-08 20:40:01 +00001730 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001731 self.assertEqual(obj.utcoffset(), expected)
1732 self.assertEqual(obj.dst(), expected)
1733
Tim Peters855fe882002-12-22 03:43:39 +00001734 def test_bad_tzinfo_classes(self):
1735 cls = self.theclass
1736 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001737
Tim Peters855fe882002-12-22 03:43:39 +00001738 class NiceTry(object):
1739 def __init__(self): pass
1740 def utcoffset(self, dt): pass
1741 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1742
1743 class BetterTry(tzinfo):
1744 def __init__(self): pass
1745 def utcoffset(self, dt): pass
1746 b = BetterTry()
1747 t = cls(1, 1, 1, tzinfo=b)
1748 self.failUnless(t.tzinfo is b)
1749
1750 def test_utc_offset_out_of_bounds(self):
1751 class Edgy(tzinfo):
1752 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001753 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001754 def utcoffset(self, dt):
1755 return self.offset
1756
1757 cls = self.theclass
1758 for offset, legit in ((-1440, False),
1759 (-1439, True),
1760 (1439, True),
1761 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001762 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001763 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001764 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001765 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001766 else:
1767 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001768 if legit:
1769 aofs = abs(offset)
1770 h, m = divmod(aofs, 60)
1771 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001772 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001773 t = t.timetz()
1774 self.assertEqual(str(t), "01:02:03" + tag)
1775 else:
1776 self.assertRaises(ValueError, str, t)
1777
1778 def test_tzinfo_classes(self):
1779 cls = self.theclass
1780 class C1(tzinfo):
1781 def utcoffset(self, dt): return None
1782 def dst(self, dt): return None
1783 def tzname(self, dt): return None
1784 for t in (cls(1, 1, 1),
1785 cls(1, 1, 1, tzinfo=None),
1786 cls(1, 1, 1, tzinfo=C1())):
1787 self.failUnless(t.utcoffset() is None)
1788 self.failUnless(t.dst() is None)
1789 self.failUnless(t.tzname() is None)
1790
Tim Peters855fe882002-12-22 03:43:39 +00001791 class C3(tzinfo):
1792 def utcoffset(self, dt): return timedelta(minutes=-1439)
1793 def dst(self, dt): return timedelta(minutes=1439)
1794 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001795 t = cls(1, 1, 1, tzinfo=C3())
1796 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1797 self.assertEqual(t.dst(), timedelta(minutes=1439))
1798 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001799
1800 # Wrong types.
1801 class C4(tzinfo):
1802 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001803 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001804 def tzname(self, dt): return 0
1805 t = cls(1, 1, 1, tzinfo=C4())
1806 self.assertRaises(TypeError, t.utcoffset)
1807 self.assertRaises(TypeError, t.dst)
1808 self.assertRaises(TypeError, t.tzname)
1809
1810 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001811 class C6(tzinfo):
1812 def utcoffset(self, dt): return timedelta(hours=-24)
1813 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001814 t = cls(1, 1, 1, tzinfo=C6())
1815 self.assertRaises(ValueError, t.utcoffset)
1816 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001817
1818 # Not a whole number of minutes.
1819 class C7(tzinfo):
1820 def utcoffset(self, dt): return timedelta(seconds=61)
1821 def dst(self, dt): return timedelta(microseconds=-81)
1822 t = cls(1, 1, 1, tzinfo=C7())
1823 self.assertRaises(ValueError, t.utcoffset)
1824 self.assertRaises(ValueError, t.dst)
1825
Tim Peters4c0db782002-12-26 05:01:19 +00001826 def test_aware_compare(self):
1827 cls = self.theclass
1828
Tim Peters60c76e42002-12-27 00:41:11 +00001829 # Ensure that utcoffset() gets ignored if the comparands have
1830 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001831 class OperandDependentOffset(tzinfo):
1832 def utcoffset(self, t):
1833 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001834 # d0 and d1 equal after adjustment
1835 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001836 else:
Tim Peters397301e2003-01-02 21:28:08 +00001837 # d2 off in the weeds
1838 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001839
1840 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1841 d0 = base.replace(minute=3)
1842 d1 = base.replace(minute=9)
1843 d2 = base.replace(minute=11)
1844 for x in d0, d1, d2:
1845 for y in d0, d1, d2:
1846 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001847 expected = cmp(x.minute, y.minute)
1848 self.assertEqual(got, expected)
1849
1850 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001851 # Note that a time can't actually have an operand-depedent offset,
1852 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1853 # so skip this test for time.
1854 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001855 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1856 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1857 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1858 for x in d0, d1, d2:
1859 for y in d0, d1, d2:
1860 got = cmp(x, y)
1861 if (x is d0 or x is d1) and (y is d0 or y is d1):
1862 expected = 0
1863 elif x is y is d2:
1864 expected = 0
1865 elif x is d2:
1866 expected = -1
1867 else:
1868 assert y is d2
1869 expected = 1
1870 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001871
Tim Peters855fe882002-12-22 03:43:39 +00001872
Tim Peters0bf60bd2003-01-08 20:40:01 +00001873# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001874class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001875 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001876
1877 def test_empty(self):
1878 t = self.theclass()
1879 self.assertEqual(t.hour, 0)
1880 self.assertEqual(t.minute, 0)
1881 self.assertEqual(t.second, 0)
1882 self.assertEqual(t.microsecond, 0)
1883 self.failUnless(t.tzinfo is None)
1884
Tim Peters2a799bf2002-12-16 20:18:38 +00001885 def test_zones(self):
1886 est = FixedOffset(-300, "EST", 1)
1887 utc = FixedOffset(0, "UTC", -2)
1888 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001889 t1 = time( 7, 47, tzinfo=est)
1890 t2 = time(12, 47, tzinfo=utc)
1891 t3 = time(13, 47, tzinfo=met)
1892 t4 = time(microsecond=40)
1893 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001894
1895 self.assertEqual(t1.tzinfo, est)
1896 self.assertEqual(t2.tzinfo, utc)
1897 self.assertEqual(t3.tzinfo, met)
1898 self.failUnless(t4.tzinfo is None)
1899 self.assertEqual(t5.tzinfo, utc)
1900
Tim Peters855fe882002-12-22 03:43:39 +00001901 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1902 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1903 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001904 self.failUnless(t4.utcoffset() is None)
1905 self.assertRaises(TypeError, t1.utcoffset, "no args")
1906
1907 self.assertEqual(t1.tzname(), "EST")
1908 self.assertEqual(t2.tzname(), "UTC")
1909 self.assertEqual(t3.tzname(), "MET")
1910 self.failUnless(t4.tzname() is None)
1911 self.assertRaises(TypeError, t1.tzname, "no args")
1912
Tim Peters855fe882002-12-22 03:43:39 +00001913 self.assertEqual(t1.dst(), timedelta(minutes=1))
1914 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1915 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001916 self.failUnless(t4.dst() is None)
1917 self.assertRaises(TypeError, t1.dst, "no args")
1918
1919 self.assertEqual(hash(t1), hash(t2))
1920 self.assertEqual(hash(t1), hash(t3))
1921 self.assertEqual(hash(t2), hash(t3))
1922
1923 self.assertEqual(t1, t2)
1924 self.assertEqual(t1, t3)
1925 self.assertEqual(t2, t3)
1926 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1927 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1928 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1929
1930 self.assertEqual(str(t1), "07:47:00-05:00")
1931 self.assertEqual(str(t2), "12:47:00+00:00")
1932 self.assertEqual(str(t3), "13:47:00+01:00")
1933 self.assertEqual(str(t4), "00:00:00.000040")
1934 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1935
1936 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1937 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1938 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1939 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1940 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1941
Tim Peters0bf60bd2003-01-08 20:40:01 +00001942 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001943 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1944 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1945 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1946 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1947 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1948
1949 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1950 "07:47:00 %Z=EST %z=-0500")
1951 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1952 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1953
1954 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001955 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001956 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1957 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1958
Tim Petersb92bb712002-12-21 17:44:07 +00001959 # Check that an invalid tzname result raises an exception.
1960 class Badtzname(tzinfo):
1961 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001962 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001963 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1964 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001965
1966 def test_hash_edge_cases(self):
1967 # Offsets that overflow a basic time.
1968 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1969 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1970 self.assertEqual(hash(t1), hash(t2))
1971
1972 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1973 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1974 self.assertEqual(hash(t1), hash(t2))
1975
Tim Peters2a799bf2002-12-16 20:18:38 +00001976 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001977 # Try one without a tzinfo.
1978 args = 20, 59, 16, 64**2
1979 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001980 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001981 green = pickler.dumps(orig, proto)
1982 derived = unpickler.loads(green)
1983 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001984
1985 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001986 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001987 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001988 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001989 green = pickler.dumps(orig, proto)
1990 derived = unpickler.loads(green)
1991 self.assertEqual(orig, derived)
1992 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1993 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1994 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001995
1996 def test_more_bool(self):
1997 # Test cases with non-None tzinfo.
1998 cls = self.theclass
1999
2000 t = cls(0, tzinfo=FixedOffset(-300, ""))
2001 self.failUnless(t)
2002
2003 t = cls(5, tzinfo=FixedOffset(-300, ""))
2004 self.failUnless(t)
2005
2006 t = cls(5, tzinfo=FixedOffset(300, ""))
2007 self.failUnless(not t)
2008
2009 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2010 self.failUnless(not t)
2011
2012 # Mostly ensuring this doesn't overflow internally.
2013 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2014 self.failUnless(t)
2015
2016 # But this should yield a value error -- the utcoffset is bogus.
2017 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2018 self.assertRaises(ValueError, lambda: bool(t))
2019
2020 # Likewise.
2021 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2022 self.assertRaises(ValueError, lambda: bool(t))
2023
Tim Peters12bf3392002-12-24 05:41:27 +00002024 def test_replace(self):
2025 cls = self.theclass
2026 z100 = FixedOffset(100, "+100")
2027 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2028 args = [1, 2, 3, 4, z100]
2029 base = cls(*args)
2030 self.assertEqual(base, base.replace())
2031
2032 i = 0
2033 for name, newval in (("hour", 5),
2034 ("minute", 6),
2035 ("second", 7),
2036 ("microsecond", 8),
2037 ("tzinfo", zm200)):
2038 newargs = args[:]
2039 newargs[i] = newval
2040 expected = cls(*newargs)
2041 got = base.replace(**{name: newval})
2042 self.assertEqual(expected, got)
2043 i += 1
2044
2045 # Ensure we can get rid of a tzinfo.
2046 self.assertEqual(base.tzname(), "+100")
2047 base2 = base.replace(tzinfo=None)
2048 self.failUnless(base2.tzinfo is None)
2049 self.failUnless(base2.tzname() is None)
2050
2051 # Ensure we can add one.
2052 base3 = base2.replace(tzinfo=z100)
2053 self.assertEqual(base, base3)
2054 self.failUnless(base.tzinfo is base3.tzinfo)
2055
2056 # Out of bounds.
2057 base = cls(1)
2058 self.assertRaises(ValueError, base.replace, hour=24)
2059 self.assertRaises(ValueError, base.replace, minute=-1)
2060 self.assertRaises(ValueError, base.replace, second=100)
2061 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2062
Tim Peters60c76e42002-12-27 00:41:11 +00002063 def test_mixed_compare(self):
2064 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002065 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002066 self.assertEqual(t1, t2)
2067 t2 = t2.replace(tzinfo=None)
2068 self.assertEqual(t1, t2)
2069 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2070 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002071 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2072 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002073
Tim Peters0bf60bd2003-01-08 20:40:01 +00002074 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002075 class Varies(tzinfo):
2076 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002077 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002078 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002079 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002080 return self.offset
2081
2082 v = Varies()
2083 t1 = t2.replace(tzinfo=v)
2084 t2 = t2.replace(tzinfo=v)
2085 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2086 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2087 self.assertEqual(t1, t2)
2088
2089 # But if they're not identical, it isn't ignored.
2090 t2 = t2.replace(tzinfo=Varies())
2091 self.failUnless(t1 < t2) # t1's offset counter still going up
2092
Tim Petersa98924a2003-05-17 05:55:19 +00002093 def test_subclass_timetz(self):
2094
2095 class C(self.theclass):
2096 theAnswer = 42
2097
2098 def __new__(cls, *args, **kws):
2099 temp = kws.copy()
2100 extra = temp.pop('extra')
2101 result = self.theclass.__new__(cls, *args, **temp)
2102 result.extra = extra
2103 return result
2104
2105 def newmeth(self, start):
2106 return start + self.hour + self.second
2107
2108 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2109
2110 dt1 = self.theclass(*args)
2111 dt2 = C(*args, **{'extra': 7})
2112
2113 self.assertEqual(dt2.__class__, C)
2114 self.assertEqual(dt2.theAnswer, 42)
2115 self.assertEqual(dt2.extra, 7)
2116 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2117 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2118
Tim Peters4c0db782002-12-26 05:01:19 +00002119
Tim Peters0bf60bd2003-01-08 20:40:01 +00002120# Testing datetime objects with a non-None tzinfo.
2121
Tim Peters855fe882002-12-22 03:43:39 +00002122class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002123 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002124
2125 def test_trivial(self):
2126 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2127 self.assertEqual(dt.year, 1)
2128 self.assertEqual(dt.month, 2)
2129 self.assertEqual(dt.day, 3)
2130 self.assertEqual(dt.hour, 4)
2131 self.assertEqual(dt.minute, 5)
2132 self.assertEqual(dt.second, 6)
2133 self.assertEqual(dt.microsecond, 7)
2134 self.assertEqual(dt.tzinfo, None)
2135
2136 def test_even_more_compare(self):
2137 # The test_compare() and test_more_compare() inherited from TestDate
2138 # and TestDateTime covered non-tzinfo cases.
2139
2140 # Smallest possible after UTC adjustment.
2141 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2142 # Largest possible after UTC adjustment.
2143 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2144 tzinfo=FixedOffset(-1439, ""))
2145
2146 # Make sure those compare correctly, and w/o overflow.
2147 self.failUnless(t1 < t2)
2148 self.failUnless(t1 != t2)
2149 self.failUnless(t2 > t1)
2150
2151 self.failUnless(t1 == t1)
2152 self.failUnless(t2 == t2)
2153
2154 # Equal afer adjustment.
2155 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2156 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2157 self.assertEqual(t1, t2)
2158
2159 # Change t1 not to subtract a minute, and t1 should be larger.
2160 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2161 self.failUnless(t1 > t2)
2162
2163 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2164 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2165 self.failUnless(t1 < t2)
2166
2167 # Back to the original t1, but make seconds resolve it.
2168 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2169 second=1)
2170 self.failUnless(t1 > t2)
2171
2172 # Likewise, but make microseconds resolve it.
2173 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2174 microsecond=1)
2175 self.failUnless(t1 > t2)
2176
2177 # Make t2 naive and it should fail.
2178 t2 = self.theclass.min
2179 self.assertRaises(TypeError, lambda: t1 == t2)
2180 self.assertEqual(t2, t2)
2181
2182 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2183 class Naive(tzinfo):
2184 def utcoffset(self, dt): return None
2185 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2186 self.assertRaises(TypeError, lambda: t1 == t2)
2187 self.assertEqual(t2, t2)
2188
2189 # OTOH, it's OK to compare two of these mixing the two ways of being
2190 # naive.
2191 t1 = self.theclass(5, 6, 7)
2192 self.assertEqual(t1, t2)
2193
2194 # Try a bogus uctoffset.
2195 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002196 def utcoffset(self, dt):
2197 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002198 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2199 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002200 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002201
Tim Peters2a799bf2002-12-16 20:18:38 +00002202 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002203 # Try one without a tzinfo.
2204 args = 6, 7, 23, 20, 59, 1, 64**2
2205 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002206 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002207 green = pickler.dumps(orig, proto)
2208 derived = unpickler.loads(green)
2209 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002210
2211 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002212 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002213 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002214 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002215 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002216 green = pickler.dumps(orig, proto)
2217 derived = unpickler.loads(green)
2218 self.assertEqual(orig, derived)
2219 self.failUnless(isinstance(derived.tzinfo,
2220 PicklableFixedOffset))
2221 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2222 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002223
2224 def test_extreme_hashes(self):
2225 # If an attempt is made to hash these via subtracting the offset
2226 # then hashing a datetime object, OverflowError results. The
2227 # Python implementation used to blow up here.
2228 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2229 hash(t)
2230 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2231 tzinfo=FixedOffset(-1439, ""))
2232 hash(t)
2233
2234 # OTOH, an OOB offset should blow up.
2235 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2236 self.assertRaises(ValueError, hash, t)
2237
2238 def test_zones(self):
2239 est = FixedOffset(-300, "EST")
2240 utc = FixedOffset(0, "UTC")
2241 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002242 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2243 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2244 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002245 self.assertEqual(t1.tzinfo, est)
2246 self.assertEqual(t2.tzinfo, utc)
2247 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002248 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2249 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2250 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002251 self.assertEqual(t1.tzname(), "EST")
2252 self.assertEqual(t2.tzname(), "UTC")
2253 self.assertEqual(t3.tzname(), "MET")
2254 self.assertEqual(hash(t1), hash(t2))
2255 self.assertEqual(hash(t1), hash(t3))
2256 self.assertEqual(hash(t2), hash(t3))
2257 self.assertEqual(t1, t2)
2258 self.assertEqual(t1, t3)
2259 self.assertEqual(t2, t3)
2260 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2261 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2262 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002263 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002264 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2265 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2266 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2267
2268 def test_combine(self):
2269 met = FixedOffset(60, "MET")
2270 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002271 tz = time(18, 45, 3, 1234, tzinfo=met)
2272 dt = datetime.combine(d, tz)
2273 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002274 tzinfo=met))
2275
2276 def test_extract(self):
2277 met = FixedOffset(60, "MET")
2278 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2279 self.assertEqual(dt.date(), date(2002, 3, 4))
2280 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002281 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002282
2283 def test_tz_aware_arithmetic(self):
2284 import random
2285
2286 now = self.theclass.now()
2287 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002288 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002289 nowaware = self.theclass.combine(now.date(), timeaware)
2290 self.failUnless(nowaware.tzinfo is tz55)
2291 self.assertEqual(nowaware.timetz(), timeaware)
2292
2293 # Can't mix aware and non-aware.
2294 self.assertRaises(TypeError, lambda: now - nowaware)
2295 self.assertRaises(TypeError, lambda: nowaware - now)
2296
Tim Peters0bf60bd2003-01-08 20:40:01 +00002297 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002298 self.assertRaises(TypeError, lambda: now + nowaware)
2299 self.assertRaises(TypeError, lambda: nowaware + now)
2300 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2301
2302 # Subtracting should yield 0.
2303 self.assertEqual(now - now, timedelta(0))
2304 self.assertEqual(nowaware - nowaware, timedelta(0))
2305
2306 # Adding a delta should preserve tzinfo.
2307 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2308 nowawareplus = nowaware + delta
2309 self.failUnless(nowaware.tzinfo is tz55)
2310 nowawareplus2 = delta + nowaware
2311 self.failUnless(nowawareplus2.tzinfo is tz55)
2312 self.assertEqual(nowawareplus, nowawareplus2)
2313
2314 # that - delta should be what we started with, and that - what we
2315 # started with should be delta.
2316 diff = nowawareplus - delta
2317 self.failUnless(diff.tzinfo is tz55)
2318 self.assertEqual(nowaware, diff)
2319 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2320 self.assertEqual(nowawareplus - nowaware, delta)
2321
2322 # Make up a random timezone.
2323 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002324 # Attach it to nowawareplus.
2325 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002326 self.failUnless(nowawareplus.tzinfo is tzr)
2327 # Make sure the difference takes the timezone adjustments into account.
2328 got = nowaware - nowawareplus
2329 # Expected: (nowaware base - nowaware offset) -
2330 # (nowawareplus base - nowawareplus offset) =
2331 # (nowaware base - nowawareplus base) +
2332 # (nowawareplus offset - nowaware offset) =
2333 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002334 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002335 self.assertEqual(got, expected)
2336
2337 # Try max possible difference.
2338 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2339 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2340 tzinfo=FixedOffset(-1439, "max"))
2341 maxdiff = max - min
2342 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2343 timedelta(minutes=2*1439))
2344
2345 def test_tzinfo_now(self):
2346 meth = self.theclass.now
2347 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2348 base = meth()
2349 # Try with and without naming the keyword.
2350 off42 = FixedOffset(42, "42")
2351 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002352 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002353 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002354 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002355 # Bad argument with and w/o naming the keyword.
2356 self.assertRaises(TypeError, meth, 16)
2357 self.assertRaises(TypeError, meth, tzinfo=16)
2358 # Bad keyword name.
2359 self.assertRaises(TypeError, meth, tinfo=off42)
2360 # Too many args.
2361 self.assertRaises(TypeError, meth, off42, off42)
2362
Tim Peters10cadce2003-01-23 19:58:02 +00002363 # We don't know which time zone we're in, and don't have a tzinfo
2364 # class to represent it, so seeing whether a tz argument actually
2365 # does a conversion is tricky.
2366 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2367 utc = FixedOffset(0, "utc", 0)
2368 for dummy in range(3):
2369 now = datetime.now(weirdtz)
2370 self.failUnless(now.tzinfo is weirdtz)
2371 utcnow = datetime.utcnow().replace(tzinfo=utc)
2372 now2 = utcnow.astimezone(weirdtz)
2373 if abs(now - now2) < timedelta(seconds=30):
2374 break
2375 # Else the code is broken, or more than 30 seconds passed between
2376 # calls; assuming the latter, just try again.
2377 else:
2378 # Three strikes and we're out.
2379 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2380
Tim Peters2a799bf2002-12-16 20:18:38 +00002381 def test_tzinfo_fromtimestamp(self):
2382 import time
2383 meth = self.theclass.fromtimestamp
2384 ts = time.time()
2385 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2386 base = meth(ts)
2387 # Try with and without naming the keyword.
2388 off42 = FixedOffset(42, "42")
2389 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002390 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002391 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002392 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002393 # Bad argument with and w/o naming the keyword.
2394 self.assertRaises(TypeError, meth, ts, 16)
2395 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2396 # Bad keyword name.
2397 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2398 # Too many args.
2399 self.assertRaises(TypeError, meth, ts, off42, off42)
2400 # Too few args.
2401 self.assertRaises(TypeError, meth)
2402
Tim Peters2a44a8d2003-01-23 20:53:10 +00002403 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002404 timestamp = 1000000000
2405 utcdatetime = datetime.utcfromtimestamp(timestamp)
2406 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2407 # But on some flavor of Mac, it's nowhere near that. So we can't have
2408 # any idea here what time that actually is, we can only test that
2409 # relative changes match.
2410 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2411 tz = FixedOffset(utcoffset, "tz", 0)
2412 expected = utcdatetime + utcoffset
2413 got = datetime.fromtimestamp(timestamp, tz)
2414 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002415
Tim Peters2a799bf2002-12-16 20:18:38 +00002416 def test_tzinfo_utcnow(self):
2417 meth = self.theclass.utcnow
2418 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2419 base = meth()
2420 # Try with and without naming the keyword; for whatever reason,
2421 # utcnow() doesn't accept a tzinfo argument.
2422 off42 = FixedOffset(42, "42")
2423 self.assertRaises(TypeError, meth, off42)
2424 self.assertRaises(TypeError, meth, tzinfo=off42)
2425
2426 def test_tzinfo_utcfromtimestamp(self):
2427 import time
2428 meth = self.theclass.utcfromtimestamp
2429 ts = time.time()
2430 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2431 base = meth(ts)
2432 # Try with and without naming the keyword; for whatever reason,
2433 # utcfromtimestamp() doesn't accept a tzinfo argument.
2434 off42 = FixedOffset(42, "42")
2435 self.assertRaises(TypeError, meth, ts, off42)
2436 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2437
2438 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002439 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002440 # DST flag.
2441 class DST(tzinfo):
2442 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002443 if isinstance(dstvalue, int):
2444 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002445 self.dstvalue = dstvalue
2446 def dst(self, dt):
2447 return self.dstvalue
2448
2449 cls = self.theclass
2450 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2451 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2452 t = d.timetuple()
2453 self.assertEqual(1, t.tm_year)
2454 self.assertEqual(1, t.tm_mon)
2455 self.assertEqual(1, t.tm_mday)
2456 self.assertEqual(10, t.tm_hour)
2457 self.assertEqual(20, t.tm_min)
2458 self.assertEqual(30, t.tm_sec)
2459 self.assertEqual(0, t.tm_wday)
2460 self.assertEqual(1, t.tm_yday)
2461 self.assertEqual(flag, t.tm_isdst)
2462
2463 # dst() returns wrong type.
2464 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2465
2466 # dst() at the edge.
2467 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2468 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2469
2470 # dst() out of range.
2471 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2472 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2473
2474 def test_utctimetuple(self):
2475 class DST(tzinfo):
2476 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002477 if isinstance(dstvalue, int):
2478 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002479 self.dstvalue = dstvalue
2480 def dst(self, dt):
2481 return self.dstvalue
2482
2483 cls = self.theclass
2484 # This can't work: DST didn't implement utcoffset.
2485 self.assertRaises(NotImplementedError,
2486 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2487
2488 class UOFS(DST):
2489 def __init__(self, uofs, dofs=None):
2490 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002491 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002492 def utcoffset(self, dt):
2493 return self.uofs
2494
2495 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2496 # in effect for a UTC time.
2497 for dstvalue in -33, 33, 0, None:
2498 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2499 t = d.utctimetuple()
2500 self.assertEqual(d.year, t.tm_year)
2501 self.assertEqual(d.month, t.tm_mon)
2502 self.assertEqual(d.day, t.tm_mday)
2503 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2504 self.assertEqual(13, t.tm_min)
2505 self.assertEqual(d.second, t.tm_sec)
2506 self.assertEqual(d.weekday(), t.tm_wday)
2507 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2508 t.tm_yday)
2509 self.assertEqual(0, t.tm_isdst)
2510
2511 # At the edges, UTC adjustment can normalize into years out-of-range
2512 # for a datetime object. Ensure that a correct timetuple is
2513 # created anyway.
2514 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2515 # That goes back 1 minute less than a full day.
2516 t = tiny.utctimetuple()
2517 self.assertEqual(t.tm_year, MINYEAR-1)
2518 self.assertEqual(t.tm_mon, 12)
2519 self.assertEqual(t.tm_mday, 31)
2520 self.assertEqual(t.tm_hour, 0)
2521 self.assertEqual(t.tm_min, 1)
2522 self.assertEqual(t.tm_sec, 37)
2523 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2524 self.assertEqual(t.tm_isdst, 0)
2525
2526 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2527 # That goes forward 1 minute less than a full day.
2528 t = huge.utctimetuple()
2529 self.assertEqual(t.tm_year, MAXYEAR+1)
2530 self.assertEqual(t.tm_mon, 1)
2531 self.assertEqual(t.tm_mday, 1)
2532 self.assertEqual(t.tm_hour, 23)
2533 self.assertEqual(t.tm_min, 58)
2534 self.assertEqual(t.tm_sec, 37)
2535 self.assertEqual(t.tm_yday, 1)
2536 self.assertEqual(t.tm_isdst, 0)
2537
2538 def test_tzinfo_isoformat(self):
2539 zero = FixedOffset(0, "+00:00")
2540 plus = FixedOffset(220, "+03:40")
2541 minus = FixedOffset(-231, "-03:51")
2542 unknown = FixedOffset(None, "")
2543
2544 cls = self.theclass
2545 datestr = '0001-02-03'
2546 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002547 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002548 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2549 timestr = '04:05:59' + (us and '.987001' or '')
2550 ofsstr = ofs is not None and d.tzname() or ''
2551 tailstr = timestr + ofsstr
2552 iso = d.isoformat()
2553 self.assertEqual(iso, datestr + 'T' + tailstr)
2554 self.assertEqual(iso, d.isoformat('T'))
2555 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2556 self.assertEqual(str(d), datestr + ' ' + tailstr)
2557
Tim Peters12bf3392002-12-24 05:41:27 +00002558 def test_replace(self):
2559 cls = self.theclass
2560 z100 = FixedOffset(100, "+100")
2561 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2562 args = [1, 2, 3, 4, 5, 6, 7, z100]
2563 base = cls(*args)
2564 self.assertEqual(base, base.replace())
2565
2566 i = 0
2567 for name, newval in (("year", 2),
2568 ("month", 3),
2569 ("day", 4),
2570 ("hour", 5),
2571 ("minute", 6),
2572 ("second", 7),
2573 ("microsecond", 8),
2574 ("tzinfo", zm200)):
2575 newargs = args[:]
2576 newargs[i] = newval
2577 expected = cls(*newargs)
2578 got = base.replace(**{name: newval})
2579 self.assertEqual(expected, got)
2580 i += 1
2581
2582 # Ensure we can get rid of a tzinfo.
2583 self.assertEqual(base.tzname(), "+100")
2584 base2 = base.replace(tzinfo=None)
2585 self.failUnless(base2.tzinfo is None)
2586 self.failUnless(base2.tzname() is None)
2587
2588 # Ensure we can add one.
2589 base3 = base2.replace(tzinfo=z100)
2590 self.assertEqual(base, base3)
2591 self.failUnless(base.tzinfo is base3.tzinfo)
2592
2593 # Out of bounds.
2594 base = cls(2000, 2, 29)
2595 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002596
Tim Peters80475bb2002-12-25 07:40:55 +00002597 def test_more_astimezone(self):
2598 # The inherited test_astimezone covered some trivial and error cases.
2599 fnone = FixedOffset(None, "None")
2600 f44m = FixedOffset(44, "44")
2601 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2602
Tim Peters10cadce2003-01-23 19:58:02 +00002603 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002604 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002605 # Replacing with degenerate tzinfo raises an exception.
2606 self.assertRaises(ValueError, dt.astimezone, fnone)
2607 # Ditto with None tz.
2608 self.assertRaises(TypeError, dt.astimezone, None)
2609 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002610 x = dt.astimezone(dt.tzinfo)
2611 self.failUnless(x.tzinfo is f44m)
2612 self.assertEqual(x.date(), dt.date())
2613 self.assertEqual(x.time(), dt.time())
2614
2615 # Replacing with different tzinfo does adjust.
2616 got = dt.astimezone(fm5h)
2617 self.failUnless(got.tzinfo is fm5h)
2618 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2619 expected = dt - dt.utcoffset() # in effect, convert to UTC
2620 expected += fm5h.utcoffset(dt) # and from there to local time
2621 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2622 self.assertEqual(got.date(), expected.date())
2623 self.assertEqual(got.time(), expected.time())
2624 self.assertEqual(got.timetz(), expected.timetz())
2625 self.failUnless(got.tzinfo is expected.tzinfo)
2626 self.assertEqual(got, expected)
2627
Tim Peters4c0db782002-12-26 05:01:19 +00002628 def test_aware_subtract(self):
2629 cls = self.theclass
2630
Tim Peters60c76e42002-12-27 00:41:11 +00002631 # Ensure that utcoffset() is ignored when the operands have the
2632 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002633 class OperandDependentOffset(tzinfo):
2634 def utcoffset(self, t):
2635 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002636 # d0 and d1 equal after adjustment
2637 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002638 else:
Tim Peters397301e2003-01-02 21:28:08 +00002639 # d2 off in the weeds
2640 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002641
2642 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2643 d0 = base.replace(minute=3)
2644 d1 = base.replace(minute=9)
2645 d2 = base.replace(minute=11)
2646 for x in d0, d1, d2:
2647 for y in d0, d1, d2:
2648 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002649 expected = timedelta(minutes=x.minute - y.minute)
2650 self.assertEqual(got, expected)
2651
2652 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2653 # ignored.
2654 base = cls(8, 9, 10, 11, 12, 13, 14)
2655 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2656 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2657 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2658 for x in d0, d1, d2:
2659 for y in d0, d1, d2:
2660 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002661 if (x is d0 or x is d1) and (y is d0 or y is d1):
2662 expected = timedelta(0)
2663 elif x is y is d2:
2664 expected = timedelta(0)
2665 elif x is d2:
2666 expected = timedelta(minutes=(11-59)-0)
2667 else:
2668 assert y is d2
2669 expected = timedelta(minutes=0-(11-59))
2670 self.assertEqual(got, expected)
2671
Tim Peters60c76e42002-12-27 00:41:11 +00002672 def test_mixed_compare(self):
2673 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002674 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002675 self.assertEqual(t1, t2)
2676 t2 = t2.replace(tzinfo=None)
2677 self.assertEqual(t1, t2)
2678 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2679 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002680 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2681 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002682
Tim Peters0bf60bd2003-01-08 20:40:01 +00002683 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002684 class Varies(tzinfo):
2685 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002686 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002687 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002688 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002689 return self.offset
2690
2691 v = Varies()
2692 t1 = t2.replace(tzinfo=v)
2693 t2 = t2.replace(tzinfo=v)
2694 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2695 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2696 self.assertEqual(t1, t2)
2697
2698 # But if they're not identical, it isn't ignored.
2699 t2 = t2.replace(tzinfo=Varies())
2700 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002701
Tim Petersa98924a2003-05-17 05:55:19 +00002702 def test_subclass_datetimetz(self):
2703
2704 class C(self.theclass):
2705 theAnswer = 42
2706
2707 def __new__(cls, *args, **kws):
2708 temp = kws.copy()
2709 extra = temp.pop('extra')
2710 result = self.theclass.__new__(cls, *args, **temp)
2711 result.extra = extra
2712 return result
2713
2714 def newmeth(self, start):
2715 return start + self.hour + self.year
2716
2717 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2718
2719 dt1 = self.theclass(*args)
2720 dt2 = C(*args, **{'extra': 7})
2721
2722 self.assertEqual(dt2.__class__, C)
2723 self.assertEqual(dt2.theAnswer, 42)
2724 self.assertEqual(dt2.extra, 7)
2725 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2726 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2727
Tim Peters621818b2002-12-29 23:44:49 +00002728# Pain to set up DST-aware tzinfo classes.
2729
2730def first_sunday_on_or_after(dt):
2731 days_to_go = 6 - dt.weekday()
2732 if days_to_go:
2733 dt += timedelta(days_to_go)
2734 return dt
2735
2736ZERO = timedelta(0)
2737HOUR = timedelta(hours=1)
2738DAY = timedelta(days=1)
2739# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2740DSTSTART = datetime(1, 4, 1, 2)
2741# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002742# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2743# being standard time on that day, there is no spelling in local time of
2744# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2745DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002746
2747class USTimeZone(tzinfo):
2748
2749 def __init__(self, hours, reprname, stdname, dstname):
2750 self.stdoffset = timedelta(hours=hours)
2751 self.reprname = reprname
2752 self.stdname = stdname
2753 self.dstname = dstname
2754
2755 def __repr__(self):
2756 return self.reprname
2757
2758 def tzname(self, dt):
2759 if self.dst(dt):
2760 return self.dstname
2761 else:
2762 return self.stdname
2763
2764 def utcoffset(self, dt):
2765 return self.stdoffset + self.dst(dt)
2766
2767 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002768 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002769 # An exception instead may be sensible here, in one or more of
2770 # the cases.
2771 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002772 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002773
2774 # Find first Sunday in April.
2775 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2776 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2777
2778 # Find last Sunday in October.
2779 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2780 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2781
Tim Peters621818b2002-12-29 23:44:49 +00002782 # Can't compare naive to aware objects, so strip the timezone from
2783 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002784 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002785 return HOUR
2786 else:
2787 return ZERO
2788
Tim Peters521fc152002-12-31 17:36:56 +00002789Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2790Central = USTimeZone(-6, "Central", "CST", "CDT")
2791Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2792Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002793utc_real = FixedOffset(0, "UTC", 0)
2794# For better test coverage, we want another flavor of UTC that's west of
2795# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002796utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002797
2798class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002799 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002800 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002801 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002802
Tim Peters0bf60bd2003-01-08 20:40:01 +00002803 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002804
Tim Peters521fc152002-12-31 17:36:56 +00002805 # Check a time that's inside DST.
2806 def checkinside(self, dt, tz, utc, dston, dstoff):
2807 self.assertEqual(dt.dst(), HOUR)
2808
2809 # Conversion to our own timezone is always an identity.
2810 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002811
2812 asutc = dt.astimezone(utc)
2813 there_and_back = asutc.astimezone(tz)
2814
2815 # Conversion to UTC and back isn't always an identity here,
2816 # because there are redundant spellings (in local time) of
2817 # UTC time when DST begins: the clock jumps from 1:59:59
2818 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2819 # make sense then. The classes above treat 2:MM:SS as
2820 # daylight time then (it's "after 2am"), really an alias
2821 # for 1:MM:SS standard time. The latter form is what
2822 # conversion back from UTC produces.
2823 if dt.date() == dston.date() and dt.hour == 2:
2824 # We're in the redundant hour, and coming back from
2825 # UTC gives the 1:MM:SS standard-time spelling.
2826 self.assertEqual(there_and_back + HOUR, dt)
2827 # Although during was considered to be in daylight
2828 # time, there_and_back is not.
2829 self.assertEqual(there_and_back.dst(), ZERO)
2830 # They're the same times in UTC.
2831 self.assertEqual(there_and_back.astimezone(utc),
2832 dt.astimezone(utc))
2833 else:
2834 # We're not in the redundant hour.
2835 self.assertEqual(dt, there_and_back)
2836
Tim Peters327098a2003-01-20 22:54:38 +00002837 # Because we have a redundant spelling when DST begins, there is
2838 # (unforunately) an hour when DST ends that can't be spelled at all in
2839 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2840 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2841 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2842 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2843 # expressed in local time. Nevertheless, we want conversion back
2844 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002845 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002846 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002847 if dt.date() == dstoff.date() and dt.hour == 0:
2848 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002849 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002850 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2851 nexthour_utc += HOUR
2852 nexthour_tz = nexthour_utc.astimezone(tz)
2853 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002854 else:
Tim Peters327098a2003-01-20 22:54:38 +00002855 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002856
2857 # Check a time that's outside DST.
2858 def checkoutside(self, dt, tz, utc):
2859 self.assertEqual(dt.dst(), ZERO)
2860
2861 # Conversion to our own timezone is always an identity.
2862 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002863
2864 # Converting to UTC and back is an identity too.
2865 asutc = dt.astimezone(utc)
2866 there_and_back = asutc.astimezone(tz)
2867 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002868
Tim Peters1024bf82002-12-30 17:09:40 +00002869 def convert_between_tz_and_utc(self, tz, utc):
2870 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002871 # Because 1:MM on the day DST ends is taken as being standard time,
2872 # there is no spelling in tz for the last hour of daylight time.
2873 # For purposes of the test, the last hour of DST is 0:MM, which is
2874 # taken as being daylight time (and 1:MM is taken as being standard
2875 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002876 dstoff = self.dstoff.replace(tzinfo=tz)
2877 for delta in (timedelta(weeks=13),
2878 DAY,
2879 HOUR,
2880 timedelta(minutes=1),
2881 timedelta(microseconds=1)):
2882
Tim Peters521fc152002-12-31 17:36:56 +00002883 self.checkinside(dston, tz, utc, dston, dstoff)
2884 for during in dston + delta, dstoff - delta:
2885 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002886
Tim Peters521fc152002-12-31 17:36:56 +00002887 self.checkoutside(dstoff, tz, utc)
2888 for outside in dston - delta, dstoff + delta:
2889 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002890
Tim Peters621818b2002-12-29 23:44:49 +00002891 def test_easy(self):
2892 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002893 self.convert_between_tz_and_utc(Eastern, utc_real)
2894 self.convert_between_tz_and_utc(Pacific, utc_real)
2895 self.convert_between_tz_and_utc(Eastern, utc_fake)
2896 self.convert_between_tz_and_utc(Pacific, utc_fake)
2897 # The next is really dancing near the edge. It works because
2898 # Pacific and Eastern are far enough apart that their "problem
2899 # hours" don't overlap.
2900 self.convert_between_tz_and_utc(Eastern, Pacific)
2901 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002902 # OTOH, these fail! Don't enable them. The difficulty is that
2903 # the edge case tests assume that every hour is representable in
2904 # the "utc" class. This is always true for a fixed-offset tzinfo
2905 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2906 # For these adjacent DST-aware time zones, the range of time offsets
2907 # tested ends up creating hours in the one that aren't representable
2908 # in the other. For the same reason, we would see failures in the
2909 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2910 # offset deltas in convert_between_tz_and_utc().
2911 #
2912 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2913 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002914
Tim Petersf3615152003-01-01 21:51:37 +00002915 def test_tricky(self):
2916 # 22:00 on day before daylight starts.
2917 fourback = self.dston - timedelta(hours=4)
2918 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002919 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002920 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2921 # 2", we should get the 3 spelling.
2922 # If we plug 22:00 the day before into Eastern, it "looks like std
2923 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2924 # to 22:00 lands on 2:00, which makes no sense in local time (the
2925 # local clock jumps from 1 to 3). The point here is to make sure we
2926 # get the 3 spelling.
2927 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002928 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002929 self.assertEqual(expected, got)
2930
2931 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2932 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002933 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002934 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2935 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2936 # spelling.
2937 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002938 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002939 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002940
Tim Petersadf64202003-01-04 06:03:15 +00002941 # Now on the day DST ends, we want "repeat an hour" behavior.
2942 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2943 # EST 23:MM 0:MM 1:MM 2:MM
2944 # EDT 0:MM 1:MM 2:MM 3:MM
2945 # wall 0:MM 1:MM 1:MM 2:MM against these
2946 for utc in utc_real, utc_fake:
2947 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002948 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002949 # Convert that to UTC.
2950 first_std_hour -= tz.utcoffset(None)
2951 # Adjust for possibly fake UTC.
2952 asutc = first_std_hour + utc.utcoffset(None)
2953 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2954 # tz=Eastern.
2955 asutcbase = asutc.replace(tzinfo=utc)
2956 for tzhour in (0, 1, 1, 2):
2957 expectedbase = self.dstoff.replace(hour=tzhour)
2958 for minute in 0, 30, 59:
2959 expected = expectedbase.replace(minute=minute)
2960 asutc = asutcbase.replace(minute=minute)
2961 astz = asutc.astimezone(tz)
2962 self.assertEqual(astz.replace(tzinfo=None), expected)
2963 asutcbase += HOUR
2964
2965
Tim Peters710fb152003-01-02 19:35:54 +00002966 def test_bogus_dst(self):
2967 class ok(tzinfo):
2968 def utcoffset(self, dt): return HOUR
2969 def dst(self, dt): return HOUR
2970
2971 now = self.theclass.now().replace(tzinfo=utc_real)
2972 # Doesn't blow up.
2973 now.astimezone(ok())
2974
2975 # Does blow up.
2976 class notok(ok):
2977 def dst(self, dt): return None
2978 self.assertRaises(ValueError, now.astimezone, notok())
2979
Tim Peters52dcce22003-01-23 16:36:11 +00002980 def test_fromutc(self):
2981 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2982 now = datetime.utcnow().replace(tzinfo=utc_real)
2983 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2984 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2985 enow = Eastern.fromutc(now) # doesn't blow up
2986 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2987 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2988 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2989
2990 # Always converts UTC to standard time.
2991 class FauxUSTimeZone(USTimeZone):
2992 def fromutc(self, dt):
2993 return dt + self.stdoffset
2994 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2995
2996 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2997 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2998 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2999
3000 # Check around DST start.
3001 start = self.dston.replace(hour=4, tzinfo=Eastern)
3002 fstart = start.replace(tzinfo=FEastern)
3003 for wall in 23, 0, 1, 3, 4, 5:
3004 expected = start.replace(hour=wall)
3005 if wall == 23:
3006 expected -= timedelta(days=1)
3007 got = Eastern.fromutc(start)
3008 self.assertEqual(expected, got)
3009
3010 expected = fstart + FEastern.stdoffset
3011 got = FEastern.fromutc(fstart)
3012 self.assertEqual(expected, got)
3013
3014 # Ensure astimezone() calls fromutc() too.
3015 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3016 self.assertEqual(expected, got)
3017
3018 start += HOUR
3019 fstart += HOUR
3020
3021 # Check around DST end.
3022 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3023 fstart = start.replace(tzinfo=FEastern)
3024 for wall in 0, 1, 1, 2, 3, 4:
3025 expected = start.replace(hour=wall)
3026 got = Eastern.fromutc(start)
3027 self.assertEqual(expected, got)
3028
3029 expected = fstart + FEastern.stdoffset
3030 got = FEastern.fromutc(fstart)
3031 self.assertEqual(expected, got)
3032
3033 # Ensure astimezone() calls fromutc() too.
3034 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3035 self.assertEqual(expected, got)
3036
3037 start += HOUR
3038 fstart += HOUR
3039
Tim Peters710fb152003-01-02 19:35:54 +00003040
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003041def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003042 allsuites = [unittest.makeSuite(klass, 'test')
3043 for klass in (TestModule,
3044 TestTZInfo,
3045 TestTimeDelta,
3046 TestDateOnly,
3047 TestDate,
3048 TestDateTime,
3049 TestTime,
3050 TestTimeTZ,
3051 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003052 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00003053 )
3054 ]
3055 return unittest.TestSuite(allsuites)
3056
3057def test_main():
3058 import gc
3059 import sys
3060
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003061 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003062 lastrc = None
3063 while True:
3064 test_support.run_suite(thesuite)
3065 if 1: # change to 0, under a debug build, for some leak detection
3066 break
3067 gc.collect()
3068 if gc.garbage:
3069 raise SystemError("gc.garbage not empty after test run: %r" %
3070 gc.garbage)
3071 if hasattr(sys, 'gettotalrefcount'):
3072 thisrc = sys.gettotalrefcount()
3073 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3074 if lastrc:
3075 print >> sys.stderr, 'delta:', thisrc - lastrc
3076 else:
3077 print >> sys.stderr
3078 lastrc = thisrc
3079
3080if __name__ == "__main__":
3081 test_main()