blob: 9532a2a7838951e4baf6bc5e3fc6013cc7eef8d6 [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 Peters2a799bf2002-12-16 20:18:38 +0000974#############################################################################
975# datetime tests
976
977class TestDateTime(TestDate):
978
979 theclass = datetime
980
981 def test_basic_attributes(self):
982 dt = self.theclass(2002, 3, 1, 12, 0)
983 self.assertEqual(dt.year, 2002)
984 self.assertEqual(dt.month, 3)
985 self.assertEqual(dt.day, 1)
986 self.assertEqual(dt.hour, 12)
987 self.assertEqual(dt.minute, 0)
988 self.assertEqual(dt.second, 0)
989 self.assertEqual(dt.microsecond, 0)
990
991 def test_basic_attributes_nonzero(self):
992 # Make sure all attributes are non-zero so bugs in
993 # bit-shifting access show up.
994 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
995 self.assertEqual(dt.year, 2002)
996 self.assertEqual(dt.month, 3)
997 self.assertEqual(dt.day, 1)
998 self.assertEqual(dt.hour, 12)
999 self.assertEqual(dt.minute, 59)
1000 self.assertEqual(dt.second, 59)
1001 self.assertEqual(dt.microsecond, 8000)
1002
1003 def test_roundtrip(self):
1004 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1005 self.theclass.now()):
1006 # Verify dt -> string -> datetime identity.
1007 s = repr(dt)
1008 self.failUnless(s.startswith('datetime.'))
1009 s = s[9:]
1010 dt2 = eval(s)
1011 self.assertEqual(dt, dt2)
1012
1013 # Verify identity via reconstructing from pieces.
1014 dt2 = self.theclass(dt.year, dt.month, dt.day,
1015 dt.hour, dt.minute, dt.second,
1016 dt.microsecond)
1017 self.assertEqual(dt, dt2)
1018
1019 def test_isoformat(self):
1020 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1021 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1022 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1023 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1024 # str is ISO format with the separator forced to a blank.
1025 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1026
1027 t = self.theclass(2, 3, 2)
1028 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1029 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1030 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1031 # str is ISO format with the separator forced to a blank.
1032 self.assertEqual(str(t), "0002-03-02 00:00:00")
1033
1034 def test_more_ctime(self):
1035 # Test fields that TestDate doesn't touch.
1036 import time
1037
1038 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1039 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1040 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1041 # out. The difference is that t.ctime() produces " 2" for the day,
1042 # but platform ctime() produces "02" for the day. According to
1043 # C99, t.ctime() is correct here.
1044 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1045
1046 # So test a case where that difference doesn't matter.
1047 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1048 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1049
1050 def test_tz_independent_comparing(self):
1051 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1052 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1053 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1054 self.assertEqual(dt1, dt3)
1055 self.assert_(dt2 > dt3)
1056
1057 # Make sure comparison doesn't forget microseconds, and isn't done
1058 # via comparing a float timestamp (an IEEE double doesn't have enough
1059 # precision to span microsecond resolution across years 1 thru 9999,
1060 # so comparing via timestamp necessarily calls some distinct values
1061 # equal).
1062 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1063 us = timedelta(microseconds=1)
1064 dt2 = dt1 + us
1065 self.assertEqual(dt2 - dt1, us)
1066 self.assert_(dt1 < dt2)
1067
1068 def test_bad_constructor_arguments(self):
1069 # bad years
1070 self.theclass(MINYEAR, 1, 1) # no exception
1071 self.theclass(MAXYEAR, 1, 1) # no exception
1072 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1073 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1074 # bad months
1075 self.theclass(2000, 1, 1) # no exception
1076 self.theclass(2000, 12, 1) # no exception
1077 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1078 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1079 # bad days
1080 self.theclass(2000, 2, 29) # no exception
1081 self.theclass(2004, 2, 29) # no exception
1082 self.theclass(2400, 2, 29) # no exception
1083 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1084 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1085 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1086 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1087 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1088 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1089 # bad hours
1090 self.theclass(2000, 1, 31, 0) # no exception
1091 self.theclass(2000, 1, 31, 23) # no exception
1092 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1093 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1094 # bad minutes
1095 self.theclass(2000, 1, 31, 23, 0) # no exception
1096 self.theclass(2000, 1, 31, 23, 59) # no exception
1097 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1098 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1099 # bad seconds
1100 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1101 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1102 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1103 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1104 # bad microseconds
1105 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1106 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1107 self.assertRaises(ValueError, self.theclass,
1108 2000, 1, 31, 23, 59, 59, -1)
1109 self.assertRaises(ValueError, self.theclass,
1110 2000, 1, 31, 23, 59, 59,
1111 1000000)
1112
1113 def test_hash_equality(self):
1114 d = self.theclass(2000, 12, 31, 23, 30, 17)
1115 e = self.theclass(2000, 12, 31, 23, 30, 17)
1116 self.assertEqual(d, e)
1117 self.assertEqual(hash(d), hash(e))
1118
1119 dic = {d: 1}
1120 dic[e] = 2
1121 self.assertEqual(len(dic), 1)
1122 self.assertEqual(dic[d], 2)
1123 self.assertEqual(dic[e], 2)
1124
1125 d = self.theclass(2001, 1, 1, 0, 5, 17)
1126 e = self.theclass(2001, 1, 1, 0, 5, 17)
1127 self.assertEqual(d, e)
1128 self.assertEqual(hash(d), hash(e))
1129
1130 dic = {d: 1}
1131 dic[e] = 2
1132 self.assertEqual(len(dic), 1)
1133 self.assertEqual(dic[d], 2)
1134 self.assertEqual(dic[e], 2)
1135
1136 def test_computations(self):
1137 a = self.theclass(2002, 1, 31)
1138 b = self.theclass(1956, 1, 31)
1139 diff = a-b
1140 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1141 self.assertEqual(diff.seconds, 0)
1142 self.assertEqual(diff.microseconds, 0)
1143 a = self.theclass(2002, 3, 2, 17, 6)
1144 millisec = timedelta(0, 0, 1000)
1145 hour = timedelta(0, 3600)
1146 day = timedelta(1)
1147 week = timedelta(7)
1148 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1149 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1150 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1151 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1152 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1153 self.assertEqual(a - hour, a + -hour)
1154 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1155 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1156 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1157 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1158 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1159 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1160 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1161 self.assertEqual((a + week) - a, week)
1162 self.assertEqual((a + day) - a, day)
1163 self.assertEqual((a + hour) - a, hour)
1164 self.assertEqual((a + millisec) - a, millisec)
1165 self.assertEqual((a - week) - a, -week)
1166 self.assertEqual((a - day) - a, -day)
1167 self.assertEqual((a - hour) - a, -hour)
1168 self.assertEqual((a - millisec) - a, -millisec)
1169 self.assertEqual(a - (a + week), -week)
1170 self.assertEqual(a - (a + day), -day)
1171 self.assertEqual(a - (a + hour), -hour)
1172 self.assertEqual(a - (a + millisec), -millisec)
1173 self.assertEqual(a - (a - week), week)
1174 self.assertEqual(a - (a - day), day)
1175 self.assertEqual(a - (a - hour), hour)
1176 self.assertEqual(a - (a - millisec), millisec)
1177 self.assertEqual(a + (week + day + hour + millisec),
1178 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1179 self.assertEqual(a + (week + day + hour + millisec),
1180 (((a + week) + day) + hour) + millisec)
1181 self.assertEqual(a - (week + day + hour + millisec),
1182 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1183 self.assertEqual(a - (week + day + hour + millisec),
1184 (((a - week) - day) - hour) - millisec)
1185 # Add/sub ints, longs, floats should be illegal
1186 for i in 1, 1L, 1.0:
1187 self.assertRaises(TypeError, lambda: a+i)
1188 self.assertRaises(TypeError, lambda: a-i)
1189 self.assertRaises(TypeError, lambda: i+a)
1190 self.assertRaises(TypeError, lambda: i-a)
1191
1192 # delta - datetime is senseless.
1193 self.assertRaises(TypeError, lambda: day - a)
1194 # mixing datetime and (delta or datetime) via * or // is senseless
1195 self.assertRaises(TypeError, lambda: day * a)
1196 self.assertRaises(TypeError, lambda: a * day)
1197 self.assertRaises(TypeError, lambda: day // a)
1198 self.assertRaises(TypeError, lambda: a // day)
1199 self.assertRaises(TypeError, lambda: a * a)
1200 self.assertRaises(TypeError, lambda: a // a)
1201 # datetime + datetime is senseless
1202 self.assertRaises(TypeError, lambda: a + a)
1203
1204 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001205 args = 6, 7, 23, 20, 59, 1, 64**2
1206 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001207 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001208 green = pickler.dumps(orig, proto)
1209 derived = unpickler.loads(green)
1210 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001211
Guido van Rossum275666f2003-02-07 21:49:01 +00001212 def test_more_pickling(self):
1213 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1214 s = pickle.dumps(a)
1215 b = pickle.loads(s)
1216 self.assertEqual(b.year, 2003)
1217 self.assertEqual(b.month, 2)
1218 self.assertEqual(b.day, 7)
1219
Tim Peters2a799bf2002-12-16 20:18:38 +00001220 def test_more_compare(self):
1221 # The test_compare() inherited from TestDate covers the error cases.
1222 # We just want to test lexicographic ordering on the members datetime
1223 # has that date lacks.
1224 args = [2000, 11, 29, 20, 58, 16, 999998]
1225 t1 = self.theclass(*args)
1226 t2 = self.theclass(*args)
1227 self.failUnless(t1 == t2)
1228 self.failUnless(t1 <= t2)
1229 self.failUnless(t1 >= t2)
1230 self.failUnless(not t1 != t2)
1231 self.failUnless(not t1 < t2)
1232 self.failUnless(not t1 > t2)
1233 self.assertEqual(cmp(t1, t2), 0)
1234 self.assertEqual(cmp(t2, t1), 0)
1235
1236 for i in range(len(args)):
1237 newargs = args[:]
1238 newargs[i] = args[i] + 1
1239 t2 = self.theclass(*newargs) # this is larger than t1
1240 self.failUnless(t1 < t2)
1241 self.failUnless(t2 > t1)
1242 self.failUnless(t1 <= t2)
1243 self.failUnless(t2 >= t1)
1244 self.failUnless(t1 != t2)
1245 self.failUnless(t2 != t1)
1246 self.failUnless(not t1 == t2)
1247 self.failUnless(not t2 == t1)
1248 self.failUnless(not t1 > t2)
1249 self.failUnless(not t2 < t1)
1250 self.failUnless(not t1 >= t2)
1251 self.failUnless(not t2 <= t1)
1252 self.assertEqual(cmp(t1, t2), -1)
1253 self.assertEqual(cmp(t2, t1), 1)
1254
1255
1256 # A helper for timestamp constructor tests.
1257 def verify_field_equality(self, expected, got):
1258 self.assertEqual(expected.tm_year, got.year)
1259 self.assertEqual(expected.tm_mon, got.month)
1260 self.assertEqual(expected.tm_mday, got.day)
1261 self.assertEqual(expected.tm_hour, got.hour)
1262 self.assertEqual(expected.tm_min, got.minute)
1263 self.assertEqual(expected.tm_sec, got.second)
1264
1265 def test_fromtimestamp(self):
1266 import time
1267
1268 ts = time.time()
1269 expected = time.localtime(ts)
1270 got = self.theclass.fromtimestamp(ts)
1271 self.verify_field_equality(expected, got)
1272
1273 def test_utcfromtimestamp(self):
1274 import time
1275
1276 ts = time.time()
1277 expected = time.gmtime(ts)
1278 got = self.theclass.utcfromtimestamp(ts)
1279 self.verify_field_equality(expected, got)
1280
1281 def test_utcnow(self):
1282 import time
1283
1284 # Call it a success if utcnow() and utcfromtimestamp() are within
1285 # a second of each other.
1286 tolerance = timedelta(seconds=1)
1287 for dummy in range(3):
1288 from_now = self.theclass.utcnow()
1289 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1290 if abs(from_timestamp - from_now) <= tolerance:
1291 break
1292 # Else try again a few times.
1293 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1294
1295 def test_more_timetuple(self):
1296 # This tests fields beyond those tested by the TestDate.test_timetuple.
1297 t = self.theclass(2004, 12, 31, 6, 22, 33)
1298 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1299 self.assertEqual(t.timetuple(),
1300 (t.year, t.month, t.day,
1301 t.hour, t.minute, t.second,
1302 t.weekday(),
1303 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1304 -1))
1305 tt = t.timetuple()
1306 self.assertEqual(tt.tm_year, t.year)
1307 self.assertEqual(tt.tm_mon, t.month)
1308 self.assertEqual(tt.tm_mday, t.day)
1309 self.assertEqual(tt.tm_hour, t.hour)
1310 self.assertEqual(tt.tm_min, t.minute)
1311 self.assertEqual(tt.tm_sec, t.second)
1312 self.assertEqual(tt.tm_wday, t.weekday())
1313 self.assertEqual(tt.tm_yday, t.toordinal() -
1314 date(t.year, 1, 1).toordinal() + 1)
1315 self.assertEqual(tt.tm_isdst, -1)
1316
1317 def test_more_strftime(self):
1318 # This tests fields beyond those tested by the TestDate.test_strftime.
1319 t = self.theclass(2004, 12, 31, 6, 22, 33)
1320 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1321 "12 31 04 33 22 06 366")
1322
1323 def test_extract(self):
1324 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1325 self.assertEqual(dt.date(), date(2002, 3, 4))
1326 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1327
1328 def test_combine(self):
1329 d = date(2002, 3, 4)
1330 t = time(18, 45, 3, 1234)
1331 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1332 combine = self.theclass.combine
1333 dt = combine(d, t)
1334 self.assertEqual(dt, expected)
1335
1336 dt = combine(time=t, date=d)
1337 self.assertEqual(dt, expected)
1338
1339 self.assertEqual(d, dt.date())
1340 self.assertEqual(t, dt.time())
1341 self.assertEqual(dt, combine(dt.date(), dt.time()))
1342
1343 self.assertRaises(TypeError, combine) # need an arg
1344 self.assertRaises(TypeError, combine, d) # need two args
1345 self.assertRaises(TypeError, combine, t, d) # args reversed
1346 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1347 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1348
Tim Peters12bf3392002-12-24 05:41:27 +00001349 def test_replace(self):
1350 cls = self.theclass
1351 args = [1, 2, 3, 4, 5, 6, 7]
1352 base = cls(*args)
1353 self.assertEqual(base, base.replace())
1354
1355 i = 0
1356 for name, newval in (("year", 2),
1357 ("month", 3),
1358 ("day", 4),
1359 ("hour", 5),
1360 ("minute", 6),
1361 ("second", 7),
1362 ("microsecond", 8)):
1363 newargs = args[:]
1364 newargs[i] = newval
1365 expected = cls(*newargs)
1366 got = base.replace(**{name: newval})
1367 self.assertEqual(expected, got)
1368 i += 1
1369
1370 # Out of bounds.
1371 base = cls(2000, 2, 29)
1372 self.assertRaises(ValueError, base.replace, year=2001)
1373
Tim Peters80475bb2002-12-25 07:40:55 +00001374 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001375 # Pretty boring! The TZ test is more interesting here. astimezone()
1376 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001377 dt = self.theclass.now()
1378 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001379 self.assertRaises(TypeError, dt.astimezone) # not enough args
1380 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1381 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001382 self.assertRaises(ValueError, dt.astimezone, f) # naive
1383 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001384
Tim Peters52dcce22003-01-23 16:36:11 +00001385 class Bogus(tzinfo):
1386 def utcoffset(self, dt): return None
1387 def dst(self, dt): return timedelta(0)
1388 bog = Bogus()
1389 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1390
1391 class AlsoBogus(tzinfo):
1392 def utcoffset(self, dt): return timedelta(0)
1393 def dst(self, dt): return None
1394 alsobog = AlsoBogus()
1395 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001396
Tim Peters07534a62003-02-07 22:50:28 +00001397class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001398
1399 theclass = time
1400
1401 def test_basic_attributes(self):
1402 t = self.theclass(12, 0)
1403 self.assertEqual(t.hour, 12)
1404 self.assertEqual(t.minute, 0)
1405 self.assertEqual(t.second, 0)
1406 self.assertEqual(t.microsecond, 0)
1407
1408 def test_basic_attributes_nonzero(self):
1409 # Make sure all attributes are non-zero so bugs in
1410 # bit-shifting access show up.
1411 t = self.theclass(12, 59, 59, 8000)
1412 self.assertEqual(t.hour, 12)
1413 self.assertEqual(t.minute, 59)
1414 self.assertEqual(t.second, 59)
1415 self.assertEqual(t.microsecond, 8000)
1416
1417 def test_roundtrip(self):
1418 t = self.theclass(1, 2, 3, 4)
1419
1420 # Verify t -> string -> time identity.
1421 s = repr(t)
1422 self.failUnless(s.startswith('datetime.'))
1423 s = s[9:]
1424 t2 = eval(s)
1425 self.assertEqual(t, t2)
1426
1427 # Verify identity via reconstructing from pieces.
1428 t2 = self.theclass(t.hour, t.minute, t.second,
1429 t.microsecond)
1430 self.assertEqual(t, t2)
1431
1432 def test_comparing(self):
1433 args = [1, 2, 3, 4]
1434 t1 = self.theclass(*args)
1435 t2 = self.theclass(*args)
1436 self.failUnless(t1 == t2)
1437 self.failUnless(t1 <= t2)
1438 self.failUnless(t1 >= t2)
1439 self.failUnless(not t1 != t2)
1440 self.failUnless(not t1 < t2)
1441 self.failUnless(not t1 > t2)
1442 self.assertEqual(cmp(t1, t2), 0)
1443 self.assertEqual(cmp(t2, t1), 0)
1444
1445 for i in range(len(args)):
1446 newargs = args[:]
1447 newargs[i] = args[i] + 1
1448 t2 = self.theclass(*newargs) # this is larger than t1
1449 self.failUnless(t1 < t2)
1450 self.failUnless(t2 > t1)
1451 self.failUnless(t1 <= t2)
1452 self.failUnless(t2 >= t1)
1453 self.failUnless(t1 != t2)
1454 self.failUnless(t2 != t1)
1455 self.failUnless(not t1 == t2)
1456 self.failUnless(not t2 == t1)
1457 self.failUnless(not t1 > t2)
1458 self.failUnless(not t2 < t1)
1459 self.failUnless(not t1 >= t2)
1460 self.failUnless(not t2 <= t1)
1461 self.assertEqual(cmp(t1, t2), -1)
1462 self.assertEqual(cmp(t2, t1), 1)
1463
Tim Peters68124bb2003-02-08 03:46:31 +00001464 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001465 self.assertEqual(t1 == badarg, False)
1466 self.assertEqual(t1 != badarg, True)
1467 self.assertEqual(badarg == t1, False)
1468 self.assertEqual(badarg != t1, True)
1469
Tim Peters2a799bf2002-12-16 20:18:38 +00001470 self.assertRaises(TypeError, lambda: t1 <= badarg)
1471 self.assertRaises(TypeError, lambda: t1 < badarg)
1472 self.assertRaises(TypeError, lambda: t1 > badarg)
1473 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001474 self.assertRaises(TypeError, lambda: badarg <= t1)
1475 self.assertRaises(TypeError, lambda: badarg < t1)
1476 self.assertRaises(TypeError, lambda: badarg > t1)
1477 self.assertRaises(TypeError, lambda: badarg >= t1)
1478
1479 def test_bad_constructor_arguments(self):
1480 # bad hours
1481 self.theclass(0, 0) # no exception
1482 self.theclass(23, 0) # no exception
1483 self.assertRaises(ValueError, self.theclass, -1, 0)
1484 self.assertRaises(ValueError, self.theclass, 24, 0)
1485 # bad minutes
1486 self.theclass(23, 0) # no exception
1487 self.theclass(23, 59) # no exception
1488 self.assertRaises(ValueError, self.theclass, 23, -1)
1489 self.assertRaises(ValueError, self.theclass, 23, 60)
1490 # bad seconds
1491 self.theclass(23, 59, 0) # no exception
1492 self.theclass(23, 59, 59) # no exception
1493 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1494 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1495 # bad microseconds
1496 self.theclass(23, 59, 59, 0) # no exception
1497 self.theclass(23, 59, 59, 999999) # no exception
1498 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1499 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1500
1501 def test_hash_equality(self):
1502 d = self.theclass(23, 30, 17)
1503 e = self.theclass(23, 30, 17)
1504 self.assertEqual(d, e)
1505 self.assertEqual(hash(d), hash(e))
1506
1507 dic = {d: 1}
1508 dic[e] = 2
1509 self.assertEqual(len(dic), 1)
1510 self.assertEqual(dic[d], 2)
1511 self.assertEqual(dic[e], 2)
1512
1513 d = self.theclass(0, 5, 17)
1514 e = self.theclass(0, 5, 17)
1515 self.assertEqual(d, e)
1516 self.assertEqual(hash(d), hash(e))
1517
1518 dic = {d: 1}
1519 dic[e] = 2
1520 self.assertEqual(len(dic), 1)
1521 self.assertEqual(dic[d], 2)
1522 self.assertEqual(dic[e], 2)
1523
1524 def test_isoformat(self):
1525 t = self.theclass(4, 5, 1, 123)
1526 self.assertEqual(t.isoformat(), "04:05:01.000123")
1527 self.assertEqual(t.isoformat(), str(t))
1528
1529 t = self.theclass()
1530 self.assertEqual(t.isoformat(), "00:00:00")
1531 self.assertEqual(t.isoformat(), str(t))
1532
1533 t = self.theclass(microsecond=1)
1534 self.assertEqual(t.isoformat(), "00:00:00.000001")
1535 self.assertEqual(t.isoformat(), str(t))
1536
1537 t = self.theclass(microsecond=10)
1538 self.assertEqual(t.isoformat(), "00:00:00.000010")
1539 self.assertEqual(t.isoformat(), str(t))
1540
1541 t = self.theclass(microsecond=100)
1542 self.assertEqual(t.isoformat(), "00:00:00.000100")
1543 self.assertEqual(t.isoformat(), str(t))
1544
1545 t = self.theclass(microsecond=1000)
1546 self.assertEqual(t.isoformat(), "00:00:00.001000")
1547 self.assertEqual(t.isoformat(), str(t))
1548
1549 t = self.theclass(microsecond=10000)
1550 self.assertEqual(t.isoformat(), "00:00:00.010000")
1551 self.assertEqual(t.isoformat(), str(t))
1552
1553 t = self.theclass(microsecond=100000)
1554 self.assertEqual(t.isoformat(), "00:00:00.100000")
1555 self.assertEqual(t.isoformat(), str(t))
1556
1557 def test_strftime(self):
1558 t = self.theclass(1, 2, 3, 4)
1559 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1560 # A naive object replaces %z and %Z with empty strings.
1561 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1562
1563 def test_str(self):
1564 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1565 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1566 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1567 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1568 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1569
1570 def test_repr(self):
1571 name = 'datetime.' + self.theclass.__name__
1572 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1573 "%s(1, 2, 3, 4)" % name)
1574 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1575 "%s(10, 2, 3, 4000)" % name)
1576 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1577 "%s(0, 2, 3, 400000)" % name)
1578 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1579 "%s(12, 2, 3)" % name)
1580 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1581 "%s(23, 15)" % name)
1582
1583 def test_resolution_info(self):
1584 self.assert_(isinstance(self.theclass.min, self.theclass))
1585 self.assert_(isinstance(self.theclass.max, self.theclass))
1586 self.assert_(isinstance(self.theclass.resolution, timedelta))
1587 self.assert_(self.theclass.max > self.theclass.min)
1588
1589 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001590 args = 20, 59, 16, 64**2
1591 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001592 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001593 green = pickler.dumps(orig, proto)
1594 derived = unpickler.loads(green)
1595 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001596
1597 def test_bool(self):
1598 cls = self.theclass
1599 self.failUnless(cls(1))
1600 self.failUnless(cls(0, 1))
1601 self.failUnless(cls(0, 0, 1))
1602 self.failUnless(cls(0, 0, 0, 1))
1603 self.failUnless(not cls(0))
1604 self.failUnless(not cls())
1605
Tim Peters12bf3392002-12-24 05:41:27 +00001606 def test_replace(self):
1607 cls = self.theclass
1608 args = [1, 2, 3, 4]
1609 base = cls(*args)
1610 self.assertEqual(base, base.replace())
1611
1612 i = 0
1613 for name, newval in (("hour", 5),
1614 ("minute", 6),
1615 ("second", 7),
1616 ("microsecond", 8)):
1617 newargs = args[:]
1618 newargs[i] = newval
1619 expected = cls(*newargs)
1620 got = base.replace(**{name: newval})
1621 self.assertEqual(expected, got)
1622 i += 1
1623
1624 # Out of bounds.
1625 base = cls(1)
1626 self.assertRaises(ValueError, base.replace, hour=24)
1627 self.assertRaises(ValueError, base.replace, minute=-1)
1628 self.assertRaises(ValueError, base.replace, second=100)
1629 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1630
Tim Peters855fe882002-12-22 03:43:39 +00001631# A mixin for classes with a tzinfo= argument. Subclasses must define
1632# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001633# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001634class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001635
Tim Petersbad8ff02002-12-30 20:52:32 +00001636 def test_argument_passing(self):
1637 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001638 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001639 class introspective(tzinfo):
1640 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001641 def utcoffset(self, dt):
1642 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001643 dst = utcoffset
1644
1645 obj = cls(1, 2, 3, tzinfo=introspective())
1646
Tim Peters0bf60bd2003-01-08 20:40:01 +00001647 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001648 self.assertEqual(obj.tzname(), expected)
1649
Tim Peters0bf60bd2003-01-08 20:40:01 +00001650 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001651 self.assertEqual(obj.utcoffset(), expected)
1652 self.assertEqual(obj.dst(), expected)
1653
Tim Peters855fe882002-12-22 03:43:39 +00001654 def test_bad_tzinfo_classes(self):
1655 cls = self.theclass
1656 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001657
Tim Peters855fe882002-12-22 03:43:39 +00001658 class NiceTry(object):
1659 def __init__(self): pass
1660 def utcoffset(self, dt): pass
1661 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1662
1663 class BetterTry(tzinfo):
1664 def __init__(self): pass
1665 def utcoffset(self, dt): pass
1666 b = BetterTry()
1667 t = cls(1, 1, 1, tzinfo=b)
1668 self.failUnless(t.tzinfo is b)
1669
1670 def test_utc_offset_out_of_bounds(self):
1671 class Edgy(tzinfo):
1672 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001673 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001674 def utcoffset(self, dt):
1675 return self.offset
1676
1677 cls = self.theclass
1678 for offset, legit in ((-1440, False),
1679 (-1439, True),
1680 (1439, True),
1681 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001682 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001683 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001684 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001685 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001686 else:
1687 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001688 if legit:
1689 aofs = abs(offset)
1690 h, m = divmod(aofs, 60)
1691 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001692 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001693 t = t.timetz()
1694 self.assertEqual(str(t), "01:02:03" + tag)
1695 else:
1696 self.assertRaises(ValueError, str, t)
1697
1698 def test_tzinfo_classes(self):
1699 cls = self.theclass
1700 class C1(tzinfo):
1701 def utcoffset(self, dt): return None
1702 def dst(self, dt): return None
1703 def tzname(self, dt): return None
1704 for t in (cls(1, 1, 1),
1705 cls(1, 1, 1, tzinfo=None),
1706 cls(1, 1, 1, tzinfo=C1())):
1707 self.failUnless(t.utcoffset() is None)
1708 self.failUnless(t.dst() is None)
1709 self.failUnless(t.tzname() is None)
1710
Tim Peters855fe882002-12-22 03:43:39 +00001711 class C3(tzinfo):
1712 def utcoffset(self, dt): return timedelta(minutes=-1439)
1713 def dst(self, dt): return timedelta(minutes=1439)
1714 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001715 t = cls(1, 1, 1, tzinfo=C3())
1716 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1717 self.assertEqual(t.dst(), timedelta(minutes=1439))
1718 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001719
1720 # Wrong types.
1721 class C4(tzinfo):
1722 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001723 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001724 def tzname(self, dt): return 0
1725 t = cls(1, 1, 1, tzinfo=C4())
1726 self.assertRaises(TypeError, t.utcoffset)
1727 self.assertRaises(TypeError, t.dst)
1728 self.assertRaises(TypeError, t.tzname)
1729
1730 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001731 class C6(tzinfo):
1732 def utcoffset(self, dt): return timedelta(hours=-24)
1733 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001734 t = cls(1, 1, 1, tzinfo=C6())
1735 self.assertRaises(ValueError, t.utcoffset)
1736 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001737
1738 # Not a whole number of minutes.
1739 class C7(tzinfo):
1740 def utcoffset(self, dt): return timedelta(seconds=61)
1741 def dst(self, dt): return timedelta(microseconds=-81)
1742 t = cls(1, 1, 1, tzinfo=C7())
1743 self.assertRaises(ValueError, t.utcoffset)
1744 self.assertRaises(ValueError, t.dst)
1745
Tim Peters4c0db782002-12-26 05:01:19 +00001746 def test_aware_compare(self):
1747 cls = self.theclass
1748
Tim Peters60c76e42002-12-27 00:41:11 +00001749 # Ensure that utcoffset() gets ignored if the comparands have
1750 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001751 class OperandDependentOffset(tzinfo):
1752 def utcoffset(self, t):
1753 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001754 # d0 and d1 equal after adjustment
1755 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001756 else:
Tim Peters397301e2003-01-02 21:28:08 +00001757 # d2 off in the weeds
1758 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001759
1760 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1761 d0 = base.replace(minute=3)
1762 d1 = base.replace(minute=9)
1763 d2 = base.replace(minute=11)
1764 for x in d0, d1, d2:
1765 for y in d0, d1, d2:
1766 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001767 expected = cmp(x.minute, y.minute)
1768 self.assertEqual(got, expected)
1769
1770 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001771 # Note that a time can't actually have an operand-depedent offset,
1772 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1773 # so skip this test for time.
1774 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001775 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1776 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1777 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1778 for x in d0, d1, d2:
1779 for y in d0, d1, d2:
1780 got = cmp(x, y)
1781 if (x is d0 or x is d1) and (y is d0 or y is d1):
1782 expected = 0
1783 elif x is y is d2:
1784 expected = 0
1785 elif x is d2:
1786 expected = -1
1787 else:
1788 assert y is d2
1789 expected = 1
1790 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001791
Tim Peters855fe882002-12-22 03:43:39 +00001792
Tim Peters0bf60bd2003-01-08 20:40:01 +00001793# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001794class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001795 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001796
1797 def test_empty(self):
1798 t = self.theclass()
1799 self.assertEqual(t.hour, 0)
1800 self.assertEqual(t.minute, 0)
1801 self.assertEqual(t.second, 0)
1802 self.assertEqual(t.microsecond, 0)
1803 self.failUnless(t.tzinfo is None)
1804
Tim Peters2a799bf2002-12-16 20:18:38 +00001805 def test_zones(self):
1806 est = FixedOffset(-300, "EST", 1)
1807 utc = FixedOffset(0, "UTC", -2)
1808 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001809 t1 = time( 7, 47, tzinfo=est)
1810 t2 = time(12, 47, tzinfo=utc)
1811 t3 = time(13, 47, tzinfo=met)
1812 t4 = time(microsecond=40)
1813 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001814
1815 self.assertEqual(t1.tzinfo, est)
1816 self.assertEqual(t2.tzinfo, utc)
1817 self.assertEqual(t3.tzinfo, met)
1818 self.failUnless(t4.tzinfo is None)
1819 self.assertEqual(t5.tzinfo, utc)
1820
Tim Peters855fe882002-12-22 03:43:39 +00001821 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1822 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1823 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001824 self.failUnless(t4.utcoffset() is None)
1825 self.assertRaises(TypeError, t1.utcoffset, "no args")
1826
1827 self.assertEqual(t1.tzname(), "EST")
1828 self.assertEqual(t2.tzname(), "UTC")
1829 self.assertEqual(t3.tzname(), "MET")
1830 self.failUnless(t4.tzname() is None)
1831 self.assertRaises(TypeError, t1.tzname, "no args")
1832
Tim Peters855fe882002-12-22 03:43:39 +00001833 self.assertEqual(t1.dst(), timedelta(minutes=1))
1834 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1835 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001836 self.failUnless(t4.dst() is None)
1837 self.assertRaises(TypeError, t1.dst, "no args")
1838
1839 self.assertEqual(hash(t1), hash(t2))
1840 self.assertEqual(hash(t1), hash(t3))
1841 self.assertEqual(hash(t2), hash(t3))
1842
1843 self.assertEqual(t1, t2)
1844 self.assertEqual(t1, t3)
1845 self.assertEqual(t2, t3)
1846 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1847 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1848 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1849
1850 self.assertEqual(str(t1), "07:47:00-05:00")
1851 self.assertEqual(str(t2), "12:47:00+00:00")
1852 self.assertEqual(str(t3), "13:47:00+01:00")
1853 self.assertEqual(str(t4), "00:00:00.000040")
1854 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1855
1856 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1857 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1858 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1859 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1860 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1861
Tim Peters0bf60bd2003-01-08 20:40:01 +00001862 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001863 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1864 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1865 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1866 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1867 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1868
1869 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1870 "07:47:00 %Z=EST %z=-0500")
1871 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1872 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1873
1874 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001875 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001876 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1877 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1878
Tim Petersb92bb712002-12-21 17:44:07 +00001879 # Check that an invalid tzname result raises an exception.
1880 class Badtzname(tzinfo):
1881 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001882 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001883 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1884 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001885
1886 def test_hash_edge_cases(self):
1887 # Offsets that overflow a basic time.
1888 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1889 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1890 self.assertEqual(hash(t1), hash(t2))
1891
1892 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1893 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1894 self.assertEqual(hash(t1), hash(t2))
1895
Tim Peters2a799bf2002-12-16 20:18:38 +00001896 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001897 # Try one without a tzinfo.
1898 args = 20, 59, 16, 64**2
1899 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001900 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001901 green = pickler.dumps(orig, proto)
1902 derived = unpickler.loads(green)
1903 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001904
1905 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001906 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001907 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001908 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001909 green = pickler.dumps(orig, proto)
1910 derived = unpickler.loads(green)
1911 self.assertEqual(orig, derived)
1912 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1913 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1914 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001915
1916 def test_more_bool(self):
1917 # Test cases with non-None tzinfo.
1918 cls = self.theclass
1919
1920 t = cls(0, tzinfo=FixedOffset(-300, ""))
1921 self.failUnless(t)
1922
1923 t = cls(5, tzinfo=FixedOffset(-300, ""))
1924 self.failUnless(t)
1925
1926 t = cls(5, tzinfo=FixedOffset(300, ""))
1927 self.failUnless(not t)
1928
1929 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1930 self.failUnless(not t)
1931
1932 # Mostly ensuring this doesn't overflow internally.
1933 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1934 self.failUnless(t)
1935
1936 # But this should yield a value error -- the utcoffset is bogus.
1937 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1938 self.assertRaises(ValueError, lambda: bool(t))
1939
1940 # Likewise.
1941 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1942 self.assertRaises(ValueError, lambda: bool(t))
1943
Tim Peters12bf3392002-12-24 05:41:27 +00001944 def test_replace(self):
1945 cls = self.theclass
1946 z100 = FixedOffset(100, "+100")
1947 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1948 args = [1, 2, 3, 4, z100]
1949 base = cls(*args)
1950 self.assertEqual(base, base.replace())
1951
1952 i = 0
1953 for name, newval in (("hour", 5),
1954 ("minute", 6),
1955 ("second", 7),
1956 ("microsecond", 8),
1957 ("tzinfo", zm200)):
1958 newargs = args[:]
1959 newargs[i] = newval
1960 expected = cls(*newargs)
1961 got = base.replace(**{name: newval})
1962 self.assertEqual(expected, got)
1963 i += 1
1964
1965 # Ensure we can get rid of a tzinfo.
1966 self.assertEqual(base.tzname(), "+100")
1967 base2 = base.replace(tzinfo=None)
1968 self.failUnless(base2.tzinfo is None)
1969 self.failUnless(base2.tzname() is None)
1970
1971 # Ensure we can add one.
1972 base3 = base2.replace(tzinfo=z100)
1973 self.assertEqual(base, base3)
1974 self.failUnless(base.tzinfo is base3.tzinfo)
1975
1976 # Out of bounds.
1977 base = cls(1)
1978 self.assertRaises(ValueError, base.replace, hour=24)
1979 self.assertRaises(ValueError, base.replace, minute=-1)
1980 self.assertRaises(ValueError, base.replace, second=100)
1981 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1982
Tim Peters60c76e42002-12-27 00:41:11 +00001983 def test_mixed_compare(self):
1984 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001985 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001986 self.assertEqual(t1, t2)
1987 t2 = t2.replace(tzinfo=None)
1988 self.assertEqual(t1, t2)
1989 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1990 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00001991 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1992 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001993
Tim Peters0bf60bd2003-01-08 20:40:01 +00001994 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00001995 class Varies(tzinfo):
1996 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00001997 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00001998 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00001999 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002000 return self.offset
2001
2002 v = Varies()
2003 t1 = t2.replace(tzinfo=v)
2004 t2 = t2.replace(tzinfo=v)
2005 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2006 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2007 self.assertEqual(t1, t2)
2008
2009 # But if they're not identical, it isn't ignored.
2010 t2 = t2.replace(tzinfo=Varies())
2011 self.failUnless(t1 < t2) # t1's offset counter still going up
2012
Tim Peters4c0db782002-12-26 05:01:19 +00002013
Tim Peters0bf60bd2003-01-08 20:40:01 +00002014# Testing datetime objects with a non-None tzinfo.
2015
Tim Peters855fe882002-12-22 03:43:39 +00002016class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002017 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002018
2019 def test_trivial(self):
2020 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2021 self.assertEqual(dt.year, 1)
2022 self.assertEqual(dt.month, 2)
2023 self.assertEqual(dt.day, 3)
2024 self.assertEqual(dt.hour, 4)
2025 self.assertEqual(dt.minute, 5)
2026 self.assertEqual(dt.second, 6)
2027 self.assertEqual(dt.microsecond, 7)
2028 self.assertEqual(dt.tzinfo, None)
2029
2030 def test_even_more_compare(self):
2031 # The test_compare() and test_more_compare() inherited from TestDate
2032 # and TestDateTime covered non-tzinfo cases.
2033
2034 # Smallest possible after UTC adjustment.
2035 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2036 # Largest possible after UTC adjustment.
2037 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2038 tzinfo=FixedOffset(-1439, ""))
2039
2040 # Make sure those compare correctly, and w/o overflow.
2041 self.failUnless(t1 < t2)
2042 self.failUnless(t1 != t2)
2043 self.failUnless(t2 > t1)
2044
2045 self.failUnless(t1 == t1)
2046 self.failUnless(t2 == t2)
2047
2048 # Equal afer adjustment.
2049 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2050 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2051 self.assertEqual(t1, t2)
2052
2053 # Change t1 not to subtract a minute, and t1 should be larger.
2054 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2055 self.failUnless(t1 > t2)
2056
2057 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2058 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2059 self.failUnless(t1 < t2)
2060
2061 # Back to the original t1, but make seconds resolve it.
2062 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2063 second=1)
2064 self.failUnless(t1 > t2)
2065
2066 # Likewise, but make microseconds resolve it.
2067 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2068 microsecond=1)
2069 self.failUnless(t1 > t2)
2070
2071 # Make t2 naive and it should fail.
2072 t2 = self.theclass.min
2073 self.assertRaises(TypeError, lambda: t1 == t2)
2074 self.assertEqual(t2, t2)
2075
2076 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2077 class Naive(tzinfo):
2078 def utcoffset(self, dt): return None
2079 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2080 self.assertRaises(TypeError, lambda: t1 == t2)
2081 self.assertEqual(t2, t2)
2082
2083 # OTOH, it's OK to compare two of these mixing the two ways of being
2084 # naive.
2085 t1 = self.theclass(5, 6, 7)
2086 self.assertEqual(t1, t2)
2087
2088 # Try a bogus uctoffset.
2089 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002090 def utcoffset(self, dt):
2091 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002092 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2093 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002094 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002095
Tim Peters2a799bf2002-12-16 20:18:38 +00002096 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002097 # Try one without a tzinfo.
2098 args = 6, 7, 23, 20, 59, 1, 64**2
2099 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002100 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002101 green = pickler.dumps(orig, proto)
2102 derived = unpickler.loads(green)
2103 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002104
2105 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002106 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002107 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002108 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002109 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002110 green = pickler.dumps(orig, proto)
2111 derived = unpickler.loads(green)
2112 self.assertEqual(orig, derived)
2113 self.failUnless(isinstance(derived.tzinfo,
2114 PicklableFixedOffset))
2115 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2116 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002117
2118 def test_extreme_hashes(self):
2119 # If an attempt is made to hash these via subtracting the offset
2120 # then hashing a datetime object, OverflowError results. The
2121 # Python implementation used to blow up here.
2122 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2123 hash(t)
2124 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2125 tzinfo=FixedOffset(-1439, ""))
2126 hash(t)
2127
2128 # OTOH, an OOB offset should blow up.
2129 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2130 self.assertRaises(ValueError, hash, t)
2131
2132 def test_zones(self):
2133 est = FixedOffset(-300, "EST")
2134 utc = FixedOffset(0, "UTC")
2135 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002136 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2137 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2138 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002139 self.assertEqual(t1.tzinfo, est)
2140 self.assertEqual(t2.tzinfo, utc)
2141 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002142 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2143 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2144 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002145 self.assertEqual(t1.tzname(), "EST")
2146 self.assertEqual(t2.tzname(), "UTC")
2147 self.assertEqual(t3.tzname(), "MET")
2148 self.assertEqual(hash(t1), hash(t2))
2149 self.assertEqual(hash(t1), hash(t3))
2150 self.assertEqual(hash(t2), hash(t3))
2151 self.assertEqual(t1, t2)
2152 self.assertEqual(t1, t3)
2153 self.assertEqual(t2, t3)
2154 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2155 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2156 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002157 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002158 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2159 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2160 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2161
2162 def test_combine(self):
2163 met = FixedOffset(60, "MET")
2164 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002165 tz = time(18, 45, 3, 1234, tzinfo=met)
2166 dt = datetime.combine(d, tz)
2167 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002168 tzinfo=met))
2169
2170 def test_extract(self):
2171 met = FixedOffset(60, "MET")
2172 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2173 self.assertEqual(dt.date(), date(2002, 3, 4))
2174 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002175 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002176
2177 def test_tz_aware_arithmetic(self):
2178 import random
2179
2180 now = self.theclass.now()
2181 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002182 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002183 nowaware = self.theclass.combine(now.date(), timeaware)
2184 self.failUnless(nowaware.tzinfo is tz55)
2185 self.assertEqual(nowaware.timetz(), timeaware)
2186
2187 # Can't mix aware and non-aware.
2188 self.assertRaises(TypeError, lambda: now - nowaware)
2189 self.assertRaises(TypeError, lambda: nowaware - now)
2190
Tim Peters0bf60bd2003-01-08 20:40:01 +00002191 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002192 self.assertRaises(TypeError, lambda: now + nowaware)
2193 self.assertRaises(TypeError, lambda: nowaware + now)
2194 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2195
2196 # Subtracting should yield 0.
2197 self.assertEqual(now - now, timedelta(0))
2198 self.assertEqual(nowaware - nowaware, timedelta(0))
2199
2200 # Adding a delta should preserve tzinfo.
2201 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2202 nowawareplus = nowaware + delta
2203 self.failUnless(nowaware.tzinfo is tz55)
2204 nowawareplus2 = delta + nowaware
2205 self.failUnless(nowawareplus2.tzinfo is tz55)
2206 self.assertEqual(nowawareplus, nowawareplus2)
2207
2208 # that - delta should be what we started with, and that - what we
2209 # started with should be delta.
2210 diff = nowawareplus - delta
2211 self.failUnless(diff.tzinfo is tz55)
2212 self.assertEqual(nowaware, diff)
2213 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2214 self.assertEqual(nowawareplus - nowaware, delta)
2215
2216 # Make up a random timezone.
2217 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002218 # Attach it to nowawareplus.
2219 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002220 self.failUnless(nowawareplus.tzinfo is tzr)
2221 # Make sure the difference takes the timezone adjustments into account.
2222 got = nowaware - nowawareplus
2223 # Expected: (nowaware base - nowaware offset) -
2224 # (nowawareplus base - nowawareplus offset) =
2225 # (nowaware base - nowawareplus base) +
2226 # (nowawareplus offset - nowaware offset) =
2227 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002228 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002229 self.assertEqual(got, expected)
2230
2231 # Try max possible difference.
2232 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2233 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2234 tzinfo=FixedOffset(-1439, "max"))
2235 maxdiff = max - min
2236 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2237 timedelta(minutes=2*1439))
2238
2239 def test_tzinfo_now(self):
2240 meth = self.theclass.now
2241 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2242 base = meth()
2243 # Try with and without naming the keyword.
2244 off42 = FixedOffset(42, "42")
2245 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002246 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002247 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002248 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002249 # Bad argument with and w/o naming the keyword.
2250 self.assertRaises(TypeError, meth, 16)
2251 self.assertRaises(TypeError, meth, tzinfo=16)
2252 # Bad keyword name.
2253 self.assertRaises(TypeError, meth, tinfo=off42)
2254 # Too many args.
2255 self.assertRaises(TypeError, meth, off42, off42)
2256
Tim Peters10cadce2003-01-23 19:58:02 +00002257 # We don't know which time zone we're in, and don't have a tzinfo
2258 # class to represent it, so seeing whether a tz argument actually
2259 # does a conversion is tricky.
2260 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2261 utc = FixedOffset(0, "utc", 0)
2262 for dummy in range(3):
2263 now = datetime.now(weirdtz)
2264 self.failUnless(now.tzinfo is weirdtz)
2265 utcnow = datetime.utcnow().replace(tzinfo=utc)
2266 now2 = utcnow.astimezone(weirdtz)
2267 if abs(now - now2) < timedelta(seconds=30):
2268 break
2269 # Else the code is broken, or more than 30 seconds passed between
2270 # calls; assuming the latter, just try again.
2271 else:
2272 # Three strikes and we're out.
2273 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2274
Tim Peters2a799bf2002-12-16 20:18:38 +00002275 def test_tzinfo_fromtimestamp(self):
2276 import time
2277 meth = self.theclass.fromtimestamp
2278 ts = time.time()
2279 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2280 base = meth(ts)
2281 # Try with and without naming the keyword.
2282 off42 = FixedOffset(42, "42")
2283 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002284 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002285 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002286 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002287 # Bad argument with and w/o naming the keyword.
2288 self.assertRaises(TypeError, meth, ts, 16)
2289 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2290 # Bad keyword name.
2291 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2292 # Too many args.
2293 self.assertRaises(TypeError, meth, ts, off42, off42)
2294 # Too few args.
2295 self.assertRaises(TypeError, meth)
2296
Tim Peters2a44a8d2003-01-23 20:53:10 +00002297 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002298 timestamp = 1000000000
2299 utcdatetime = datetime.utcfromtimestamp(timestamp)
2300 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2301 # But on some flavor of Mac, it's nowhere near that. So we can't have
2302 # any idea here what time that actually is, we can only test that
2303 # relative changes match.
2304 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2305 tz = FixedOffset(utcoffset, "tz", 0)
2306 expected = utcdatetime + utcoffset
2307 got = datetime.fromtimestamp(timestamp, tz)
2308 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002309
Tim Peters2a799bf2002-12-16 20:18:38 +00002310 def test_tzinfo_utcnow(self):
2311 meth = self.theclass.utcnow
2312 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2313 base = meth()
2314 # Try with and without naming the keyword; for whatever reason,
2315 # utcnow() doesn't accept a tzinfo argument.
2316 off42 = FixedOffset(42, "42")
2317 self.assertRaises(TypeError, meth, off42)
2318 self.assertRaises(TypeError, meth, tzinfo=off42)
2319
2320 def test_tzinfo_utcfromtimestamp(self):
2321 import time
2322 meth = self.theclass.utcfromtimestamp
2323 ts = time.time()
2324 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2325 base = meth(ts)
2326 # Try with and without naming the keyword; for whatever reason,
2327 # utcfromtimestamp() doesn't accept a tzinfo argument.
2328 off42 = FixedOffset(42, "42")
2329 self.assertRaises(TypeError, meth, ts, off42)
2330 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2331
2332 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002333 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002334 # DST flag.
2335 class DST(tzinfo):
2336 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002337 if isinstance(dstvalue, int):
2338 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002339 self.dstvalue = dstvalue
2340 def dst(self, dt):
2341 return self.dstvalue
2342
2343 cls = self.theclass
2344 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2345 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2346 t = d.timetuple()
2347 self.assertEqual(1, t.tm_year)
2348 self.assertEqual(1, t.tm_mon)
2349 self.assertEqual(1, t.tm_mday)
2350 self.assertEqual(10, t.tm_hour)
2351 self.assertEqual(20, t.tm_min)
2352 self.assertEqual(30, t.tm_sec)
2353 self.assertEqual(0, t.tm_wday)
2354 self.assertEqual(1, t.tm_yday)
2355 self.assertEqual(flag, t.tm_isdst)
2356
2357 # dst() returns wrong type.
2358 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2359
2360 # dst() at the edge.
2361 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2362 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2363
2364 # dst() out of range.
2365 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2366 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2367
2368 def test_utctimetuple(self):
2369 class DST(tzinfo):
2370 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002371 if isinstance(dstvalue, int):
2372 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002373 self.dstvalue = dstvalue
2374 def dst(self, dt):
2375 return self.dstvalue
2376
2377 cls = self.theclass
2378 # This can't work: DST didn't implement utcoffset.
2379 self.assertRaises(NotImplementedError,
2380 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2381
2382 class UOFS(DST):
2383 def __init__(self, uofs, dofs=None):
2384 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002385 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002386 def utcoffset(self, dt):
2387 return self.uofs
2388
2389 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2390 # in effect for a UTC time.
2391 for dstvalue in -33, 33, 0, None:
2392 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2393 t = d.utctimetuple()
2394 self.assertEqual(d.year, t.tm_year)
2395 self.assertEqual(d.month, t.tm_mon)
2396 self.assertEqual(d.day, t.tm_mday)
2397 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2398 self.assertEqual(13, t.tm_min)
2399 self.assertEqual(d.second, t.tm_sec)
2400 self.assertEqual(d.weekday(), t.tm_wday)
2401 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2402 t.tm_yday)
2403 self.assertEqual(0, t.tm_isdst)
2404
2405 # At the edges, UTC adjustment can normalize into years out-of-range
2406 # for a datetime object. Ensure that a correct timetuple is
2407 # created anyway.
2408 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2409 # That goes back 1 minute less than a full day.
2410 t = tiny.utctimetuple()
2411 self.assertEqual(t.tm_year, MINYEAR-1)
2412 self.assertEqual(t.tm_mon, 12)
2413 self.assertEqual(t.tm_mday, 31)
2414 self.assertEqual(t.tm_hour, 0)
2415 self.assertEqual(t.tm_min, 1)
2416 self.assertEqual(t.tm_sec, 37)
2417 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2418 self.assertEqual(t.tm_isdst, 0)
2419
2420 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2421 # That goes forward 1 minute less than a full day.
2422 t = huge.utctimetuple()
2423 self.assertEqual(t.tm_year, MAXYEAR+1)
2424 self.assertEqual(t.tm_mon, 1)
2425 self.assertEqual(t.tm_mday, 1)
2426 self.assertEqual(t.tm_hour, 23)
2427 self.assertEqual(t.tm_min, 58)
2428 self.assertEqual(t.tm_sec, 37)
2429 self.assertEqual(t.tm_yday, 1)
2430 self.assertEqual(t.tm_isdst, 0)
2431
2432 def test_tzinfo_isoformat(self):
2433 zero = FixedOffset(0, "+00:00")
2434 plus = FixedOffset(220, "+03:40")
2435 minus = FixedOffset(-231, "-03:51")
2436 unknown = FixedOffset(None, "")
2437
2438 cls = self.theclass
2439 datestr = '0001-02-03'
2440 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002441 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002442 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2443 timestr = '04:05:59' + (us and '.987001' or '')
2444 ofsstr = ofs is not None and d.tzname() or ''
2445 tailstr = timestr + ofsstr
2446 iso = d.isoformat()
2447 self.assertEqual(iso, datestr + 'T' + tailstr)
2448 self.assertEqual(iso, d.isoformat('T'))
2449 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2450 self.assertEqual(str(d), datestr + ' ' + tailstr)
2451
Tim Peters12bf3392002-12-24 05:41:27 +00002452 def test_replace(self):
2453 cls = self.theclass
2454 z100 = FixedOffset(100, "+100")
2455 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2456 args = [1, 2, 3, 4, 5, 6, 7, z100]
2457 base = cls(*args)
2458 self.assertEqual(base, base.replace())
2459
2460 i = 0
2461 for name, newval in (("year", 2),
2462 ("month", 3),
2463 ("day", 4),
2464 ("hour", 5),
2465 ("minute", 6),
2466 ("second", 7),
2467 ("microsecond", 8),
2468 ("tzinfo", zm200)):
2469 newargs = args[:]
2470 newargs[i] = newval
2471 expected = cls(*newargs)
2472 got = base.replace(**{name: newval})
2473 self.assertEqual(expected, got)
2474 i += 1
2475
2476 # Ensure we can get rid of a tzinfo.
2477 self.assertEqual(base.tzname(), "+100")
2478 base2 = base.replace(tzinfo=None)
2479 self.failUnless(base2.tzinfo is None)
2480 self.failUnless(base2.tzname() is None)
2481
2482 # Ensure we can add one.
2483 base3 = base2.replace(tzinfo=z100)
2484 self.assertEqual(base, base3)
2485 self.failUnless(base.tzinfo is base3.tzinfo)
2486
2487 # Out of bounds.
2488 base = cls(2000, 2, 29)
2489 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002490
Tim Peters80475bb2002-12-25 07:40:55 +00002491 def test_more_astimezone(self):
2492 # The inherited test_astimezone covered some trivial and error cases.
2493 fnone = FixedOffset(None, "None")
2494 f44m = FixedOffset(44, "44")
2495 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2496
Tim Peters10cadce2003-01-23 19:58:02 +00002497 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002498 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002499 # Replacing with degenerate tzinfo raises an exception.
2500 self.assertRaises(ValueError, dt.astimezone, fnone)
2501 # Ditto with None tz.
2502 self.assertRaises(TypeError, dt.astimezone, None)
2503 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002504 x = dt.astimezone(dt.tzinfo)
2505 self.failUnless(x.tzinfo is f44m)
2506 self.assertEqual(x.date(), dt.date())
2507 self.assertEqual(x.time(), dt.time())
2508
2509 # Replacing with different tzinfo does adjust.
2510 got = dt.astimezone(fm5h)
2511 self.failUnless(got.tzinfo is fm5h)
2512 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2513 expected = dt - dt.utcoffset() # in effect, convert to UTC
2514 expected += fm5h.utcoffset(dt) # and from there to local time
2515 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2516 self.assertEqual(got.date(), expected.date())
2517 self.assertEqual(got.time(), expected.time())
2518 self.assertEqual(got.timetz(), expected.timetz())
2519 self.failUnless(got.tzinfo is expected.tzinfo)
2520 self.assertEqual(got, expected)
2521
Tim Peters4c0db782002-12-26 05:01:19 +00002522 def test_aware_subtract(self):
2523 cls = self.theclass
2524
Tim Peters60c76e42002-12-27 00:41:11 +00002525 # Ensure that utcoffset() is ignored when the operands have the
2526 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002527 class OperandDependentOffset(tzinfo):
2528 def utcoffset(self, t):
2529 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002530 # d0 and d1 equal after adjustment
2531 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002532 else:
Tim Peters397301e2003-01-02 21:28:08 +00002533 # d2 off in the weeds
2534 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002535
2536 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2537 d0 = base.replace(minute=3)
2538 d1 = base.replace(minute=9)
2539 d2 = base.replace(minute=11)
2540 for x in d0, d1, d2:
2541 for y in d0, d1, d2:
2542 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002543 expected = timedelta(minutes=x.minute - y.minute)
2544 self.assertEqual(got, expected)
2545
2546 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2547 # ignored.
2548 base = cls(8, 9, 10, 11, 12, 13, 14)
2549 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2550 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2551 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2552 for x in d0, d1, d2:
2553 for y in d0, d1, d2:
2554 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002555 if (x is d0 or x is d1) and (y is d0 or y is d1):
2556 expected = timedelta(0)
2557 elif x is y is d2:
2558 expected = timedelta(0)
2559 elif x is d2:
2560 expected = timedelta(minutes=(11-59)-0)
2561 else:
2562 assert y is d2
2563 expected = timedelta(minutes=0-(11-59))
2564 self.assertEqual(got, expected)
2565
Tim Peters60c76e42002-12-27 00:41:11 +00002566 def test_mixed_compare(self):
2567 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002568 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002569 self.assertEqual(t1, t2)
2570 t2 = t2.replace(tzinfo=None)
2571 self.assertEqual(t1, t2)
2572 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2573 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002574 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2575 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002576
Tim Peters0bf60bd2003-01-08 20:40:01 +00002577 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002578 class Varies(tzinfo):
2579 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002580 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002581 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002582 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002583 return self.offset
2584
2585 v = Varies()
2586 t1 = t2.replace(tzinfo=v)
2587 t2 = t2.replace(tzinfo=v)
2588 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2589 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2590 self.assertEqual(t1, t2)
2591
2592 # But if they're not identical, it isn't ignored.
2593 t2 = t2.replace(tzinfo=Varies())
2594 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002595
Tim Peters621818b2002-12-29 23:44:49 +00002596# Pain to set up DST-aware tzinfo classes.
2597
2598def first_sunday_on_or_after(dt):
2599 days_to_go = 6 - dt.weekday()
2600 if days_to_go:
2601 dt += timedelta(days_to_go)
2602 return dt
2603
2604ZERO = timedelta(0)
2605HOUR = timedelta(hours=1)
2606DAY = timedelta(days=1)
2607# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2608DSTSTART = datetime(1, 4, 1, 2)
2609# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002610# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2611# being standard time on that day, there is no spelling in local time of
2612# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2613DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002614
2615class USTimeZone(tzinfo):
2616
2617 def __init__(self, hours, reprname, stdname, dstname):
2618 self.stdoffset = timedelta(hours=hours)
2619 self.reprname = reprname
2620 self.stdname = stdname
2621 self.dstname = dstname
2622
2623 def __repr__(self):
2624 return self.reprname
2625
2626 def tzname(self, dt):
2627 if self.dst(dt):
2628 return self.dstname
2629 else:
2630 return self.stdname
2631
2632 def utcoffset(self, dt):
2633 return self.stdoffset + self.dst(dt)
2634
2635 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002636 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002637 # An exception instead may be sensible here, in one or more of
2638 # the cases.
2639 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002640 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002641
2642 # Find first Sunday in April.
2643 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2644 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2645
2646 # Find last Sunday in October.
2647 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2648 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2649
Tim Peters621818b2002-12-29 23:44:49 +00002650 # Can't compare naive to aware objects, so strip the timezone from
2651 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002652 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002653 return HOUR
2654 else:
2655 return ZERO
2656
Tim Peters521fc152002-12-31 17:36:56 +00002657Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2658Central = USTimeZone(-6, "Central", "CST", "CDT")
2659Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2660Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002661utc_real = FixedOffset(0, "UTC", 0)
2662# For better test coverage, we want another flavor of UTC that's west of
2663# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002664utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002665
2666class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002667 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002668 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002669 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002670
Tim Peters0bf60bd2003-01-08 20:40:01 +00002671 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002672
Tim Peters521fc152002-12-31 17:36:56 +00002673 # Check a time that's inside DST.
2674 def checkinside(self, dt, tz, utc, dston, dstoff):
2675 self.assertEqual(dt.dst(), HOUR)
2676
2677 # Conversion to our own timezone is always an identity.
2678 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002679
2680 asutc = dt.astimezone(utc)
2681 there_and_back = asutc.astimezone(tz)
2682
2683 # Conversion to UTC and back isn't always an identity here,
2684 # because there are redundant spellings (in local time) of
2685 # UTC time when DST begins: the clock jumps from 1:59:59
2686 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2687 # make sense then. The classes above treat 2:MM:SS as
2688 # daylight time then (it's "after 2am"), really an alias
2689 # for 1:MM:SS standard time. The latter form is what
2690 # conversion back from UTC produces.
2691 if dt.date() == dston.date() and dt.hour == 2:
2692 # We're in the redundant hour, and coming back from
2693 # UTC gives the 1:MM:SS standard-time spelling.
2694 self.assertEqual(there_and_back + HOUR, dt)
2695 # Although during was considered to be in daylight
2696 # time, there_and_back is not.
2697 self.assertEqual(there_and_back.dst(), ZERO)
2698 # They're the same times in UTC.
2699 self.assertEqual(there_and_back.astimezone(utc),
2700 dt.astimezone(utc))
2701 else:
2702 # We're not in the redundant hour.
2703 self.assertEqual(dt, there_and_back)
2704
Tim Peters327098a2003-01-20 22:54:38 +00002705 # Because we have a redundant spelling when DST begins, there is
2706 # (unforunately) an hour when DST ends that can't be spelled at all in
2707 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2708 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2709 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2710 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2711 # expressed in local time. Nevertheless, we want conversion back
2712 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002713 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002714 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002715 if dt.date() == dstoff.date() and dt.hour == 0:
2716 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002717 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002718 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2719 nexthour_utc += HOUR
2720 nexthour_tz = nexthour_utc.astimezone(tz)
2721 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002722 else:
Tim Peters327098a2003-01-20 22:54:38 +00002723 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002724
2725 # Check a time that's outside DST.
2726 def checkoutside(self, dt, tz, utc):
2727 self.assertEqual(dt.dst(), ZERO)
2728
2729 # Conversion to our own timezone is always an identity.
2730 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002731
2732 # Converting to UTC and back is an identity too.
2733 asutc = dt.astimezone(utc)
2734 there_and_back = asutc.astimezone(tz)
2735 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002736
Tim Peters1024bf82002-12-30 17:09:40 +00002737 def convert_between_tz_and_utc(self, tz, utc):
2738 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002739 # Because 1:MM on the day DST ends is taken as being standard time,
2740 # there is no spelling in tz for the last hour of daylight time.
2741 # For purposes of the test, the last hour of DST is 0:MM, which is
2742 # taken as being daylight time (and 1:MM is taken as being standard
2743 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002744 dstoff = self.dstoff.replace(tzinfo=tz)
2745 for delta in (timedelta(weeks=13),
2746 DAY,
2747 HOUR,
2748 timedelta(minutes=1),
2749 timedelta(microseconds=1)):
2750
Tim Peters521fc152002-12-31 17:36:56 +00002751 self.checkinside(dston, tz, utc, dston, dstoff)
2752 for during in dston + delta, dstoff - delta:
2753 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002754
Tim Peters521fc152002-12-31 17:36:56 +00002755 self.checkoutside(dstoff, tz, utc)
2756 for outside in dston - delta, dstoff + delta:
2757 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002758
Tim Peters621818b2002-12-29 23:44:49 +00002759 def test_easy(self):
2760 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002761 self.convert_between_tz_and_utc(Eastern, utc_real)
2762 self.convert_between_tz_and_utc(Pacific, utc_real)
2763 self.convert_between_tz_and_utc(Eastern, utc_fake)
2764 self.convert_between_tz_and_utc(Pacific, utc_fake)
2765 # The next is really dancing near the edge. It works because
2766 # Pacific and Eastern are far enough apart that their "problem
2767 # hours" don't overlap.
2768 self.convert_between_tz_and_utc(Eastern, Pacific)
2769 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002770 # OTOH, these fail! Don't enable them. The difficulty is that
2771 # the edge case tests assume that every hour is representable in
2772 # the "utc" class. This is always true for a fixed-offset tzinfo
2773 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2774 # For these adjacent DST-aware time zones, the range of time offsets
2775 # tested ends up creating hours in the one that aren't representable
2776 # in the other. For the same reason, we would see failures in the
2777 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2778 # offset deltas in convert_between_tz_and_utc().
2779 #
2780 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2781 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002782
Tim Petersf3615152003-01-01 21:51:37 +00002783 def test_tricky(self):
2784 # 22:00 on day before daylight starts.
2785 fourback = self.dston - timedelta(hours=4)
2786 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002787 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002788 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2789 # 2", we should get the 3 spelling.
2790 # If we plug 22:00 the day before into Eastern, it "looks like std
2791 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2792 # to 22:00 lands on 2:00, which makes no sense in local time (the
2793 # local clock jumps from 1 to 3). The point here is to make sure we
2794 # get the 3 spelling.
2795 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002796 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002797 self.assertEqual(expected, got)
2798
2799 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2800 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002801 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002802 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2803 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2804 # spelling.
2805 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002806 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002807 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002808
Tim Petersadf64202003-01-04 06:03:15 +00002809 # Now on the day DST ends, we want "repeat an hour" behavior.
2810 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2811 # EST 23:MM 0:MM 1:MM 2:MM
2812 # EDT 0:MM 1:MM 2:MM 3:MM
2813 # wall 0:MM 1:MM 1:MM 2:MM against these
2814 for utc in utc_real, utc_fake:
2815 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002816 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002817 # Convert that to UTC.
2818 first_std_hour -= tz.utcoffset(None)
2819 # Adjust for possibly fake UTC.
2820 asutc = first_std_hour + utc.utcoffset(None)
2821 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2822 # tz=Eastern.
2823 asutcbase = asutc.replace(tzinfo=utc)
2824 for tzhour in (0, 1, 1, 2):
2825 expectedbase = self.dstoff.replace(hour=tzhour)
2826 for minute in 0, 30, 59:
2827 expected = expectedbase.replace(minute=minute)
2828 asutc = asutcbase.replace(minute=minute)
2829 astz = asutc.astimezone(tz)
2830 self.assertEqual(astz.replace(tzinfo=None), expected)
2831 asutcbase += HOUR
2832
2833
Tim Peters710fb152003-01-02 19:35:54 +00002834 def test_bogus_dst(self):
2835 class ok(tzinfo):
2836 def utcoffset(self, dt): return HOUR
2837 def dst(self, dt): return HOUR
2838
2839 now = self.theclass.now().replace(tzinfo=utc_real)
2840 # Doesn't blow up.
2841 now.astimezone(ok())
2842
2843 # Does blow up.
2844 class notok(ok):
2845 def dst(self, dt): return None
2846 self.assertRaises(ValueError, now.astimezone, notok())
2847
Tim Peters52dcce22003-01-23 16:36:11 +00002848 def test_fromutc(self):
2849 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2850 now = datetime.utcnow().replace(tzinfo=utc_real)
2851 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2852 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2853 enow = Eastern.fromutc(now) # doesn't blow up
2854 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2855 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2856 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2857
2858 # Always converts UTC to standard time.
2859 class FauxUSTimeZone(USTimeZone):
2860 def fromutc(self, dt):
2861 return dt + self.stdoffset
2862 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2863
2864 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2865 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2866 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2867
2868 # Check around DST start.
2869 start = self.dston.replace(hour=4, tzinfo=Eastern)
2870 fstart = start.replace(tzinfo=FEastern)
2871 for wall in 23, 0, 1, 3, 4, 5:
2872 expected = start.replace(hour=wall)
2873 if wall == 23:
2874 expected -= timedelta(days=1)
2875 got = Eastern.fromutc(start)
2876 self.assertEqual(expected, got)
2877
2878 expected = fstart + FEastern.stdoffset
2879 got = FEastern.fromutc(fstart)
2880 self.assertEqual(expected, got)
2881
2882 # Ensure astimezone() calls fromutc() too.
2883 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2884 self.assertEqual(expected, got)
2885
2886 start += HOUR
2887 fstart += HOUR
2888
2889 # Check around DST end.
2890 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2891 fstart = start.replace(tzinfo=FEastern)
2892 for wall in 0, 1, 1, 2, 3, 4:
2893 expected = start.replace(hour=wall)
2894 got = Eastern.fromutc(start)
2895 self.assertEqual(expected, got)
2896
2897 expected = fstart + FEastern.stdoffset
2898 got = FEastern.fromutc(fstart)
2899 self.assertEqual(expected, got)
2900
2901 # Ensure astimezone() calls fromutc() too.
2902 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2903 self.assertEqual(expected, got)
2904
2905 start += HOUR
2906 fstart += HOUR
2907
Tim Peters710fb152003-01-02 19:35:54 +00002908
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002909def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002910 allsuites = [unittest.makeSuite(klass, 'test')
2911 for klass in (TestModule,
2912 TestTZInfo,
2913 TestTimeDelta,
2914 TestDateOnly,
2915 TestDate,
2916 TestDateTime,
2917 TestTime,
2918 TestTimeTZ,
2919 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002920 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002921 )
2922 ]
2923 return unittest.TestSuite(allsuites)
2924
2925def test_main():
2926 import gc
2927 import sys
2928
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002929 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002930 lastrc = None
2931 while True:
2932 test_support.run_suite(thesuite)
2933 if 1: # change to 0, under a debug build, for some leak detection
2934 break
2935 gc.collect()
2936 if gc.garbage:
2937 raise SystemError("gc.garbage not empty after test run: %r" %
2938 gc.garbage)
2939 if hasattr(sys, 'gettotalrefcount'):
2940 thisrc = sys.gettotalrefcount()
2941 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2942 if lastrc:
2943 print >> sys.stderr, 'delta:', thisrc - lastrc
2944 else:
2945 print >> sys.stderr
2946 lastrc = thisrc
2947
2948if __name__ == "__main__":
2949 test_main()