blob: 8d6fe19810e99cb1f75f28e8b07ace35d0857454 [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 Peters0bf60bd2003-01-08 20:40:01 +000025# XXX The test suite uncovered a bug in Python 2.2.2: if x and y are
26# XXX instances of new-style classes (like date and time) that both
27# XXX define __cmp__, and x is compared to y, and one of the __cmp__
28# XXX implementations raises an exception, the exception can get dropped
29# XXX on the floor when it occurs, and pop up again at some "random" time
30# XXX later (it depends on when the next opcode gets executed that
31# XXX bothers to check). There isn't a workaround for this, so instead
32# XXX we disable the parts of the tests that trigger it unless
33# XXX CMP_BUG_FIXED is true. The bug is still there, we simply avoid
34# XXX provoking it here.
35# XXX Guido checked into a fix that will go into 2.2.3. The bug was
36# XXX already fixed in 2.3 CVS via a different means.
37CMP_BUG_FIXED = sys.version_info >= (2, 2, 3)
38
Tim Peters2a799bf2002-12-16 20:18:38 +000039
40#############################################################################
41# module tests
42
43class TestModule(unittest.TestCase):
44
45 def test_constants(self):
46 import datetime
47 self.assertEqual(datetime.MINYEAR, 1)
48 self.assertEqual(datetime.MAXYEAR, 9999)
49
50#############################################################################
51# tzinfo tests
52
53class FixedOffset(tzinfo):
54 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000055 if isinstance(offset, int):
56 offset = timedelta(minutes=offset)
57 if isinstance(dstoffset, int):
58 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000059 self.__offset = offset
60 self.__name = name
61 self.__dstoffset = dstoffset
62 def __repr__(self):
63 return self.__name.lower()
64 def utcoffset(self, dt):
65 return self.__offset
66 def tzname(self, dt):
67 return self.__name
68 def dst(self, dt):
69 return self.__dstoffset
70
Tim Petersfb8472c2002-12-21 05:04:42 +000071class PicklableFixedOffset(FixedOffset):
72 def __init__(self, offset=None, name=None, dstoffset=None):
73 FixedOffset.__init__(self, offset, name, dstoffset)
74
Tim Peters2a799bf2002-12-16 20:18:38 +000075class TestTZInfo(unittest.TestCase):
76
77 def test_non_abstractness(self):
78 # In order to allow subclasses to get pickled, the C implementation
79 # wasn't able to get away with having __init__ raise
80 # NotImplementedError.
81 useless = tzinfo()
82 dt = datetime.max
83 self.assertRaises(NotImplementedError, useless.tzname, dt)
84 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
85 self.assertRaises(NotImplementedError, useless.dst, dt)
86
87 def test_subclass_must_override(self):
88 class NotEnough(tzinfo):
89 def __init__(self, offset, name):
90 self.__offset = offset
91 self.__name = name
92 self.failUnless(issubclass(NotEnough, tzinfo))
93 ne = NotEnough(3, "NotByALongShot")
94 self.failUnless(isinstance(ne, tzinfo))
95
96 dt = datetime.now()
97 self.assertRaises(NotImplementedError, ne.tzname, dt)
98 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
99 self.assertRaises(NotImplementedError, ne.dst, dt)
100
101 def test_normal(self):
102 fo = FixedOffset(3, "Three")
103 self.failUnless(isinstance(fo, tzinfo))
104 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +0000105 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +0000106 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +0000107 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +0000108
109 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000110 # There's no point to pickling tzinfo objects on their own (they
111 # carry no data), but they need to be picklable anyway else
112 # concrete subclasses can't be pickled.
113 orig = tzinfo.__new__(tzinfo)
114 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000115 for pickler, unpickler, proto in pickle_choices:
116 green = pickler.dumps(orig, proto)
117 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000118 self.failUnless(type(derived) is tzinfo)
119
120 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000121 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000122 offset = timedelta(minutes=-300)
123 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000124 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000125 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000126 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000127 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000128 for pickler, unpickler, proto in pickle_choices:
129 green = pickler.dumps(orig, proto)
130 derived = unpickler.loads(green)
Tim Peters2a799bf2002-12-16 20:18:38 +0000131 self.failUnless(isinstance(derived, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000132 self.failUnless(type(derived) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000133 self.assertEqual(derived.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000134 self.assertEqual(derived.tzname(None), 'cookie')
135
136#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000137# Base clase for testing a particular aspect of timedelta, time, date and
138# datetime comparisons.
139
140class HarmlessMixedComparison(unittest.TestCase):
141 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
142
143 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
144 # legit constructor.
145
146 def test_harmless_mixed_comparison(self):
147 me = self.theclass(1, 1, 1)
148
149 self.failIf(me == ())
150 self.failUnless(me != ())
151 self.failIf(() == me)
152 self.failUnless(() != me)
153
154 self.failUnless(me in [1, 20L, [], me])
155 self.failIf(me not in [1, 20L, [], me])
156
157 self.failUnless([] in [me, 1, 20L, []])
158 self.failIf([] not in [me, 1, 20L, []])
159
160 def test_harmful_mixed_comparison(self):
161 me = self.theclass(1, 1, 1)
162
163 self.assertRaises(TypeError, lambda: me < ())
164 self.assertRaises(TypeError, lambda: me <= ())
165 self.assertRaises(TypeError, lambda: me > ())
166 self.assertRaises(TypeError, lambda: me >= ())
167
168 self.assertRaises(TypeError, lambda: () < me)
169 self.assertRaises(TypeError, lambda: () <= me)
170 self.assertRaises(TypeError, lambda: () > me)
171 self.assertRaises(TypeError, lambda: () >= me)
172
173 self.assertRaises(TypeError, cmp, (), me)
174 self.assertRaises(TypeError, cmp, me, ())
175
176#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000177# timedelta tests
178
Tim Peters07534a62003-02-07 22:50:28 +0000179class TestTimeDelta(HarmlessMixedComparison):
180
181 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000182
183 def test_constructor(self):
184 eq = self.assertEqual
185 td = timedelta
186
187 # Check keyword args to constructor
188 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
189 milliseconds=0, microseconds=0))
190 eq(td(1), td(days=1))
191 eq(td(0, 1), td(seconds=1))
192 eq(td(0, 0, 1), td(microseconds=1))
193 eq(td(weeks=1), td(days=7))
194 eq(td(days=1), td(hours=24))
195 eq(td(hours=1), td(minutes=60))
196 eq(td(minutes=1), td(seconds=60))
197 eq(td(seconds=1), td(milliseconds=1000))
198 eq(td(milliseconds=1), td(microseconds=1000))
199
200 # Check float args to constructor
201 eq(td(weeks=1.0/7), td(days=1))
202 eq(td(days=1.0/24), td(hours=1))
203 eq(td(hours=1.0/60), td(minutes=1))
204 eq(td(minutes=1.0/60), td(seconds=1))
205 eq(td(seconds=0.001), td(milliseconds=1))
206 eq(td(milliseconds=0.001), td(microseconds=1))
207
208 def test_computations(self):
209 eq = self.assertEqual
210 td = timedelta
211
212 a = td(7) # One week
213 b = td(0, 60) # One minute
214 c = td(0, 0, 1000) # One millisecond
215 eq(a+b+c, td(7, 60, 1000))
216 eq(a-b, td(6, 24*3600 - 60))
217 eq(-a, td(-7))
218 eq(+a, td(7))
219 eq(-b, td(-1, 24*3600 - 60))
220 eq(-c, td(-1, 24*3600 - 1, 999000))
221 eq(abs(a), a)
222 eq(abs(-a), a)
223 eq(td(6, 24*3600), a)
224 eq(td(0, 0, 60*1000000), b)
225 eq(a*10, td(70))
226 eq(a*10, 10*a)
227 eq(a*10L, 10*a)
228 eq(b*10, td(0, 600))
229 eq(10*b, td(0, 600))
230 eq(b*10L, td(0, 600))
231 eq(c*10, td(0, 0, 10000))
232 eq(10*c, td(0, 0, 10000))
233 eq(c*10L, td(0, 0, 10000))
234 eq(a*-1, -a)
235 eq(b*-2, -b-b)
236 eq(c*-2, -c+-c)
237 eq(b*(60*24), (b*60)*24)
238 eq(b*(60*24), (60*b)*24)
239 eq(c*1000, td(0, 1))
240 eq(1000*c, td(0, 1))
241 eq(a//7, td(1))
242 eq(b//10, td(0, 6))
243 eq(c//1000, td(0, 0, 1))
244 eq(a//10, td(0, 7*24*360))
245 eq(a//3600000, td(0, 0, 7*24*1000))
246
247 def test_disallowed_computations(self):
248 a = timedelta(42)
249
250 # Add/sub ints, longs, floats should be illegal
251 for i in 1, 1L, 1.0:
252 self.assertRaises(TypeError, lambda: a+i)
253 self.assertRaises(TypeError, lambda: a-i)
254 self.assertRaises(TypeError, lambda: i+a)
255 self.assertRaises(TypeError, lambda: i-a)
256
257 # Mul/div by float isn't supported.
258 x = 2.3
259 self.assertRaises(TypeError, lambda: a*x)
260 self.assertRaises(TypeError, lambda: x*a)
261 self.assertRaises(TypeError, lambda: a/x)
262 self.assertRaises(TypeError, lambda: x/a)
263 self.assertRaises(TypeError, lambda: a // x)
264 self.assertRaises(TypeError, lambda: x // a)
265
266 # Divison of int by timedelta doesn't make sense.
267 # Division by zero doesn't make sense.
268 for zero in 0, 0L:
269 self.assertRaises(TypeError, lambda: zero // a)
270 self.assertRaises(ZeroDivisionError, lambda: a // zero)
271
272 def test_basic_attributes(self):
273 days, seconds, us = 1, 7, 31
274 td = timedelta(days, seconds, us)
275 self.assertEqual(td.days, days)
276 self.assertEqual(td.seconds, seconds)
277 self.assertEqual(td.microseconds, us)
278
279 def test_carries(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) * 1e6 + 1)
286 t2 = timedelta(microseconds=1)
287 self.assertEqual(t1, t2)
288
289 def test_hash_equality(self):
290 t1 = timedelta(days=100,
291 weeks=-7,
292 hours=-24*(100-49),
293 minutes=-3,
294 seconds=12,
295 microseconds=(3*60 - 12) * 1000000)
296 t2 = timedelta()
297 self.assertEqual(hash(t1), hash(t2))
298
299 t1 += timedelta(weeks=7)
300 t2 += timedelta(days=7*7)
301 self.assertEqual(t1, t2)
302 self.assertEqual(hash(t1), hash(t2))
303
304 d = {t1: 1}
305 d[t2] = 2
306 self.assertEqual(len(d), 1)
307 self.assertEqual(d[t1], 2)
308
309 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000310 args = 12, 34, 56
311 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000312 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000313 green = pickler.dumps(orig, proto)
314 derived = unpickler.loads(green)
315 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000316
317 def test_compare(self):
318 t1 = timedelta(2, 3, 4)
319 t2 = timedelta(2, 3, 4)
320 self.failUnless(t1 == t2)
321 self.failUnless(t1 <= t2)
322 self.failUnless(t1 >= t2)
323 self.failUnless(not t1 != t2)
324 self.failUnless(not t1 < t2)
325 self.failUnless(not t1 > t2)
326 self.assertEqual(cmp(t1, t2), 0)
327 self.assertEqual(cmp(t2, t1), 0)
328
329 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
330 t2 = timedelta(*args) # this is larger than t1
331 self.failUnless(t1 < t2)
332 self.failUnless(t2 > t1)
333 self.failUnless(t1 <= t2)
334 self.failUnless(t2 >= t1)
335 self.failUnless(t1 != t2)
336 self.failUnless(t2 != t1)
337 self.failUnless(not t1 == t2)
338 self.failUnless(not t2 == t1)
339 self.failUnless(not t1 > t2)
340 self.failUnless(not t2 < t1)
341 self.failUnless(not t1 >= t2)
342 self.failUnless(not t2 <= t1)
343 self.assertEqual(cmp(t1, t2), -1)
344 self.assertEqual(cmp(t2, t1), 1)
345
Tim Peters07534a62003-02-07 22:50:28 +0000346 badargs = 10, 10L, 34.5, "abc", {}, [], ()
347 for badarg in badargs:
348 self.assertEqual(t1 == badarg, False)
349 self.assertEqual(t1 != badarg, True)
350 self.assertEqual(badarg == t1, False)
351 self.assertEqual(badarg != t1, True)
352
353 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +0000354 self.assertRaises(TypeError, lambda: t1 <= badarg)
355 self.assertRaises(TypeError, lambda: t1 < badarg)
356 self.assertRaises(TypeError, lambda: t1 > badarg)
357 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000358 self.assertRaises(TypeError, lambda: badarg <= t1)
359 self.assertRaises(TypeError, lambda: badarg < t1)
360 self.assertRaises(TypeError, lambda: badarg > t1)
361 self.assertRaises(TypeError, lambda: badarg >= t1)
362
363 def test_str(self):
364 td = timedelta
365 eq = self.assertEqual
366
367 eq(str(td(1)), "1 day, 0:00:00")
368 eq(str(td(-1)), "-1 day, 0:00:00")
369 eq(str(td(2)), "2 days, 0:00:00")
370 eq(str(td(-2)), "-2 days, 0:00:00")
371
372 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
373 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
374 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
375 "-210 days, 23:12:34")
376
377 eq(str(td(milliseconds=1)), "0:00:00.001000")
378 eq(str(td(microseconds=3)), "0:00:00.000003")
379
380 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
381 microseconds=999999)),
382 "999999999 days, 23:59:59.999999")
383
384 def test_roundtrip(self):
385 for td in (timedelta(days=999999999, hours=23, minutes=59,
386 seconds=59, microseconds=999999),
387 timedelta(days=-999999999),
388 timedelta(days=1, seconds=2, microseconds=3)):
389
390 # Verify td -> string -> td identity.
391 s = repr(td)
392 self.failUnless(s.startswith('datetime.'))
393 s = s[9:]
394 td2 = eval(s)
395 self.assertEqual(td, td2)
396
397 # Verify identity via reconstructing from pieces.
398 td2 = timedelta(td.days, td.seconds, td.microseconds)
399 self.assertEqual(td, td2)
400
401 def test_resolution_info(self):
402 self.assert_(isinstance(timedelta.min, timedelta))
403 self.assert_(isinstance(timedelta.max, timedelta))
404 self.assert_(isinstance(timedelta.resolution, timedelta))
405 self.assert_(timedelta.max > timedelta.min)
406 self.assertEqual(timedelta.min, timedelta(-999999999))
407 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
408 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
409
410 def test_overflow(self):
411 tiny = timedelta.resolution
412
413 td = timedelta.min + tiny
414 td -= tiny # no problem
415 self.assertRaises(OverflowError, td.__sub__, tiny)
416 self.assertRaises(OverflowError, td.__add__, -tiny)
417
418 td = timedelta.max - tiny
419 td += tiny # no problem
420 self.assertRaises(OverflowError, td.__add__, tiny)
421 self.assertRaises(OverflowError, td.__sub__, -tiny)
422
423 self.assertRaises(OverflowError, lambda: -timedelta.max)
424
425 def test_microsecond_rounding(self):
426 td = timedelta
427 eq = self.assertEqual
428
429 # Single-field rounding.
430 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
431 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
432 eq(td(milliseconds=0.6/1000), td(microseconds=1))
433 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
434
435 # Rounding due to contributions from more than one field.
436 us_per_hour = 3600e6
437 us_per_day = us_per_hour * 24
438 eq(td(days=.4/us_per_day), td(0))
439 eq(td(hours=.2/us_per_hour), td(0))
440 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
441
442 eq(td(days=-.4/us_per_day), td(0))
443 eq(td(hours=-.2/us_per_hour), td(0))
444 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
445
446 def test_massive_normalization(self):
447 td = timedelta(microseconds=-1)
448 self.assertEqual((td.days, td.seconds, td.microseconds),
449 (-1, 24*3600-1, 999999))
450
451 def test_bool(self):
452 self.failUnless(timedelta(1))
453 self.failUnless(timedelta(0, 1))
454 self.failUnless(timedelta(0, 0, 1))
455 self.failUnless(timedelta(microseconds=1))
456 self.failUnless(not timedelta(0))
457
458#############################################################################
459# date tests
460
461class TestDateOnly(unittest.TestCase):
462 # Tests here won't pass if also run on datetime objects, so don't
463 # subclass this to test datetimes too.
464
465 def test_delta_non_days_ignored(self):
466 dt = date(2000, 1, 2)
467 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
468 microseconds=5)
469 days = timedelta(delta.days)
470 self.assertEqual(days, timedelta(1))
471
472 dt2 = dt + delta
473 self.assertEqual(dt2, dt + days)
474
475 dt2 = delta + dt
476 self.assertEqual(dt2, dt + days)
477
478 dt2 = dt - delta
479 self.assertEqual(dt2, dt - days)
480
481 delta = -delta
482 days = timedelta(delta.days)
483 self.assertEqual(days, timedelta(-2))
484
485 dt2 = dt + delta
486 self.assertEqual(dt2, dt + days)
487
488 dt2 = delta + dt
489 self.assertEqual(dt2, dt + days)
490
491 dt2 = dt - delta
492 self.assertEqual(dt2, dt - days)
493
Tim Peters07534a62003-02-07 22:50:28 +0000494class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000495 # Tests here should pass for both dates and datetimes, except for a
496 # few tests that TestDateTime overrides.
497
498 theclass = date
499
500 def test_basic_attributes(self):
501 dt = self.theclass(2002, 3, 1)
502 self.assertEqual(dt.year, 2002)
503 self.assertEqual(dt.month, 3)
504 self.assertEqual(dt.day, 1)
505
506 def test_roundtrip(self):
507 for dt in (self.theclass(1, 2, 3),
508 self.theclass.today()):
509 # Verify dt -> string -> date identity.
510 s = repr(dt)
511 self.failUnless(s.startswith('datetime.'))
512 s = s[9:]
513 dt2 = eval(s)
514 self.assertEqual(dt, dt2)
515
516 # Verify identity via reconstructing from pieces.
517 dt2 = self.theclass(dt.year, dt.month, dt.day)
518 self.assertEqual(dt, dt2)
519
520 def test_ordinal_conversions(self):
521 # Check some fixed values.
522 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
523 (1, 12, 31, 365),
524 (2, 1, 1, 366),
525 # first example from "Calendrical Calculations"
526 (1945, 11, 12, 710347)]:
527 d = self.theclass(y, m, d)
528 self.assertEqual(n, d.toordinal())
529 fromord = self.theclass.fromordinal(n)
530 self.assertEqual(d, fromord)
531 if hasattr(fromord, "hour"):
532 # if we're checking something fancier than a date, verify
533 # the extra fields have been zeroed out
534 self.assertEqual(fromord.hour, 0)
535 self.assertEqual(fromord.minute, 0)
536 self.assertEqual(fromord.second, 0)
537 self.assertEqual(fromord.microsecond, 0)
538
Tim Peters0bf60bd2003-01-08 20:40:01 +0000539 # Check first and last days of year spottily across the whole
540 # range of years supported.
541 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000542 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
543 d = self.theclass(year, 1, 1)
544 n = d.toordinal()
545 d2 = self.theclass.fromordinal(n)
546 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000547 # Verify that moving back a day gets to the end of year-1.
548 if year > 1:
549 d = self.theclass.fromordinal(n-1)
550 d2 = self.theclass(year-1, 12, 31)
551 self.assertEqual(d, d2)
552 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000553
554 # Test every day in a leap-year and a non-leap year.
555 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
556 for year, isleap in (2000, True), (2002, False):
557 n = self.theclass(year, 1, 1).toordinal()
558 for month, maxday in zip(range(1, 13), dim):
559 if month == 2 and isleap:
560 maxday += 1
561 for day in range(1, maxday+1):
562 d = self.theclass(year, month, day)
563 self.assertEqual(d.toordinal(), n)
564 self.assertEqual(d, self.theclass.fromordinal(n))
565 n += 1
566
567 def test_extreme_ordinals(self):
568 a = self.theclass.min
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 a = self.theclass.max
581 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
582 aord = a.toordinal()
583 b = a.fromordinal(aord)
584 self.assertEqual(a, b)
585
586 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
587
588 b = a - timedelta(days=1)
589 self.assertEqual(b.toordinal(), aord - 1)
590 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
591
592 def test_bad_constructor_arguments(self):
593 # bad years
594 self.theclass(MINYEAR, 1, 1) # no exception
595 self.theclass(MAXYEAR, 1, 1) # no exception
596 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
597 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
598 # bad months
599 self.theclass(2000, 1, 1) # no exception
600 self.theclass(2000, 12, 1) # no exception
601 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
602 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
603 # bad days
604 self.theclass(2000, 2, 29) # no exception
605 self.theclass(2004, 2, 29) # no exception
606 self.theclass(2400, 2, 29) # no exception
607 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
608 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
609 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
610 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
611 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
612 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
613
614 def test_hash_equality(self):
615 d = self.theclass(2000, 12, 31)
616 # same thing
617 e = self.theclass(2000, 12, 31)
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 d = self.theclass(2001, 1, 1)
628 # same thing
629 e = self.theclass(2001, 1, 1)
630 self.assertEqual(d, e)
631 self.assertEqual(hash(d), hash(e))
632
633 dic = {d: 1}
634 dic[e] = 2
635 self.assertEqual(len(dic), 1)
636 self.assertEqual(dic[d], 2)
637 self.assertEqual(dic[e], 2)
638
639 def test_computations(self):
640 a = self.theclass(2002, 1, 31)
641 b = self.theclass(1956, 1, 31)
642
643 diff = a-b
644 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
645 self.assertEqual(diff.seconds, 0)
646 self.assertEqual(diff.microseconds, 0)
647
648 day = timedelta(1)
649 week = timedelta(7)
650 a = self.theclass(2002, 3, 2)
651 self.assertEqual(a + day, self.theclass(2002, 3, 3))
652 self.assertEqual(day + a, self.theclass(2002, 3, 3))
653 self.assertEqual(a - day, self.theclass(2002, 3, 1))
654 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
655 self.assertEqual(a + week, self.theclass(2002, 3, 9))
656 self.assertEqual(a - week, self.theclass(2002, 2, 23))
657 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
658 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
659 self.assertEqual((a + week) - a, week)
660 self.assertEqual((a + day) - a, day)
661 self.assertEqual((a - week) - a, -week)
662 self.assertEqual((a - day) - a, -day)
663 self.assertEqual(a - (a + week), -week)
664 self.assertEqual(a - (a + day), -day)
665 self.assertEqual(a - (a - week), week)
666 self.assertEqual(a - (a - day), day)
667
668 # Add/sub ints, longs, floats should be illegal
669 for i in 1, 1L, 1.0:
670 self.assertRaises(TypeError, lambda: a+i)
671 self.assertRaises(TypeError, lambda: a-i)
672 self.assertRaises(TypeError, lambda: i+a)
673 self.assertRaises(TypeError, lambda: i-a)
674
675 # delta - date is senseless.
676 self.assertRaises(TypeError, lambda: day - a)
677 # mixing date and (delta or date) via * or // is senseless
678 self.assertRaises(TypeError, lambda: day * a)
679 self.assertRaises(TypeError, lambda: a * day)
680 self.assertRaises(TypeError, lambda: day // a)
681 self.assertRaises(TypeError, lambda: a // day)
682 self.assertRaises(TypeError, lambda: a * a)
683 self.assertRaises(TypeError, lambda: a // a)
684 # date + date is senseless
685 self.assertRaises(TypeError, lambda: a + a)
686
687 def test_overflow(self):
688 tiny = self.theclass.resolution
689
690 dt = self.theclass.min + tiny
691 dt -= tiny # no problem
692 self.assertRaises(OverflowError, dt.__sub__, tiny)
693 self.assertRaises(OverflowError, dt.__add__, -tiny)
694
695 dt = self.theclass.max - tiny
696 dt += tiny # no problem
697 self.assertRaises(OverflowError, dt.__add__, tiny)
698 self.assertRaises(OverflowError, dt.__sub__, -tiny)
699
700 def test_fromtimestamp(self):
701 import time
702
703 # Try an arbitrary fixed value.
704 year, month, day = 1999, 9, 19
705 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
706 d = self.theclass.fromtimestamp(ts)
707 self.assertEqual(d.year, year)
708 self.assertEqual(d.month, month)
709 self.assertEqual(d.day, day)
710
711 def test_today(self):
712 import time
713
714 # We claim that today() is like fromtimestamp(time.time()), so
715 # prove it.
716 for dummy in range(3):
717 today = self.theclass.today()
718 ts = time.time()
719 todayagain = self.theclass.fromtimestamp(ts)
720 if today == todayagain:
721 break
722 # There are several legit reasons that could fail:
723 # 1. It recently became midnight, between the today() and the
724 # time() calls.
725 # 2. The platform time() has such fine resolution that we'll
726 # never get the same value twice.
727 # 3. The platform time() has poor resolution, and we just
728 # happened to call today() right before a resolution quantum
729 # boundary.
730 # 4. The system clock got fiddled between calls.
731 # In any case, wait a little while and try again.
732 time.sleep(0.1)
733
734 # It worked or it didn't. If it didn't, assume it's reason #2, and
735 # let the test pass if they're within half a second of each other.
736 self.failUnless(today == todayagain or
737 abs(todayagain - today) < timedelta(seconds=0.5))
738
739 def test_weekday(self):
740 for i in range(7):
741 # March 4, 2002 is a Monday
742 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
743 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
744 # January 2, 1956 is a Monday
745 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
746 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
747
748 def test_isocalendar(self):
749 # Check examples from
750 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
751 for i in range(7):
752 d = self.theclass(2003, 12, 22+i)
753 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
754 d = self.theclass(2003, 12, 29) + timedelta(i)
755 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
756 d = self.theclass(2004, 1, 5+i)
757 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
758 d = self.theclass(2009, 12, 21+i)
759 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
760 d = self.theclass(2009, 12, 28) + timedelta(i)
761 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
762 d = self.theclass(2010, 1, 4+i)
763 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
764
765 def test_iso_long_years(self):
766 # Calculate long ISO years and compare to table from
767 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
768 ISO_LONG_YEARS_TABLE = """
769 4 32 60 88
770 9 37 65 93
771 15 43 71 99
772 20 48 76
773 26 54 82
774
775 105 133 161 189
776 111 139 167 195
777 116 144 172
778 122 150 178
779 128 156 184
780
781 201 229 257 285
782 207 235 263 291
783 212 240 268 296
784 218 246 274
785 224 252 280
786
787 303 331 359 387
788 308 336 364 392
789 314 342 370 398
790 320 348 376
791 325 353 381
792 """
793 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
794 iso_long_years.sort()
795 L = []
796 for i in range(400):
797 d = self.theclass(2000+i, 12, 31)
798 d1 = self.theclass(1600+i, 12, 31)
799 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
800 if d.isocalendar()[1] == 53:
801 L.append(i)
802 self.assertEqual(L, iso_long_years)
803
804 def test_isoformat(self):
805 t = self.theclass(2, 3, 2)
806 self.assertEqual(t.isoformat(), "0002-03-02")
807
808 def test_ctime(self):
809 t = self.theclass(2002, 3, 2)
810 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
811
812 def test_strftime(self):
813 t = self.theclass(2005, 3, 2)
814 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
815
816 self.assertRaises(TypeError, t.strftime) # needs an arg
817 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
818 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
819
820 # A naive object replaces %z and %Z w/ empty strings.
821 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
822
823 def test_resolution_info(self):
824 self.assert_(isinstance(self.theclass.min, self.theclass))
825 self.assert_(isinstance(self.theclass.max, self.theclass))
826 self.assert_(isinstance(self.theclass.resolution, timedelta))
827 self.assert_(self.theclass.max > self.theclass.min)
828
829 def test_extreme_timedelta(self):
830 big = self.theclass.max - self.theclass.min
831 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
832 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
833 # n == 315537897599999999 ~= 2**58.13
834 justasbig = timedelta(0, 0, n)
835 self.assertEqual(big, justasbig)
836 self.assertEqual(self.theclass.min + big, self.theclass.max)
837 self.assertEqual(self.theclass.max - big, self.theclass.min)
838
839 def test_timetuple(self):
840 for i in range(7):
841 # January 2, 1956 is a Monday (0)
842 d = self.theclass(1956, 1, 2+i)
843 t = d.timetuple()
844 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
845 # February 1, 1956 is a Wednesday (2)
846 d = self.theclass(1956, 2, 1+i)
847 t = d.timetuple()
848 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
849 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
850 # of the year.
851 d = self.theclass(1956, 3, 1+i)
852 t = d.timetuple()
853 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
854 self.assertEqual(t.tm_year, 1956)
855 self.assertEqual(t.tm_mon, 3)
856 self.assertEqual(t.tm_mday, 1+i)
857 self.assertEqual(t.tm_hour, 0)
858 self.assertEqual(t.tm_min, 0)
859 self.assertEqual(t.tm_sec, 0)
860 self.assertEqual(t.tm_wday, (3+i)%7)
861 self.assertEqual(t.tm_yday, 61+i)
862 self.assertEqual(t.tm_isdst, -1)
863
864 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000865 args = 6, 7, 23
866 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000867 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000868 green = pickler.dumps(orig, proto)
869 derived = unpickler.loads(green)
870 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000871
872 def test_compare(self):
873 t1 = self.theclass(2, 3, 4)
874 t2 = self.theclass(2, 3, 4)
875 self.failUnless(t1 == t2)
876 self.failUnless(t1 <= t2)
877 self.failUnless(t1 >= t2)
878 self.failUnless(not t1 != t2)
879 self.failUnless(not t1 < t2)
880 self.failUnless(not t1 > t2)
881 self.assertEqual(cmp(t1, t2), 0)
882 self.assertEqual(cmp(t2, t1), 0)
883
884 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
885 t2 = self.theclass(*args) # this is larger than t1
886 self.failUnless(t1 < t2)
887 self.failUnless(t2 > t1)
888 self.failUnless(t1 <= t2)
889 self.failUnless(t2 >= t1)
890 self.failUnless(t1 != t2)
891 self.failUnless(t2 != t1)
892 self.failUnless(not t1 == t2)
893 self.failUnless(not t2 == t1)
894 self.failUnless(not t1 > t2)
895 self.failUnless(not t2 < t1)
896 self.failUnless(not t1 >= t2)
897 self.failUnless(not t2 <= t1)
898 self.assertEqual(cmp(t1, t2), -1)
899 self.assertEqual(cmp(t2, t1), 1)
900
Tim Peters07534a62003-02-07 22:50:28 +0000901 badargs = 10, 10L, 34.5, "abc", {}, [], ()
902 for badarg in badargs:
903 self.assertEqual(t1 == badarg, False)
904 self.assertEqual(t1 != badarg, True)
905 self.assertEqual(badarg == t1, False)
906 self.assertEqual(badarg != t1, True)
907
908 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +0000909 self.assertRaises(TypeError, lambda: t1 < badarg)
910 self.assertRaises(TypeError, lambda: t1 > badarg)
911 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000912 self.assertRaises(TypeError, lambda: badarg <= t1)
913 self.assertRaises(TypeError, lambda: badarg < t1)
914 self.assertRaises(TypeError, lambda: badarg > t1)
915 self.assertRaises(TypeError, lambda: badarg >= t1)
916
Tim Peters8d81a012003-01-24 22:36:34 +0000917 def test_mixed_compare(self):
918 our = self.theclass(2000, 4, 5)
919 self.assertRaises(TypeError, cmp, our, 1)
920 self.assertRaises(TypeError, cmp, 1, our)
921
922 class AnotherDateTimeClass(object):
923 def __cmp__(self, other):
924 # Return "equal" so calling this can't be confused with
925 # compare-by-address (which never says "equal" for distinct
926 # objects).
927 return 0
928
929 # This still errors, because date and datetime comparison raise
930 # TypeError instead of NotImplemented when they don't know what to
931 # do, in order to stop comparison from falling back to the default
932 # compare-by-address.
933 their = AnotherDateTimeClass()
934 self.assertRaises(TypeError, cmp, our, their)
935 # Oops: The next stab raises TypeError in the C implementation,
936 # but not in the Python implementation of datetime. The difference
937 # is due to that the Python implementation defines __cmp__ but
938 # the C implementation defines tp_richcompare. This is more pain
939 # to fix than it's worth, so commenting out the test.
940 # self.assertEqual(cmp(their, our), 0)
941
942 # But date and datetime comparison return NotImplemented instead if the
943 # other object has a timetuple attr. This gives the other object a
944 # chance to do the comparison.
945 class Comparable(AnotherDateTimeClass):
946 def timetuple(self):
947 return ()
948
949 their = Comparable()
950 self.assertEqual(cmp(our, their), 0)
951 self.assertEqual(cmp(their, our), 0)
952 self.failUnless(our == their)
953 self.failUnless(their == our)
954
Tim Peters2a799bf2002-12-16 20:18:38 +0000955 def test_bool(self):
956 # All dates are considered true.
957 self.failUnless(self.theclass.min)
958 self.failUnless(self.theclass.max)
959
Tim Petersd6844152002-12-22 20:58:42 +0000960 def test_srftime_out_of_range(self):
961 # For nasty technical reasons, we can't handle years before 1900.
962 cls = self.theclass
963 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
964 for y in 1, 49, 51, 99, 100, 1000, 1899:
965 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000966
967 def test_replace(self):
968 cls = self.theclass
969 args = [1, 2, 3]
970 base = cls(*args)
971 self.assertEqual(base, base.replace())
972
973 i = 0
974 for name, newval in (("year", 2),
975 ("month", 3),
976 ("day", 4)):
977 newargs = args[:]
978 newargs[i] = newval
979 expected = cls(*newargs)
980 got = base.replace(**{name: newval})
981 self.assertEqual(expected, got)
982 i += 1
983
984 # Out of bounds.
985 base = cls(2000, 2, 29)
986 self.assertRaises(ValueError, base.replace, year=2001)
987
Tim Peters2a799bf2002-12-16 20:18:38 +0000988#############################################################################
989# datetime tests
990
991class TestDateTime(TestDate):
992
993 theclass = datetime
994
995 def test_basic_attributes(self):
996 dt = self.theclass(2002, 3, 1, 12, 0)
997 self.assertEqual(dt.year, 2002)
998 self.assertEqual(dt.month, 3)
999 self.assertEqual(dt.day, 1)
1000 self.assertEqual(dt.hour, 12)
1001 self.assertEqual(dt.minute, 0)
1002 self.assertEqual(dt.second, 0)
1003 self.assertEqual(dt.microsecond, 0)
1004
1005 def test_basic_attributes_nonzero(self):
1006 # Make sure all attributes are non-zero so bugs in
1007 # bit-shifting access show up.
1008 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1009 self.assertEqual(dt.year, 2002)
1010 self.assertEqual(dt.month, 3)
1011 self.assertEqual(dt.day, 1)
1012 self.assertEqual(dt.hour, 12)
1013 self.assertEqual(dt.minute, 59)
1014 self.assertEqual(dt.second, 59)
1015 self.assertEqual(dt.microsecond, 8000)
1016
1017 def test_roundtrip(self):
1018 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1019 self.theclass.now()):
1020 # Verify dt -> string -> datetime identity.
1021 s = repr(dt)
1022 self.failUnless(s.startswith('datetime.'))
1023 s = s[9:]
1024 dt2 = eval(s)
1025 self.assertEqual(dt, dt2)
1026
1027 # Verify identity via reconstructing from pieces.
1028 dt2 = self.theclass(dt.year, dt.month, dt.day,
1029 dt.hour, dt.minute, dt.second,
1030 dt.microsecond)
1031 self.assertEqual(dt, dt2)
1032
1033 def test_isoformat(self):
1034 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1035 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1036 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1037 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1038 # str is ISO format with the separator forced to a blank.
1039 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1040
1041 t = self.theclass(2, 3, 2)
1042 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1043 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1044 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1045 # str is ISO format with the separator forced to a blank.
1046 self.assertEqual(str(t), "0002-03-02 00:00:00")
1047
1048 def test_more_ctime(self):
1049 # Test fields that TestDate doesn't touch.
1050 import time
1051
1052 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1053 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1054 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1055 # out. The difference is that t.ctime() produces " 2" for the day,
1056 # but platform ctime() produces "02" for the day. According to
1057 # C99, t.ctime() is correct here.
1058 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1059
1060 # So test a case where that difference doesn't matter.
1061 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1062 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1063
1064 def test_tz_independent_comparing(self):
1065 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1066 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1067 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1068 self.assertEqual(dt1, dt3)
1069 self.assert_(dt2 > dt3)
1070
1071 # Make sure comparison doesn't forget microseconds, and isn't done
1072 # via comparing a float timestamp (an IEEE double doesn't have enough
1073 # precision to span microsecond resolution across years 1 thru 9999,
1074 # so comparing via timestamp necessarily calls some distinct values
1075 # equal).
1076 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1077 us = timedelta(microseconds=1)
1078 dt2 = dt1 + us
1079 self.assertEqual(dt2 - dt1, us)
1080 self.assert_(dt1 < dt2)
1081
1082 def test_bad_constructor_arguments(self):
1083 # bad years
1084 self.theclass(MINYEAR, 1, 1) # no exception
1085 self.theclass(MAXYEAR, 1, 1) # no exception
1086 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1087 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1088 # bad months
1089 self.theclass(2000, 1, 1) # no exception
1090 self.theclass(2000, 12, 1) # no exception
1091 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1092 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1093 # bad days
1094 self.theclass(2000, 2, 29) # no exception
1095 self.theclass(2004, 2, 29) # no exception
1096 self.theclass(2400, 2, 29) # no exception
1097 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1098 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1099 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1100 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1101 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1102 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1103 # bad hours
1104 self.theclass(2000, 1, 31, 0) # no exception
1105 self.theclass(2000, 1, 31, 23) # no exception
1106 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1107 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1108 # bad minutes
1109 self.theclass(2000, 1, 31, 23, 0) # no exception
1110 self.theclass(2000, 1, 31, 23, 59) # no exception
1111 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1112 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1113 # bad seconds
1114 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1115 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1116 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1117 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1118 # bad microseconds
1119 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1120 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1121 self.assertRaises(ValueError, self.theclass,
1122 2000, 1, 31, 23, 59, 59, -1)
1123 self.assertRaises(ValueError, self.theclass,
1124 2000, 1, 31, 23, 59, 59,
1125 1000000)
1126
1127 def test_hash_equality(self):
1128 d = self.theclass(2000, 12, 31, 23, 30, 17)
1129 e = self.theclass(2000, 12, 31, 23, 30, 17)
1130 self.assertEqual(d, e)
1131 self.assertEqual(hash(d), hash(e))
1132
1133 dic = {d: 1}
1134 dic[e] = 2
1135 self.assertEqual(len(dic), 1)
1136 self.assertEqual(dic[d], 2)
1137 self.assertEqual(dic[e], 2)
1138
1139 d = self.theclass(2001, 1, 1, 0, 5, 17)
1140 e = self.theclass(2001, 1, 1, 0, 5, 17)
1141 self.assertEqual(d, e)
1142 self.assertEqual(hash(d), hash(e))
1143
1144 dic = {d: 1}
1145 dic[e] = 2
1146 self.assertEqual(len(dic), 1)
1147 self.assertEqual(dic[d], 2)
1148 self.assertEqual(dic[e], 2)
1149
1150 def test_computations(self):
1151 a = self.theclass(2002, 1, 31)
1152 b = self.theclass(1956, 1, 31)
1153 diff = a-b
1154 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1155 self.assertEqual(diff.seconds, 0)
1156 self.assertEqual(diff.microseconds, 0)
1157 a = self.theclass(2002, 3, 2, 17, 6)
1158 millisec = timedelta(0, 0, 1000)
1159 hour = timedelta(0, 3600)
1160 day = timedelta(1)
1161 week = timedelta(7)
1162 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1163 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1164 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1165 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1166 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1167 self.assertEqual(a - hour, a + -hour)
1168 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1169 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1170 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1171 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1172 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1173 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1174 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1175 self.assertEqual((a + week) - a, week)
1176 self.assertEqual((a + day) - a, day)
1177 self.assertEqual((a + hour) - a, hour)
1178 self.assertEqual((a + millisec) - a, millisec)
1179 self.assertEqual((a - week) - a, -week)
1180 self.assertEqual((a - day) - a, -day)
1181 self.assertEqual((a - hour) - a, -hour)
1182 self.assertEqual((a - millisec) - a, -millisec)
1183 self.assertEqual(a - (a + week), -week)
1184 self.assertEqual(a - (a + day), -day)
1185 self.assertEqual(a - (a + hour), -hour)
1186 self.assertEqual(a - (a + millisec), -millisec)
1187 self.assertEqual(a - (a - week), week)
1188 self.assertEqual(a - (a - day), day)
1189 self.assertEqual(a - (a - hour), hour)
1190 self.assertEqual(a - (a - millisec), millisec)
1191 self.assertEqual(a + (week + day + hour + millisec),
1192 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1193 self.assertEqual(a + (week + day + hour + millisec),
1194 (((a + week) + day) + hour) + millisec)
1195 self.assertEqual(a - (week + day + hour + millisec),
1196 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1197 self.assertEqual(a - (week + day + hour + millisec),
1198 (((a - week) - day) - hour) - millisec)
1199 # Add/sub ints, longs, floats should be illegal
1200 for i in 1, 1L, 1.0:
1201 self.assertRaises(TypeError, lambda: a+i)
1202 self.assertRaises(TypeError, lambda: a-i)
1203 self.assertRaises(TypeError, lambda: i+a)
1204 self.assertRaises(TypeError, lambda: i-a)
1205
1206 # delta - datetime is senseless.
1207 self.assertRaises(TypeError, lambda: day - a)
1208 # mixing datetime and (delta or datetime) via * or // is senseless
1209 self.assertRaises(TypeError, lambda: day * a)
1210 self.assertRaises(TypeError, lambda: a * day)
1211 self.assertRaises(TypeError, lambda: day // a)
1212 self.assertRaises(TypeError, lambda: a // day)
1213 self.assertRaises(TypeError, lambda: a * a)
1214 self.assertRaises(TypeError, lambda: a // a)
1215 # datetime + datetime is senseless
1216 self.assertRaises(TypeError, lambda: a + a)
1217
1218 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001219 args = 6, 7, 23, 20, 59, 1, 64**2
1220 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001221 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001222 green = pickler.dumps(orig, proto)
1223 derived = unpickler.loads(green)
1224 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001225
Guido van Rossum275666f2003-02-07 21:49:01 +00001226 def test_more_pickling(self):
1227 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1228 s = pickle.dumps(a)
1229 b = pickle.loads(s)
1230 self.assertEqual(b.year, 2003)
1231 self.assertEqual(b.month, 2)
1232 self.assertEqual(b.day, 7)
1233
Tim Peters2a799bf2002-12-16 20:18:38 +00001234 def test_more_compare(self):
1235 # The test_compare() inherited from TestDate covers the error cases.
1236 # We just want to test lexicographic ordering on the members datetime
1237 # has that date lacks.
1238 args = [2000, 11, 29, 20, 58, 16, 999998]
1239 t1 = self.theclass(*args)
1240 t2 = self.theclass(*args)
1241 self.failUnless(t1 == t2)
1242 self.failUnless(t1 <= t2)
1243 self.failUnless(t1 >= t2)
1244 self.failUnless(not t1 != t2)
1245 self.failUnless(not t1 < t2)
1246 self.failUnless(not t1 > t2)
1247 self.assertEqual(cmp(t1, t2), 0)
1248 self.assertEqual(cmp(t2, t1), 0)
1249
1250 for i in range(len(args)):
1251 newargs = args[:]
1252 newargs[i] = args[i] + 1
1253 t2 = self.theclass(*newargs) # this is larger than t1
1254 self.failUnless(t1 < t2)
1255 self.failUnless(t2 > t1)
1256 self.failUnless(t1 <= t2)
1257 self.failUnless(t2 >= t1)
1258 self.failUnless(t1 != t2)
1259 self.failUnless(t2 != t1)
1260 self.failUnless(not t1 == t2)
1261 self.failUnless(not t2 == t1)
1262 self.failUnless(not t1 > t2)
1263 self.failUnless(not t2 < t1)
1264 self.failUnless(not t1 >= t2)
1265 self.failUnless(not t2 <= t1)
1266 self.assertEqual(cmp(t1, t2), -1)
1267 self.assertEqual(cmp(t2, t1), 1)
1268
1269
1270 # A helper for timestamp constructor tests.
1271 def verify_field_equality(self, expected, got):
1272 self.assertEqual(expected.tm_year, got.year)
1273 self.assertEqual(expected.tm_mon, got.month)
1274 self.assertEqual(expected.tm_mday, got.day)
1275 self.assertEqual(expected.tm_hour, got.hour)
1276 self.assertEqual(expected.tm_min, got.minute)
1277 self.assertEqual(expected.tm_sec, got.second)
1278
1279 def test_fromtimestamp(self):
1280 import time
1281
1282 ts = time.time()
1283 expected = time.localtime(ts)
1284 got = self.theclass.fromtimestamp(ts)
1285 self.verify_field_equality(expected, got)
1286
1287 def test_utcfromtimestamp(self):
1288 import time
1289
1290 ts = time.time()
1291 expected = time.gmtime(ts)
1292 got = self.theclass.utcfromtimestamp(ts)
1293 self.verify_field_equality(expected, got)
1294
1295 def test_utcnow(self):
1296 import time
1297
1298 # Call it a success if utcnow() and utcfromtimestamp() are within
1299 # a second of each other.
1300 tolerance = timedelta(seconds=1)
1301 for dummy in range(3):
1302 from_now = self.theclass.utcnow()
1303 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1304 if abs(from_timestamp - from_now) <= tolerance:
1305 break
1306 # Else try again a few times.
1307 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1308
1309 def test_more_timetuple(self):
1310 # This tests fields beyond those tested by the TestDate.test_timetuple.
1311 t = self.theclass(2004, 12, 31, 6, 22, 33)
1312 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1313 self.assertEqual(t.timetuple(),
1314 (t.year, t.month, t.day,
1315 t.hour, t.minute, t.second,
1316 t.weekday(),
1317 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1318 -1))
1319 tt = t.timetuple()
1320 self.assertEqual(tt.tm_year, t.year)
1321 self.assertEqual(tt.tm_mon, t.month)
1322 self.assertEqual(tt.tm_mday, t.day)
1323 self.assertEqual(tt.tm_hour, t.hour)
1324 self.assertEqual(tt.tm_min, t.minute)
1325 self.assertEqual(tt.tm_sec, t.second)
1326 self.assertEqual(tt.tm_wday, t.weekday())
1327 self.assertEqual(tt.tm_yday, t.toordinal() -
1328 date(t.year, 1, 1).toordinal() + 1)
1329 self.assertEqual(tt.tm_isdst, -1)
1330
1331 def test_more_strftime(self):
1332 # This tests fields beyond those tested by the TestDate.test_strftime.
1333 t = self.theclass(2004, 12, 31, 6, 22, 33)
1334 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1335 "12 31 04 33 22 06 366")
1336
1337 def test_extract(self):
1338 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1339 self.assertEqual(dt.date(), date(2002, 3, 4))
1340 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1341
1342 def test_combine(self):
1343 d = date(2002, 3, 4)
1344 t = time(18, 45, 3, 1234)
1345 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1346 combine = self.theclass.combine
1347 dt = combine(d, t)
1348 self.assertEqual(dt, expected)
1349
1350 dt = combine(time=t, date=d)
1351 self.assertEqual(dt, expected)
1352
1353 self.assertEqual(d, dt.date())
1354 self.assertEqual(t, dt.time())
1355 self.assertEqual(dt, combine(dt.date(), dt.time()))
1356
1357 self.assertRaises(TypeError, combine) # need an arg
1358 self.assertRaises(TypeError, combine, d) # need two args
1359 self.assertRaises(TypeError, combine, t, d) # args reversed
1360 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1361 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1362
Tim Peters12bf3392002-12-24 05:41:27 +00001363 def test_replace(self):
1364 cls = self.theclass
1365 args = [1, 2, 3, 4, 5, 6, 7]
1366 base = cls(*args)
1367 self.assertEqual(base, base.replace())
1368
1369 i = 0
1370 for name, newval in (("year", 2),
1371 ("month", 3),
1372 ("day", 4),
1373 ("hour", 5),
1374 ("minute", 6),
1375 ("second", 7),
1376 ("microsecond", 8)):
1377 newargs = args[:]
1378 newargs[i] = newval
1379 expected = cls(*newargs)
1380 got = base.replace(**{name: newval})
1381 self.assertEqual(expected, got)
1382 i += 1
1383
1384 # Out of bounds.
1385 base = cls(2000, 2, 29)
1386 self.assertRaises(ValueError, base.replace, year=2001)
1387
Tim Peters80475bb2002-12-25 07:40:55 +00001388 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001389 # Pretty boring! The TZ test is more interesting here. astimezone()
1390 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001391 dt = self.theclass.now()
1392 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001393 self.assertRaises(TypeError, dt.astimezone) # not enough args
1394 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1395 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001396 self.assertRaises(ValueError, dt.astimezone, f) # naive
1397 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001398
Tim Peters52dcce22003-01-23 16:36:11 +00001399 class Bogus(tzinfo):
1400 def utcoffset(self, dt): return None
1401 def dst(self, dt): return timedelta(0)
1402 bog = Bogus()
1403 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1404
1405 class AlsoBogus(tzinfo):
1406 def utcoffset(self, dt): return timedelta(0)
1407 def dst(self, dt): return None
1408 alsobog = AlsoBogus()
1409 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001410
Tim Peters07534a62003-02-07 22:50:28 +00001411class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001412
1413 theclass = time
1414
1415 def test_basic_attributes(self):
1416 t = self.theclass(12, 0)
1417 self.assertEqual(t.hour, 12)
1418 self.assertEqual(t.minute, 0)
1419 self.assertEqual(t.second, 0)
1420 self.assertEqual(t.microsecond, 0)
1421
1422 def test_basic_attributes_nonzero(self):
1423 # Make sure all attributes are non-zero so bugs in
1424 # bit-shifting access show up.
1425 t = self.theclass(12, 59, 59, 8000)
1426 self.assertEqual(t.hour, 12)
1427 self.assertEqual(t.minute, 59)
1428 self.assertEqual(t.second, 59)
1429 self.assertEqual(t.microsecond, 8000)
1430
1431 def test_roundtrip(self):
1432 t = self.theclass(1, 2, 3, 4)
1433
1434 # Verify t -> string -> time identity.
1435 s = repr(t)
1436 self.failUnless(s.startswith('datetime.'))
1437 s = s[9:]
1438 t2 = eval(s)
1439 self.assertEqual(t, t2)
1440
1441 # Verify identity via reconstructing from pieces.
1442 t2 = self.theclass(t.hour, t.minute, t.second,
1443 t.microsecond)
1444 self.assertEqual(t, t2)
1445
1446 def test_comparing(self):
1447 args = [1, 2, 3, 4]
1448 t1 = self.theclass(*args)
1449 t2 = self.theclass(*args)
1450 self.failUnless(t1 == t2)
1451 self.failUnless(t1 <= t2)
1452 self.failUnless(t1 >= t2)
1453 self.failUnless(not t1 != t2)
1454 self.failUnless(not t1 < t2)
1455 self.failUnless(not t1 > t2)
1456 self.assertEqual(cmp(t1, t2), 0)
1457 self.assertEqual(cmp(t2, t1), 0)
1458
1459 for i in range(len(args)):
1460 newargs = args[:]
1461 newargs[i] = args[i] + 1
1462 t2 = self.theclass(*newargs) # this is larger than t1
1463 self.failUnless(t1 < t2)
1464 self.failUnless(t2 > t1)
1465 self.failUnless(t1 <= t2)
1466 self.failUnless(t2 >= t1)
1467 self.failUnless(t1 != t2)
1468 self.failUnless(t2 != t1)
1469 self.failUnless(not t1 == t2)
1470 self.failUnless(not t2 == t1)
1471 self.failUnless(not t1 > t2)
1472 self.failUnless(not t2 < t1)
1473 self.failUnless(not t1 >= t2)
1474 self.failUnless(not t2 <= t1)
1475 self.assertEqual(cmp(t1, t2), -1)
1476 self.assertEqual(cmp(t2, t1), 1)
1477
Tim Peters0bf60bd2003-01-08 20:40:01 +00001478 badargs = (10, 10L, 34.5, "abc", {}, [], ())
1479 if CMP_BUG_FIXED:
1480 badargs += (date(1, 1, 1), datetime(1, 1, 1, 1, 1), timedelta(9))
Tim Peters07534a62003-02-07 22:50:28 +00001481
Tim Peters0bf60bd2003-01-08 20:40:01 +00001482 for badarg in badargs:
Tim Peters07534a62003-02-07 22:50:28 +00001483 self.assertEqual(t1 == badarg, False)
1484 self.assertEqual(t1 != badarg, True)
1485 self.assertEqual(badarg == t1, False)
1486 self.assertEqual(badarg != t1, True)
1487
1488 for badarg in badargs:
Tim Peters2a799bf2002-12-16 20:18:38 +00001489 self.assertRaises(TypeError, lambda: t1 <= badarg)
1490 self.assertRaises(TypeError, lambda: t1 < badarg)
1491 self.assertRaises(TypeError, lambda: t1 > badarg)
1492 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001493 self.assertRaises(TypeError, lambda: badarg <= t1)
1494 self.assertRaises(TypeError, lambda: badarg < t1)
1495 self.assertRaises(TypeError, lambda: badarg > t1)
1496 self.assertRaises(TypeError, lambda: badarg >= t1)
1497
1498 def test_bad_constructor_arguments(self):
1499 # bad hours
1500 self.theclass(0, 0) # no exception
1501 self.theclass(23, 0) # no exception
1502 self.assertRaises(ValueError, self.theclass, -1, 0)
1503 self.assertRaises(ValueError, self.theclass, 24, 0)
1504 # bad minutes
1505 self.theclass(23, 0) # no exception
1506 self.theclass(23, 59) # no exception
1507 self.assertRaises(ValueError, self.theclass, 23, -1)
1508 self.assertRaises(ValueError, self.theclass, 23, 60)
1509 # bad seconds
1510 self.theclass(23, 59, 0) # no exception
1511 self.theclass(23, 59, 59) # no exception
1512 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1513 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1514 # bad microseconds
1515 self.theclass(23, 59, 59, 0) # no exception
1516 self.theclass(23, 59, 59, 999999) # no exception
1517 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1518 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1519
1520 def test_hash_equality(self):
1521 d = self.theclass(23, 30, 17)
1522 e = self.theclass(23, 30, 17)
1523 self.assertEqual(d, e)
1524 self.assertEqual(hash(d), hash(e))
1525
1526 dic = {d: 1}
1527 dic[e] = 2
1528 self.assertEqual(len(dic), 1)
1529 self.assertEqual(dic[d], 2)
1530 self.assertEqual(dic[e], 2)
1531
1532 d = self.theclass(0, 5, 17)
1533 e = self.theclass(0, 5, 17)
1534 self.assertEqual(d, e)
1535 self.assertEqual(hash(d), hash(e))
1536
1537 dic = {d: 1}
1538 dic[e] = 2
1539 self.assertEqual(len(dic), 1)
1540 self.assertEqual(dic[d], 2)
1541 self.assertEqual(dic[e], 2)
1542
1543 def test_isoformat(self):
1544 t = self.theclass(4, 5, 1, 123)
1545 self.assertEqual(t.isoformat(), "04:05:01.000123")
1546 self.assertEqual(t.isoformat(), str(t))
1547
1548 t = self.theclass()
1549 self.assertEqual(t.isoformat(), "00:00:00")
1550 self.assertEqual(t.isoformat(), str(t))
1551
1552 t = self.theclass(microsecond=1)
1553 self.assertEqual(t.isoformat(), "00:00:00.000001")
1554 self.assertEqual(t.isoformat(), str(t))
1555
1556 t = self.theclass(microsecond=10)
1557 self.assertEqual(t.isoformat(), "00:00:00.000010")
1558 self.assertEqual(t.isoformat(), str(t))
1559
1560 t = self.theclass(microsecond=100)
1561 self.assertEqual(t.isoformat(), "00:00:00.000100")
1562 self.assertEqual(t.isoformat(), str(t))
1563
1564 t = self.theclass(microsecond=1000)
1565 self.assertEqual(t.isoformat(), "00:00:00.001000")
1566 self.assertEqual(t.isoformat(), str(t))
1567
1568 t = self.theclass(microsecond=10000)
1569 self.assertEqual(t.isoformat(), "00:00:00.010000")
1570 self.assertEqual(t.isoformat(), str(t))
1571
1572 t = self.theclass(microsecond=100000)
1573 self.assertEqual(t.isoformat(), "00:00:00.100000")
1574 self.assertEqual(t.isoformat(), str(t))
1575
1576 def test_strftime(self):
1577 t = self.theclass(1, 2, 3, 4)
1578 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1579 # A naive object replaces %z and %Z with empty strings.
1580 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1581
1582 def test_str(self):
1583 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1584 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1585 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1586 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1587 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1588
1589 def test_repr(self):
1590 name = 'datetime.' + self.theclass.__name__
1591 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1592 "%s(1, 2, 3, 4)" % name)
1593 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1594 "%s(10, 2, 3, 4000)" % name)
1595 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1596 "%s(0, 2, 3, 400000)" % name)
1597 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1598 "%s(12, 2, 3)" % name)
1599 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1600 "%s(23, 15)" % name)
1601
1602 def test_resolution_info(self):
1603 self.assert_(isinstance(self.theclass.min, self.theclass))
1604 self.assert_(isinstance(self.theclass.max, self.theclass))
1605 self.assert_(isinstance(self.theclass.resolution, timedelta))
1606 self.assert_(self.theclass.max > self.theclass.min)
1607
1608 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001609 args = 20, 59, 16, 64**2
1610 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001611 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001612 green = pickler.dumps(orig, proto)
1613 derived = unpickler.loads(green)
1614 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001615
1616 def test_bool(self):
1617 cls = self.theclass
1618 self.failUnless(cls(1))
1619 self.failUnless(cls(0, 1))
1620 self.failUnless(cls(0, 0, 1))
1621 self.failUnless(cls(0, 0, 0, 1))
1622 self.failUnless(not cls(0))
1623 self.failUnless(not cls())
1624
Tim Peters12bf3392002-12-24 05:41:27 +00001625 def test_replace(self):
1626 cls = self.theclass
1627 args = [1, 2, 3, 4]
1628 base = cls(*args)
1629 self.assertEqual(base, base.replace())
1630
1631 i = 0
1632 for name, newval in (("hour", 5),
1633 ("minute", 6),
1634 ("second", 7),
1635 ("microsecond", 8)):
1636 newargs = args[:]
1637 newargs[i] = newval
1638 expected = cls(*newargs)
1639 got = base.replace(**{name: newval})
1640 self.assertEqual(expected, got)
1641 i += 1
1642
1643 # Out of bounds.
1644 base = cls(1)
1645 self.assertRaises(ValueError, base.replace, hour=24)
1646 self.assertRaises(ValueError, base.replace, minute=-1)
1647 self.assertRaises(ValueError, base.replace, second=100)
1648 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1649
Tim Peters855fe882002-12-22 03:43:39 +00001650# A mixin for classes with a tzinfo= argument. Subclasses must define
1651# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001652# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001653class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001654
Tim Petersbad8ff02002-12-30 20:52:32 +00001655 def test_argument_passing(self):
1656 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001657 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001658 class introspective(tzinfo):
1659 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001660 def utcoffset(self, dt):
1661 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001662 dst = utcoffset
1663
1664 obj = cls(1, 2, 3, tzinfo=introspective())
1665
Tim Peters0bf60bd2003-01-08 20:40:01 +00001666 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001667 self.assertEqual(obj.tzname(), expected)
1668
Tim Peters0bf60bd2003-01-08 20:40:01 +00001669 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001670 self.assertEqual(obj.utcoffset(), expected)
1671 self.assertEqual(obj.dst(), expected)
1672
Tim Peters855fe882002-12-22 03:43:39 +00001673 def test_bad_tzinfo_classes(self):
1674 cls = self.theclass
1675 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001676
Tim Peters855fe882002-12-22 03:43:39 +00001677 class NiceTry(object):
1678 def __init__(self): pass
1679 def utcoffset(self, dt): pass
1680 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1681
1682 class BetterTry(tzinfo):
1683 def __init__(self): pass
1684 def utcoffset(self, dt): pass
1685 b = BetterTry()
1686 t = cls(1, 1, 1, tzinfo=b)
1687 self.failUnless(t.tzinfo is b)
1688
1689 def test_utc_offset_out_of_bounds(self):
1690 class Edgy(tzinfo):
1691 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001692 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001693 def utcoffset(self, dt):
1694 return self.offset
1695
1696 cls = self.theclass
1697 for offset, legit in ((-1440, False),
1698 (-1439, True),
1699 (1439, True),
1700 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001701 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001702 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001703 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001704 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001705 else:
1706 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001707 if legit:
1708 aofs = abs(offset)
1709 h, m = divmod(aofs, 60)
1710 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001711 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001712 t = t.timetz()
1713 self.assertEqual(str(t), "01:02:03" + tag)
1714 else:
1715 self.assertRaises(ValueError, str, t)
1716
1717 def test_tzinfo_classes(self):
1718 cls = self.theclass
1719 class C1(tzinfo):
1720 def utcoffset(self, dt): return None
1721 def dst(self, dt): return None
1722 def tzname(self, dt): return None
1723 for t in (cls(1, 1, 1),
1724 cls(1, 1, 1, tzinfo=None),
1725 cls(1, 1, 1, tzinfo=C1())):
1726 self.failUnless(t.utcoffset() is None)
1727 self.failUnless(t.dst() is None)
1728 self.failUnless(t.tzname() is None)
1729
Tim Peters855fe882002-12-22 03:43:39 +00001730 class C3(tzinfo):
1731 def utcoffset(self, dt): return timedelta(minutes=-1439)
1732 def dst(self, dt): return timedelta(minutes=1439)
1733 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001734 t = cls(1, 1, 1, tzinfo=C3())
1735 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1736 self.assertEqual(t.dst(), timedelta(minutes=1439))
1737 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001738
1739 # Wrong types.
1740 class C4(tzinfo):
1741 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001742 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001743 def tzname(self, dt): return 0
1744 t = cls(1, 1, 1, tzinfo=C4())
1745 self.assertRaises(TypeError, t.utcoffset)
1746 self.assertRaises(TypeError, t.dst)
1747 self.assertRaises(TypeError, t.tzname)
1748
1749 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001750 class C6(tzinfo):
1751 def utcoffset(self, dt): return timedelta(hours=-24)
1752 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001753 t = cls(1, 1, 1, tzinfo=C6())
1754 self.assertRaises(ValueError, t.utcoffset)
1755 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001756
1757 # Not a whole number of minutes.
1758 class C7(tzinfo):
1759 def utcoffset(self, dt): return timedelta(seconds=61)
1760 def dst(self, dt): return timedelta(microseconds=-81)
1761 t = cls(1, 1, 1, tzinfo=C7())
1762 self.assertRaises(ValueError, t.utcoffset)
1763 self.assertRaises(ValueError, t.dst)
1764
Tim Peters4c0db782002-12-26 05:01:19 +00001765 def test_aware_compare(self):
1766 cls = self.theclass
1767
Tim Peters60c76e42002-12-27 00:41:11 +00001768 # Ensure that utcoffset() gets ignored if the comparands have
1769 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001770 class OperandDependentOffset(tzinfo):
1771 def utcoffset(self, t):
1772 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001773 # d0 and d1 equal after adjustment
1774 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001775 else:
Tim Peters397301e2003-01-02 21:28:08 +00001776 # d2 off in the weeds
1777 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001778
1779 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1780 d0 = base.replace(minute=3)
1781 d1 = base.replace(minute=9)
1782 d2 = base.replace(minute=11)
1783 for x in d0, d1, d2:
1784 for y in d0, d1, d2:
1785 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001786 expected = cmp(x.minute, y.minute)
1787 self.assertEqual(got, expected)
1788
1789 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001790 # Note that a time can't actually have an operand-depedent offset,
1791 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1792 # so skip this test for time.
1793 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001794 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1795 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1796 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1797 for x in d0, d1, d2:
1798 for y in d0, d1, d2:
1799 got = cmp(x, y)
1800 if (x is d0 or x is d1) and (y is d0 or y is d1):
1801 expected = 0
1802 elif x is y is d2:
1803 expected = 0
1804 elif x is d2:
1805 expected = -1
1806 else:
1807 assert y is d2
1808 expected = 1
1809 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001810
Tim Peters855fe882002-12-22 03:43:39 +00001811
Tim Peters0bf60bd2003-01-08 20:40:01 +00001812# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001813class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001814 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001815
1816 def test_empty(self):
1817 t = self.theclass()
1818 self.assertEqual(t.hour, 0)
1819 self.assertEqual(t.minute, 0)
1820 self.assertEqual(t.second, 0)
1821 self.assertEqual(t.microsecond, 0)
1822 self.failUnless(t.tzinfo is None)
1823
Tim Peters2a799bf2002-12-16 20:18:38 +00001824 def test_zones(self):
1825 est = FixedOffset(-300, "EST", 1)
1826 utc = FixedOffset(0, "UTC", -2)
1827 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001828 t1 = time( 7, 47, tzinfo=est)
1829 t2 = time(12, 47, tzinfo=utc)
1830 t3 = time(13, 47, tzinfo=met)
1831 t4 = time(microsecond=40)
1832 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001833
1834 self.assertEqual(t1.tzinfo, est)
1835 self.assertEqual(t2.tzinfo, utc)
1836 self.assertEqual(t3.tzinfo, met)
1837 self.failUnless(t4.tzinfo is None)
1838 self.assertEqual(t5.tzinfo, utc)
1839
Tim Peters855fe882002-12-22 03:43:39 +00001840 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1841 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1842 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001843 self.failUnless(t4.utcoffset() is None)
1844 self.assertRaises(TypeError, t1.utcoffset, "no args")
1845
1846 self.assertEqual(t1.tzname(), "EST")
1847 self.assertEqual(t2.tzname(), "UTC")
1848 self.assertEqual(t3.tzname(), "MET")
1849 self.failUnless(t4.tzname() is None)
1850 self.assertRaises(TypeError, t1.tzname, "no args")
1851
Tim Peters855fe882002-12-22 03:43:39 +00001852 self.assertEqual(t1.dst(), timedelta(minutes=1))
1853 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1854 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001855 self.failUnless(t4.dst() is None)
1856 self.assertRaises(TypeError, t1.dst, "no args")
1857
1858 self.assertEqual(hash(t1), hash(t2))
1859 self.assertEqual(hash(t1), hash(t3))
1860 self.assertEqual(hash(t2), hash(t3))
1861
1862 self.assertEqual(t1, t2)
1863 self.assertEqual(t1, t3)
1864 self.assertEqual(t2, t3)
1865 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1866 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1867 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1868
1869 self.assertEqual(str(t1), "07:47:00-05:00")
1870 self.assertEqual(str(t2), "12:47:00+00:00")
1871 self.assertEqual(str(t3), "13:47:00+01:00")
1872 self.assertEqual(str(t4), "00:00:00.000040")
1873 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1874
1875 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1876 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1877 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1878 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1879 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1880
Tim Peters0bf60bd2003-01-08 20:40:01 +00001881 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001882 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1883 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1884 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1885 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1886 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1887
1888 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1889 "07:47:00 %Z=EST %z=-0500")
1890 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1891 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1892
1893 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001894 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001895 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1896 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1897
Tim Petersb92bb712002-12-21 17:44:07 +00001898 # Check that an invalid tzname result raises an exception.
1899 class Badtzname(tzinfo):
1900 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001901 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001902 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1903 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001904
1905 def test_hash_edge_cases(self):
1906 # Offsets that overflow a basic time.
1907 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1908 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1909 self.assertEqual(hash(t1), hash(t2))
1910
1911 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1912 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1913 self.assertEqual(hash(t1), hash(t2))
1914
Tim Peters2a799bf2002-12-16 20:18:38 +00001915 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001916 # Try one without a tzinfo.
1917 args = 20, 59, 16, 64**2
1918 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001919 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001920 green = pickler.dumps(orig, proto)
1921 derived = unpickler.loads(green)
1922 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001923
1924 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001925 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001926 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001927 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001928 green = pickler.dumps(orig, proto)
1929 derived = unpickler.loads(green)
1930 self.assertEqual(orig, derived)
1931 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1932 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1933 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001934
1935 def test_more_bool(self):
1936 # Test cases with non-None tzinfo.
1937 cls = self.theclass
1938
1939 t = cls(0, tzinfo=FixedOffset(-300, ""))
1940 self.failUnless(t)
1941
1942 t = cls(5, tzinfo=FixedOffset(-300, ""))
1943 self.failUnless(t)
1944
1945 t = cls(5, tzinfo=FixedOffset(300, ""))
1946 self.failUnless(not t)
1947
1948 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1949 self.failUnless(not t)
1950
1951 # Mostly ensuring this doesn't overflow internally.
1952 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1953 self.failUnless(t)
1954
1955 # But this should yield a value error -- the utcoffset is bogus.
1956 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1957 self.assertRaises(ValueError, lambda: bool(t))
1958
1959 # Likewise.
1960 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1961 self.assertRaises(ValueError, lambda: bool(t))
1962
Tim Peters12bf3392002-12-24 05:41:27 +00001963 def test_replace(self):
1964 cls = self.theclass
1965 z100 = FixedOffset(100, "+100")
1966 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1967 args = [1, 2, 3, 4, z100]
1968 base = cls(*args)
1969 self.assertEqual(base, base.replace())
1970
1971 i = 0
1972 for name, newval in (("hour", 5),
1973 ("minute", 6),
1974 ("second", 7),
1975 ("microsecond", 8),
1976 ("tzinfo", zm200)):
1977 newargs = args[:]
1978 newargs[i] = newval
1979 expected = cls(*newargs)
1980 got = base.replace(**{name: newval})
1981 self.assertEqual(expected, got)
1982 i += 1
1983
1984 # Ensure we can get rid of a tzinfo.
1985 self.assertEqual(base.tzname(), "+100")
1986 base2 = base.replace(tzinfo=None)
1987 self.failUnless(base2.tzinfo is None)
1988 self.failUnless(base2.tzname() is None)
1989
1990 # Ensure we can add one.
1991 base3 = base2.replace(tzinfo=z100)
1992 self.assertEqual(base, base3)
1993 self.failUnless(base.tzinfo is base3.tzinfo)
1994
1995 # Out of bounds.
1996 base = cls(1)
1997 self.assertRaises(ValueError, base.replace, hour=24)
1998 self.assertRaises(ValueError, base.replace, minute=-1)
1999 self.assertRaises(ValueError, base.replace, second=100)
2000 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2001
Tim Peters60c76e42002-12-27 00:41:11 +00002002 def test_mixed_compare(self):
2003 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002004 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002005 self.assertEqual(t1, t2)
2006 t2 = t2.replace(tzinfo=None)
2007 self.assertEqual(t1, t2)
2008 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2009 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002010 if CMP_BUG_FIXED:
2011 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2012 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002013
Tim Peters0bf60bd2003-01-08 20:40:01 +00002014 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002015 class Varies(tzinfo):
2016 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002017 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002018 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002019 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002020 return self.offset
2021
2022 v = Varies()
2023 t1 = t2.replace(tzinfo=v)
2024 t2 = t2.replace(tzinfo=v)
2025 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2026 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2027 self.assertEqual(t1, t2)
2028
2029 # But if they're not identical, it isn't ignored.
2030 t2 = t2.replace(tzinfo=Varies())
2031 self.failUnless(t1 < t2) # t1's offset counter still going up
2032
Tim Peters4c0db782002-12-26 05:01:19 +00002033
Tim Peters0bf60bd2003-01-08 20:40:01 +00002034# Testing datetime objects with a non-None tzinfo.
2035
Tim Peters855fe882002-12-22 03:43:39 +00002036class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002037 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002038
2039 def test_trivial(self):
2040 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2041 self.assertEqual(dt.year, 1)
2042 self.assertEqual(dt.month, 2)
2043 self.assertEqual(dt.day, 3)
2044 self.assertEqual(dt.hour, 4)
2045 self.assertEqual(dt.minute, 5)
2046 self.assertEqual(dt.second, 6)
2047 self.assertEqual(dt.microsecond, 7)
2048 self.assertEqual(dt.tzinfo, None)
2049
2050 def test_even_more_compare(self):
2051 # The test_compare() and test_more_compare() inherited from TestDate
2052 # and TestDateTime covered non-tzinfo cases.
2053
2054 # Smallest possible after UTC adjustment.
2055 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2056 # Largest possible after UTC adjustment.
2057 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2058 tzinfo=FixedOffset(-1439, ""))
2059
2060 # Make sure those compare correctly, and w/o overflow.
2061 self.failUnless(t1 < t2)
2062 self.failUnless(t1 != t2)
2063 self.failUnless(t2 > t1)
2064
2065 self.failUnless(t1 == t1)
2066 self.failUnless(t2 == t2)
2067
2068 # Equal afer adjustment.
2069 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2070 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2071 self.assertEqual(t1, t2)
2072
2073 # Change t1 not to subtract a minute, and t1 should be larger.
2074 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2075 self.failUnless(t1 > t2)
2076
2077 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2078 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2079 self.failUnless(t1 < t2)
2080
2081 # Back to the original t1, but make seconds resolve it.
2082 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2083 second=1)
2084 self.failUnless(t1 > t2)
2085
2086 # Likewise, but make microseconds resolve it.
2087 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2088 microsecond=1)
2089 self.failUnless(t1 > t2)
2090
2091 # Make t2 naive and it should fail.
2092 t2 = self.theclass.min
2093 self.assertRaises(TypeError, lambda: t1 == t2)
2094 self.assertEqual(t2, t2)
2095
2096 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2097 class Naive(tzinfo):
2098 def utcoffset(self, dt): return None
2099 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2100 self.assertRaises(TypeError, lambda: t1 == t2)
2101 self.assertEqual(t2, t2)
2102
2103 # OTOH, it's OK to compare two of these mixing the two ways of being
2104 # naive.
2105 t1 = self.theclass(5, 6, 7)
2106 self.assertEqual(t1, t2)
2107
2108 # Try a bogus uctoffset.
2109 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002110 def utcoffset(self, dt):
2111 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002112 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2113 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002114 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002115
Tim Peters2a799bf2002-12-16 20:18:38 +00002116 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002117 # Try one without a tzinfo.
2118 args = 6, 7, 23, 20, 59, 1, 64**2
2119 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002120 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002121 green = pickler.dumps(orig, proto)
2122 derived = unpickler.loads(green)
2123 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002124
2125 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002126 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002127 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002128 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002129 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002130 green = pickler.dumps(orig, proto)
2131 derived = unpickler.loads(green)
2132 self.assertEqual(orig, derived)
2133 self.failUnless(isinstance(derived.tzinfo,
2134 PicklableFixedOffset))
2135 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2136 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002137
2138 def test_extreme_hashes(self):
2139 # If an attempt is made to hash these via subtracting the offset
2140 # then hashing a datetime object, OverflowError results. The
2141 # Python implementation used to blow up here.
2142 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2143 hash(t)
2144 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2145 tzinfo=FixedOffset(-1439, ""))
2146 hash(t)
2147
2148 # OTOH, an OOB offset should blow up.
2149 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2150 self.assertRaises(ValueError, hash, t)
2151
2152 def test_zones(self):
2153 est = FixedOffset(-300, "EST")
2154 utc = FixedOffset(0, "UTC")
2155 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002156 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2157 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2158 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002159 self.assertEqual(t1.tzinfo, est)
2160 self.assertEqual(t2.tzinfo, utc)
2161 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002162 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2163 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2164 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002165 self.assertEqual(t1.tzname(), "EST")
2166 self.assertEqual(t2.tzname(), "UTC")
2167 self.assertEqual(t3.tzname(), "MET")
2168 self.assertEqual(hash(t1), hash(t2))
2169 self.assertEqual(hash(t1), hash(t3))
2170 self.assertEqual(hash(t2), hash(t3))
2171 self.assertEqual(t1, t2)
2172 self.assertEqual(t1, t3)
2173 self.assertEqual(t2, t3)
2174 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2175 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2176 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002177 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002178 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2179 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2180 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2181
2182 def test_combine(self):
2183 met = FixedOffset(60, "MET")
2184 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002185 tz = time(18, 45, 3, 1234, tzinfo=met)
2186 dt = datetime.combine(d, tz)
2187 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002188 tzinfo=met))
2189
2190 def test_extract(self):
2191 met = FixedOffset(60, "MET")
2192 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2193 self.assertEqual(dt.date(), date(2002, 3, 4))
2194 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002195 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002196
2197 def test_tz_aware_arithmetic(self):
2198 import random
2199
2200 now = self.theclass.now()
2201 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002202 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002203 nowaware = self.theclass.combine(now.date(), timeaware)
2204 self.failUnless(nowaware.tzinfo is tz55)
2205 self.assertEqual(nowaware.timetz(), timeaware)
2206
2207 # Can't mix aware and non-aware.
2208 self.assertRaises(TypeError, lambda: now - nowaware)
2209 self.assertRaises(TypeError, lambda: nowaware - now)
2210
Tim Peters0bf60bd2003-01-08 20:40:01 +00002211 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002212 self.assertRaises(TypeError, lambda: now + nowaware)
2213 self.assertRaises(TypeError, lambda: nowaware + now)
2214 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2215
2216 # Subtracting should yield 0.
2217 self.assertEqual(now - now, timedelta(0))
2218 self.assertEqual(nowaware - nowaware, timedelta(0))
2219
2220 # Adding a delta should preserve tzinfo.
2221 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2222 nowawareplus = nowaware + delta
2223 self.failUnless(nowaware.tzinfo is tz55)
2224 nowawareplus2 = delta + nowaware
2225 self.failUnless(nowawareplus2.tzinfo is tz55)
2226 self.assertEqual(nowawareplus, nowawareplus2)
2227
2228 # that - delta should be what we started with, and that - what we
2229 # started with should be delta.
2230 diff = nowawareplus - delta
2231 self.failUnless(diff.tzinfo is tz55)
2232 self.assertEqual(nowaware, diff)
2233 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2234 self.assertEqual(nowawareplus - nowaware, delta)
2235
2236 # Make up a random timezone.
2237 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002238 # Attach it to nowawareplus.
2239 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002240 self.failUnless(nowawareplus.tzinfo is tzr)
2241 # Make sure the difference takes the timezone adjustments into account.
2242 got = nowaware - nowawareplus
2243 # Expected: (nowaware base - nowaware offset) -
2244 # (nowawareplus base - nowawareplus offset) =
2245 # (nowaware base - nowawareplus base) +
2246 # (nowawareplus offset - nowaware offset) =
2247 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002248 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002249 self.assertEqual(got, expected)
2250
2251 # Try max possible difference.
2252 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2253 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2254 tzinfo=FixedOffset(-1439, "max"))
2255 maxdiff = max - min
2256 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2257 timedelta(minutes=2*1439))
2258
2259 def test_tzinfo_now(self):
2260 meth = self.theclass.now
2261 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2262 base = meth()
2263 # Try with and without naming the keyword.
2264 off42 = FixedOffset(42, "42")
2265 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002266 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002267 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002268 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002269 # Bad argument with and w/o naming the keyword.
2270 self.assertRaises(TypeError, meth, 16)
2271 self.assertRaises(TypeError, meth, tzinfo=16)
2272 # Bad keyword name.
2273 self.assertRaises(TypeError, meth, tinfo=off42)
2274 # Too many args.
2275 self.assertRaises(TypeError, meth, off42, off42)
2276
Tim Peters10cadce2003-01-23 19:58:02 +00002277 # We don't know which time zone we're in, and don't have a tzinfo
2278 # class to represent it, so seeing whether a tz argument actually
2279 # does a conversion is tricky.
2280 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2281 utc = FixedOffset(0, "utc", 0)
2282 for dummy in range(3):
2283 now = datetime.now(weirdtz)
2284 self.failUnless(now.tzinfo is weirdtz)
2285 utcnow = datetime.utcnow().replace(tzinfo=utc)
2286 now2 = utcnow.astimezone(weirdtz)
2287 if abs(now - now2) < timedelta(seconds=30):
2288 break
2289 # Else the code is broken, or more than 30 seconds passed between
2290 # calls; assuming the latter, just try again.
2291 else:
2292 # Three strikes and we're out.
2293 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2294
Tim Peters2a799bf2002-12-16 20:18:38 +00002295 def test_tzinfo_fromtimestamp(self):
2296 import time
2297 meth = self.theclass.fromtimestamp
2298 ts = time.time()
2299 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2300 base = meth(ts)
2301 # Try with and without naming the keyword.
2302 off42 = FixedOffset(42, "42")
2303 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002304 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002305 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002306 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002307 # Bad argument with and w/o naming the keyword.
2308 self.assertRaises(TypeError, meth, ts, 16)
2309 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2310 # Bad keyword name.
2311 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2312 # Too many args.
2313 self.assertRaises(TypeError, meth, ts, off42, off42)
2314 # Too few args.
2315 self.assertRaises(TypeError, meth)
2316
Tim Peters2a44a8d2003-01-23 20:53:10 +00002317 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002318 timestamp = 1000000000
2319 utcdatetime = datetime.utcfromtimestamp(timestamp)
2320 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2321 # But on some flavor of Mac, it's nowhere near that. So we can't have
2322 # any idea here what time that actually is, we can only test that
2323 # relative changes match.
2324 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2325 tz = FixedOffset(utcoffset, "tz", 0)
2326 expected = utcdatetime + utcoffset
2327 got = datetime.fromtimestamp(timestamp, tz)
2328 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002329
Tim Peters2a799bf2002-12-16 20:18:38 +00002330 def test_tzinfo_utcnow(self):
2331 meth = self.theclass.utcnow
2332 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2333 base = meth()
2334 # Try with and without naming the keyword; for whatever reason,
2335 # utcnow() doesn't accept a tzinfo argument.
2336 off42 = FixedOffset(42, "42")
2337 self.assertRaises(TypeError, meth, off42)
2338 self.assertRaises(TypeError, meth, tzinfo=off42)
2339
2340 def test_tzinfo_utcfromtimestamp(self):
2341 import time
2342 meth = self.theclass.utcfromtimestamp
2343 ts = time.time()
2344 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2345 base = meth(ts)
2346 # Try with and without naming the keyword; for whatever reason,
2347 # utcfromtimestamp() doesn't accept a tzinfo argument.
2348 off42 = FixedOffset(42, "42")
2349 self.assertRaises(TypeError, meth, ts, off42)
2350 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2351
2352 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002353 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002354 # DST flag.
2355 class DST(tzinfo):
2356 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002357 if isinstance(dstvalue, int):
2358 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002359 self.dstvalue = dstvalue
2360 def dst(self, dt):
2361 return self.dstvalue
2362
2363 cls = self.theclass
2364 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2365 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2366 t = d.timetuple()
2367 self.assertEqual(1, t.tm_year)
2368 self.assertEqual(1, t.tm_mon)
2369 self.assertEqual(1, t.tm_mday)
2370 self.assertEqual(10, t.tm_hour)
2371 self.assertEqual(20, t.tm_min)
2372 self.assertEqual(30, t.tm_sec)
2373 self.assertEqual(0, t.tm_wday)
2374 self.assertEqual(1, t.tm_yday)
2375 self.assertEqual(flag, t.tm_isdst)
2376
2377 # dst() returns wrong type.
2378 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2379
2380 # dst() at the edge.
2381 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2382 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2383
2384 # dst() out of range.
2385 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2386 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2387
2388 def test_utctimetuple(self):
2389 class DST(tzinfo):
2390 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002391 if isinstance(dstvalue, int):
2392 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002393 self.dstvalue = dstvalue
2394 def dst(self, dt):
2395 return self.dstvalue
2396
2397 cls = self.theclass
2398 # This can't work: DST didn't implement utcoffset.
2399 self.assertRaises(NotImplementedError,
2400 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2401
2402 class UOFS(DST):
2403 def __init__(self, uofs, dofs=None):
2404 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002405 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002406 def utcoffset(self, dt):
2407 return self.uofs
2408
2409 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2410 # in effect for a UTC time.
2411 for dstvalue in -33, 33, 0, None:
2412 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2413 t = d.utctimetuple()
2414 self.assertEqual(d.year, t.tm_year)
2415 self.assertEqual(d.month, t.tm_mon)
2416 self.assertEqual(d.day, t.tm_mday)
2417 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2418 self.assertEqual(13, t.tm_min)
2419 self.assertEqual(d.second, t.tm_sec)
2420 self.assertEqual(d.weekday(), t.tm_wday)
2421 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2422 t.tm_yday)
2423 self.assertEqual(0, t.tm_isdst)
2424
2425 # At the edges, UTC adjustment can normalize into years out-of-range
2426 # for a datetime object. Ensure that a correct timetuple is
2427 # created anyway.
2428 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2429 # That goes back 1 minute less than a full day.
2430 t = tiny.utctimetuple()
2431 self.assertEqual(t.tm_year, MINYEAR-1)
2432 self.assertEqual(t.tm_mon, 12)
2433 self.assertEqual(t.tm_mday, 31)
2434 self.assertEqual(t.tm_hour, 0)
2435 self.assertEqual(t.tm_min, 1)
2436 self.assertEqual(t.tm_sec, 37)
2437 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2438 self.assertEqual(t.tm_isdst, 0)
2439
2440 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2441 # That goes forward 1 minute less than a full day.
2442 t = huge.utctimetuple()
2443 self.assertEqual(t.tm_year, MAXYEAR+1)
2444 self.assertEqual(t.tm_mon, 1)
2445 self.assertEqual(t.tm_mday, 1)
2446 self.assertEqual(t.tm_hour, 23)
2447 self.assertEqual(t.tm_min, 58)
2448 self.assertEqual(t.tm_sec, 37)
2449 self.assertEqual(t.tm_yday, 1)
2450 self.assertEqual(t.tm_isdst, 0)
2451
2452 def test_tzinfo_isoformat(self):
2453 zero = FixedOffset(0, "+00:00")
2454 plus = FixedOffset(220, "+03:40")
2455 minus = FixedOffset(-231, "-03:51")
2456 unknown = FixedOffset(None, "")
2457
2458 cls = self.theclass
2459 datestr = '0001-02-03'
2460 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002461 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002462 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2463 timestr = '04:05:59' + (us and '.987001' or '')
2464 ofsstr = ofs is not None and d.tzname() or ''
2465 tailstr = timestr + ofsstr
2466 iso = d.isoformat()
2467 self.assertEqual(iso, datestr + 'T' + tailstr)
2468 self.assertEqual(iso, d.isoformat('T'))
2469 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2470 self.assertEqual(str(d), datestr + ' ' + tailstr)
2471
Tim Peters12bf3392002-12-24 05:41:27 +00002472 def test_replace(self):
2473 cls = self.theclass
2474 z100 = FixedOffset(100, "+100")
2475 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2476 args = [1, 2, 3, 4, 5, 6, 7, z100]
2477 base = cls(*args)
2478 self.assertEqual(base, base.replace())
2479
2480 i = 0
2481 for name, newval in (("year", 2),
2482 ("month", 3),
2483 ("day", 4),
2484 ("hour", 5),
2485 ("minute", 6),
2486 ("second", 7),
2487 ("microsecond", 8),
2488 ("tzinfo", zm200)):
2489 newargs = args[:]
2490 newargs[i] = newval
2491 expected = cls(*newargs)
2492 got = base.replace(**{name: newval})
2493 self.assertEqual(expected, got)
2494 i += 1
2495
2496 # Ensure we can get rid of a tzinfo.
2497 self.assertEqual(base.tzname(), "+100")
2498 base2 = base.replace(tzinfo=None)
2499 self.failUnless(base2.tzinfo is None)
2500 self.failUnless(base2.tzname() is None)
2501
2502 # Ensure we can add one.
2503 base3 = base2.replace(tzinfo=z100)
2504 self.assertEqual(base, base3)
2505 self.failUnless(base.tzinfo is base3.tzinfo)
2506
2507 # Out of bounds.
2508 base = cls(2000, 2, 29)
2509 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002510
Tim Peters80475bb2002-12-25 07:40:55 +00002511 def test_more_astimezone(self):
2512 # The inherited test_astimezone covered some trivial and error cases.
2513 fnone = FixedOffset(None, "None")
2514 f44m = FixedOffset(44, "44")
2515 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2516
Tim Peters10cadce2003-01-23 19:58:02 +00002517 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002518 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002519 # Replacing with degenerate tzinfo raises an exception.
2520 self.assertRaises(ValueError, dt.astimezone, fnone)
2521 # Ditto with None tz.
2522 self.assertRaises(TypeError, dt.astimezone, None)
2523 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002524 x = dt.astimezone(dt.tzinfo)
2525 self.failUnless(x.tzinfo is f44m)
2526 self.assertEqual(x.date(), dt.date())
2527 self.assertEqual(x.time(), dt.time())
2528
2529 # Replacing with different tzinfo does adjust.
2530 got = dt.astimezone(fm5h)
2531 self.failUnless(got.tzinfo is fm5h)
2532 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2533 expected = dt - dt.utcoffset() # in effect, convert to UTC
2534 expected += fm5h.utcoffset(dt) # and from there to local time
2535 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2536 self.assertEqual(got.date(), expected.date())
2537 self.assertEqual(got.time(), expected.time())
2538 self.assertEqual(got.timetz(), expected.timetz())
2539 self.failUnless(got.tzinfo is expected.tzinfo)
2540 self.assertEqual(got, expected)
2541
Tim Peters4c0db782002-12-26 05:01:19 +00002542 def test_aware_subtract(self):
2543 cls = self.theclass
2544
Tim Peters60c76e42002-12-27 00:41:11 +00002545 # Ensure that utcoffset() is ignored when the operands have the
2546 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002547 class OperandDependentOffset(tzinfo):
2548 def utcoffset(self, t):
2549 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002550 # d0 and d1 equal after adjustment
2551 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002552 else:
Tim Peters397301e2003-01-02 21:28:08 +00002553 # d2 off in the weeds
2554 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002555
2556 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2557 d0 = base.replace(minute=3)
2558 d1 = base.replace(minute=9)
2559 d2 = base.replace(minute=11)
2560 for x in d0, d1, d2:
2561 for y in d0, d1, d2:
2562 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002563 expected = timedelta(minutes=x.minute - y.minute)
2564 self.assertEqual(got, expected)
2565
2566 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2567 # ignored.
2568 base = cls(8, 9, 10, 11, 12, 13, 14)
2569 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2570 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2571 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2572 for x in d0, d1, d2:
2573 for y in d0, d1, d2:
2574 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002575 if (x is d0 or x is d1) and (y is d0 or y is d1):
2576 expected = timedelta(0)
2577 elif x is y is d2:
2578 expected = timedelta(0)
2579 elif x is d2:
2580 expected = timedelta(minutes=(11-59)-0)
2581 else:
2582 assert y is d2
2583 expected = timedelta(minutes=0-(11-59))
2584 self.assertEqual(got, expected)
2585
Tim Peters60c76e42002-12-27 00:41:11 +00002586 def test_mixed_compare(self):
2587 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002588 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002589 self.assertEqual(t1, t2)
2590 t2 = t2.replace(tzinfo=None)
2591 self.assertEqual(t1, t2)
2592 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2593 self.assertEqual(t1, t2)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002594 if CMP_BUG_FIXED:
2595 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2596 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002597
Tim Peters0bf60bd2003-01-08 20:40:01 +00002598 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002599 class Varies(tzinfo):
2600 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002601 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002602 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002603 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002604 return self.offset
2605
2606 v = Varies()
2607 t1 = t2.replace(tzinfo=v)
2608 t2 = t2.replace(tzinfo=v)
2609 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2610 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2611 self.assertEqual(t1, t2)
2612
2613 # But if they're not identical, it isn't ignored.
2614 t2 = t2.replace(tzinfo=Varies())
2615 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002616
Tim Peters621818b2002-12-29 23:44:49 +00002617# Pain to set up DST-aware tzinfo classes.
2618
2619def first_sunday_on_or_after(dt):
2620 days_to_go = 6 - dt.weekday()
2621 if days_to_go:
2622 dt += timedelta(days_to_go)
2623 return dt
2624
2625ZERO = timedelta(0)
2626HOUR = timedelta(hours=1)
2627DAY = timedelta(days=1)
2628# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2629DSTSTART = datetime(1, 4, 1, 2)
2630# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002631# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2632# being standard time on that day, there is no spelling in local time of
2633# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2634DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002635
2636class USTimeZone(tzinfo):
2637
2638 def __init__(self, hours, reprname, stdname, dstname):
2639 self.stdoffset = timedelta(hours=hours)
2640 self.reprname = reprname
2641 self.stdname = stdname
2642 self.dstname = dstname
2643
2644 def __repr__(self):
2645 return self.reprname
2646
2647 def tzname(self, dt):
2648 if self.dst(dt):
2649 return self.dstname
2650 else:
2651 return self.stdname
2652
2653 def utcoffset(self, dt):
2654 return self.stdoffset + self.dst(dt)
2655
2656 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002657 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002658 # An exception instead may be sensible here, in one or more of
2659 # the cases.
2660 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002661 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002662
2663 # Find first Sunday in April.
2664 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2665 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2666
2667 # Find last Sunday in October.
2668 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2669 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2670
Tim Peters621818b2002-12-29 23:44:49 +00002671 # Can't compare naive to aware objects, so strip the timezone from
2672 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002673 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002674 return HOUR
2675 else:
2676 return ZERO
2677
Tim Peters521fc152002-12-31 17:36:56 +00002678Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2679Central = USTimeZone(-6, "Central", "CST", "CDT")
2680Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2681Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002682utc_real = FixedOffset(0, "UTC", 0)
2683# For better test coverage, we want another flavor of UTC that's west of
2684# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002685utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002686
2687class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002688 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002689 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002690 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002691
Tim Peters0bf60bd2003-01-08 20:40:01 +00002692 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002693
Tim Peters521fc152002-12-31 17:36:56 +00002694 # Check a time that's inside DST.
2695 def checkinside(self, dt, tz, utc, dston, dstoff):
2696 self.assertEqual(dt.dst(), HOUR)
2697
2698 # Conversion to our own timezone is always an identity.
2699 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002700
2701 asutc = dt.astimezone(utc)
2702 there_and_back = asutc.astimezone(tz)
2703
2704 # Conversion to UTC and back isn't always an identity here,
2705 # because there are redundant spellings (in local time) of
2706 # UTC time when DST begins: the clock jumps from 1:59:59
2707 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2708 # make sense then. The classes above treat 2:MM:SS as
2709 # daylight time then (it's "after 2am"), really an alias
2710 # for 1:MM:SS standard time. The latter form is what
2711 # conversion back from UTC produces.
2712 if dt.date() == dston.date() and dt.hour == 2:
2713 # We're in the redundant hour, and coming back from
2714 # UTC gives the 1:MM:SS standard-time spelling.
2715 self.assertEqual(there_and_back + HOUR, dt)
2716 # Although during was considered to be in daylight
2717 # time, there_and_back is not.
2718 self.assertEqual(there_and_back.dst(), ZERO)
2719 # They're the same times in UTC.
2720 self.assertEqual(there_and_back.astimezone(utc),
2721 dt.astimezone(utc))
2722 else:
2723 # We're not in the redundant hour.
2724 self.assertEqual(dt, there_and_back)
2725
Tim Peters327098a2003-01-20 22:54:38 +00002726 # Because we have a redundant spelling when DST begins, there is
2727 # (unforunately) an hour when DST ends that can't be spelled at all in
2728 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2729 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2730 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2731 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2732 # expressed in local time. Nevertheless, we want conversion back
2733 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002734 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002735 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002736 if dt.date() == dstoff.date() and dt.hour == 0:
2737 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002738 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002739 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2740 nexthour_utc += HOUR
2741 nexthour_tz = nexthour_utc.astimezone(tz)
2742 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002743 else:
Tim Peters327098a2003-01-20 22:54:38 +00002744 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002745
2746 # Check a time that's outside DST.
2747 def checkoutside(self, dt, tz, utc):
2748 self.assertEqual(dt.dst(), ZERO)
2749
2750 # Conversion to our own timezone is always an identity.
2751 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002752
2753 # Converting to UTC and back is an identity too.
2754 asutc = dt.astimezone(utc)
2755 there_and_back = asutc.astimezone(tz)
2756 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002757
Tim Peters1024bf82002-12-30 17:09:40 +00002758 def convert_between_tz_and_utc(self, tz, utc):
2759 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002760 # Because 1:MM on the day DST ends is taken as being standard time,
2761 # there is no spelling in tz for the last hour of daylight time.
2762 # For purposes of the test, the last hour of DST is 0:MM, which is
2763 # taken as being daylight time (and 1:MM is taken as being standard
2764 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002765 dstoff = self.dstoff.replace(tzinfo=tz)
2766 for delta in (timedelta(weeks=13),
2767 DAY,
2768 HOUR,
2769 timedelta(minutes=1),
2770 timedelta(microseconds=1)):
2771
Tim Peters521fc152002-12-31 17:36:56 +00002772 self.checkinside(dston, tz, utc, dston, dstoff)
2773 for during in dston + delta, dstoff - delta:
2774 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002775
Tim Peters521fc152002-12-31 17:36:56 +00002776 self.checkoutside(dstoff, tz, utc)
2777 for outside in dston - delta, dstoff + delta:
2778 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002779
Tim Peters621818b2002-12-29 23:44:49 +00002780 def test_easy(self):
2781 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002782 self.convert_between_tz_and_utc(Eastern, utc_real)
2783 self.convert_between_tz_and_utc(Pacific, utc_real)
2784 self.convert_between_tz_and_utc(Eastern, utc_fake)
2785 self.convert_between_tz_and_utc(Pacific, utc_fake)
2786 # The next is really dancing near the edge. It works because
2787 # Pacific and Eastern are far enough apart that their "problem
2788 # hours" don't overlap.
2789 self.convert_between_tz_and_utc(Eastern, Pacific)
2790 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002791 # OTOH, these fail! Don't enable them. The difficulty is that
2792 # the edge case tests assume that every hour is representable in
2793 # the "utc" class. This is always true for a fixed-offset tzinfo
2794 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2795 # For these adjacent DST-aware time zones, the range of time offsets
2796 # tested ends up creating hours in the one that aren't representable
2797 # in the other. For the same reason, we would see failures in the
2798 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2799 # offset deltas in convert_between_tz_and_utc().
2800 #
2801 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2802 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002803
Tim Petersf3615152003-01-01 21:51:37 +00002804 def test_tricky(self):
2805 # 22:00 on day before daylight starts.
2806 fourback = self.dston - timedelta(hours=4)
2807 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002808 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002809 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2810 # 2", we should get the 3 spelling.
2811 # If we plug 22:00 the day before into Eastern, it "looks like std
2812 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2813 # to 22:00 lands on 2:00, which makes no sense in local time (the
2814 # local clock jumps from 1 to 3). The point here is to make sure we
2815 # get the 3 spelling.
2816 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002817 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002818 self.assertEqual(expected, got)
2819
2820 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2821 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002822 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002823 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2824 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2825 # spelling.
2826 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002827 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002828 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002829
Tim Petersadf64202003-01-04 06:03:15 +00002830 # Now on the day DST ends, we want "repeat an hour" behavior.
2831 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2832 # EST 23:MM 0:MM 1:MM 2:MM
2833 # EDT 0:MM 1:MM 2:MM 3:MM
2834 # wall 0:MM 1:MM 1:MM 2:MM against these
2835 for utc in utc_real, utc_fake:
2836 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002837 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002838 # Convert that to UTC.
2839 first_std_hour -= tz.utcoffset(None)
2840 # Adjust for possibly fake UTC.
2841 asutc = first_std_hour + utc.utcoffset(None)
2842 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2843 # tz=Eastern.
2844 asutcbase = asutc.replace(tzinfo=utc)
2845 for tzhour in (0, 1, 1, 2):
2846 expectedbase = self.dstoff.replace(hour=tzhour)
2847 for minute in 0, 30, 59:
2848 expected = expectedbase.replace(minute=minute)
2849 asutc = asutcbase.replace(minute=minute)
2850 astz = asutc.astimezone(tz)
2851 self.assertEqual(astz.replace(tzinfo=None), expected)
2852 asutcbase += HOUR
2853
2854
Tim Peters710fb152003-01-02 19:35:54 +00002855 def test_bogus_dst(self):
2856 class ok(tzinfo):
2857 def utcoffset(self, dt): return HOUR
2858 def dst(self, dt): return HOUR
2859
2860 now = self.theclass.now().replace(tzinfo=utc_real)
2861 # Doesn't blow up.
2862 now.astimezone(ok())
2863
2864 # Does blow up.
2865 class notok(ok):
2866 def dst(self, dt): return None
2867 self.assertRaises(ValueError, now.astimezone, notok())
2868
Tim Peters52dcce22003-01-23 16:36:11 +00002869 def test_fromutc(self):
2870 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2871 now = datetime.utcnow().replace(tzinfo=utc_real)
2872 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2873 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2874 enow = Eastern.fromutc(now) # doesn't blow up
2875 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2876 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2877 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2878
2879 # Always converts UTC to standard time.
2880 class FauxUSTimeZone(USTimeZone):
2881 def fromutc(self, dt):
2882 return dt + self.stdoffset
2883 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2884
2885 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2886 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2887 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2888
2889 # Check around DST start.
2890 start = self.dston.replace(hour=4, tzinfo=Eastern)
2891 fstart = start.replace(tzinfo=FEastern)
2892 for wall in 23, 0, 1, 3, 4, 5:
2893 expected = start.replace(hour=wall)
2894 if wall == 23:
2895 expected -= timedelta(days=1)
2896 got = Eastern.fromutc(start)
2897 self.assertEqual(expected, got)
2898
2899 expected = fstart + FEastern.stdoffset
2900 got = FEastern.fromutc(fstart)
2901 self.assertEqual(expected, got)
2902
2903 # Ensure astimezone() calls fromutc() too.
2904 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2905 self.assertEqual(expected, got)
2906
2907 start += HOUR
2908 fstart += HOUR
2909
2910 # Check around DST end.
2911 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2912 fstart = start.replace(tzinfo=FEastern)
2913 for wall in 0, 1, 1, 2, 3, 4:
2914 expected = start.replace(hour=wall)
2915 got = Eastern.fromutc(start)
2916 self.assertEqual(expected, got)
2917
2918 expected = fstart + FEastern.stdoffset
2919 got = FEastern.fromutc(fstart)
2920 self.assertEqual(expected, got)
2921
2922 # Ensure astimezone() calls fromutc() too.
2923 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2924 self.assertEqual(expected, got)
2925
2926 start += HOUR
2927 fstart += HOUR
2928
Tim Peters710fb152003-01-02 19:35:54 +00002929
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002930def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002931 allsuites = [unittest.makeSuite(klass, 'test')
2932 for klass in (TestModule,
2933 TestTZInfo,
2934 TestTimeDelta,
2935 TestDateOnly,
2936 TestDate,
2937 TestDateTime,
2938 TestTime,
2939 TestTimeTZ,
2940 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002941 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002942 )
2943 ]
2944 return unittest.TestSuite(allsuites)
2945
2946def test_main():
2947 import gc
2948 import sys
2949
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002950 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002951 lastrc = None
2952 while True:
2953 test_support.run_suite(thesuite)
2954 if 1: # change to 0, under a debug build, for some leak detection
2955 break
2956 gc.collect()
2957 if gc.garbage:
2958 raise SystemError("gc.garbage not empty after test run: %r" %
2959 gc.garbage)
2960 if hasattr(sys, 'gettotalrefcount'):
2961 thisrc = sys.gettotalrefcount()
2962 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2963 if lastrc:
2964 print >> sys.stderr, 'delta:', thisrc - lastrc
2965 else:
2966 print >> sys.stderr
2967 lastrc = thisrc
2968
2969if __name__ == "__main__":
2970 test_main()