blob: b86286bbff839e20e7b450720a208694e3ee40cd [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters68124bb2003-02-08 03:46:31 +000025# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000028
Tim Peters2a799bf2002-12-16 20:18:38 +000029
30#############################################################################
31# module tests
32
33class TestModule(unittest.TestCase):
34
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
39
40#############################################################################
41# tzinfo tests
42
43class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000045 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000049 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
60
Tim Petersfb8472c2002-12-21 05:04:42 +000061class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
64
Tim Peters2a799bf2002-12-16 20:18:38 +000065class TestTZInfo(unittest.TestCase):
66
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
76
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
85
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
90
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000095 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000096 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000097 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000098
99 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000105 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000109
110 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000111 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000114 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000115 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000118 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000125
126#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000127# Base clase for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
132
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
135
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
138
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
143
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
146
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
149
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
152
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
157
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
162
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
165
166#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000167# timedelta tests
168
Tim Peters07534a62003-02-07 22:50:28 +0000169class TestTimeDelta(HarmlessMixedComparison):
170
171 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000172
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
176
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
189
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
197
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
201
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
236
237 def test_disallowed_computations(self):
238 a = timedelta(42)
239
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
246
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
255
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
261
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
268
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
278
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
288
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
293
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
298
299 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000300 args = 12, 34, 56
301 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000302 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000306
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
318
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
335
Tim Peters68124bb2003-02-08 03:46:31 +0000336 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
341
Tim Peters2a799bf2002-12-16 20:18:38 +0000342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
350
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
354
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
359
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
364
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
367
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
371
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
377
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
384
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
388
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
397
398 def test_overflow(self):
399 tiny = timedelta.resolution
400
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
405
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
410
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
412
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
416
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
422
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
429
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
433
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
438
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
445
446#############################################################################
447# date tests
448
449class TestDateOnly(unittest.TestCase):
450 # Tests here won't pass if also run on datetime objects, so don't
451 # subclass this to test datetimes too.
452
453 def test_delta_non_days_ignored(self):
454 dt = date(2000, 1, 2)
455 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
456 microseconds=5)
457 days = timedelta(delta.days)
458 self.assertEqual(days, timedelta(1))
459
460 dt2 = dt + delta
461 self.assertEqual(dt2, dt + days)
462
463 dt2 = delta + dt
464 self.assertEqual(dt2, dt + days)
465
466 dt2 = dt - delta
467 self.assertEqual(dt2, dt - days)
468
469 delta = -delta
470 days = timedelta(delta.days)
471 self.assertEqual(days, timedelta(-2))
472
473 dt2 = dt + delta
474 self.assertEqual(dt2, dt + days)
475
476 dt2 = delta + dt
477 self.assertEqual(dt2, dt + days)
478
479 dt2 = dt - delta
480 self.assertEqual(dt2, dt - days)
481
Guido van Rossum8b7a9a32003-04-14 22:01:58 +0000482 def test_subclass_date(self):
483 class C(date):
484 theAnswer = 42
485 dt = C(2003, 4, 14)
486 self.assertEqual(dt.__class__, C)
487
Tim Peters07534a62003-02-07 22:50:28 +0000488class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000489 # Tests here should pass for both dates and datetimes, except for a
490 # few tests that TestDateTime overrides.
491
492 theclass = date
493
494 def test_basic_attributes(self):
495 dt = self.theclass(2002, 3, 1)
496 self.assertEqual(dt.year, 2002)
497 self.assertEqual(dt.month, 3)
498 self.assertEqual(dt.day, 1)
499
500 def test_roundtrip(self):
501 for dt in (self.theclass(1, 2, 3),
502 self.theclass.today()):
503 # Verify dt -> string -> date identity.
504 s = repr(dt)
505 self.failUnless(s.startswith('datetime.'))
506 s = s[9:]
507 dt2 = eval(s)
508 self.assertEqual(dt, dt2)
509
510 # Verify identity via reconstructing from pieces.
511 dt2 = self.theclass(dt.year, dt.month, dt.day)
512 self.assertEqual(dt, dt2)
513
514 def test_ordinal_conversions(self):
515 # Check some fixed values.
516 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
517 (1, 12, 31, 365),
518 (2, 1, 1, 366),
519 # first example from "Calendrical Calculations"
520 (1945, 11, 12, 710347)]:
521 d = self.theclass(y, m, d)
522 self.assertEqual(n, d.toordinal())
523 fromord = self.theclass.fromordinal(n)
524 self.assertEqual(d, fromord)
525 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000526 # if we're checking something fancier than a date, verify
527 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000528 self.assertEqual(fromord.hour, 0)
529 self.assertEqual(fromord.minute, 0)
530 self.assertEqual(fromord.second, 0)
531 self.assertEqual(fromord.microsecond, 0)
532
Tim Peters0bf60bd2003-01-08 20:40:01 +0000533 # Check first and last days of year spottily across the whole
534 # range of years supported.
535 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000536 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
537 d = self.theclass(year, 1, 1)
538 n = d.toordinal()
539 d2 = self.theclass.fromordinal(n)
540 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000541 # Verify that moving back a day gets to the end of year-1.
542 if year > 1:
543 d = self.theclass.fromordinal(n-1)
544 d2 = self.theclass(year-1, 12, 31)
545 self.assertEqual(d, d2)
546 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000547
548 # Test every day in a leap-year and a non-leap year.
549 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
550 for year, isleap in (2000, True), (2002, False):
551 n = self.theclass(year, 1, 1).toordinal()
552 for month, maxday in zip(range(1, 13), dim):
553 if month == 2 and isleap:
554 maxday += 1
555 for day in range(1, maxday+1):
556 d = self.theclass(year, month, day)
557 self.assertEqual(d.toordinal(), n)
558 self.assertEqual(d, self.theclass.fromordinal(n))
559 n += 1
560
561 def test_extreme_ordinals(self):
562 a = self.theclass.min
563 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
564 aord = a.toordinal()
565 b = a.fromordinal(aord)
566 self.assertEqual(a, b)
567
568 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
569
570 b = a + timedelta(days=1)
571 self.assertEqual(b.toordinal(), aord + 1)
572 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
573
574 a = self.theclass.max
575 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
576 aord = a.toordinal()
577 b = a.fromordinal(aord)
578 self.assertEqual(a, b)
579
580 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
581
582 b = a - timedelta(days=1)
583 self.assertEqual(b.toordinal(), aord - 1)
584 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
585
586 def test_bad_constructor_arguments(self):
587 # bad years
588 self.theclass(MINYEAR, 1, 1) # no exception
589 self.theclass(MAXYEAR, 1, 1) # no exception
590 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
591 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
592 # bad months
593 self.theclass(2000, 1, 1) # no exception
594 self.theclass(2000, 12, 1) # no exception
595 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
596 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
597 # bad days
598 self.theclass(2000, 2, 29) # no exception
599 self.theclass(2004, 2, 29) # no exception
600 self.theclass(2400, 2, 29) # no exception
601 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
602 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
603 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
604 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
605 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
606 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
607
608 def test_hash_equality(self):
609 d = self.theclass(2000, 12, 31)
610 # same thing
611 e = self.theclass(2000, 12, 31)
612 self.assertEqual(d, e)
613 self.assertEqual(hash(d), hash(e))
614
615 dic = {d: 1}
616 dic[e] = 2
617 self.assertEqual(len(dic), 1)
618 self.assertEqual(dic[d], 2)
619 self.assertEqual(dic[e], 2)
620
621 d = self.theclass(2001, 1, 1)
622 # same thing
623 e = self.theclass(2001, 1, 1)
624 self.assertEqual(d, e)
625 self.assertEqual(hash(d), hash(e))
626
627 dic = {d: 1}
628 dic[e] = 2
629 self.assertEqual(len(dic), 1)
630 self.assertEqual(dic[d], 2)
631 self.assertEqual(dic[e], 2)
632
633 def test_computations(self):
634 a = self.theclass(2002, 1, 31)
635 b = self.theclass(1956, 1, 31)
636
637 diff = a-b
638 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
639 self.assertEqual(diff.seconds, 0)
640 self.assertEqual(diff.microseconds, 0)
641
642 day = timedelta(1)
643 week = timedelta(7)
644 a = self.theclass(2002, 3, 2)
645 self.assertEqual(a + day, self.theclass(2002, 3, 3))
646 self.assertEqual(day + a, self.theclass(2002, 3, 3))
647 self.assertEqual(a - day, self.theclass(2002, 3, 1))
648 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
649 self.assertEqual(a + week, self.theclass(2002, 3, 9))
650 self.assertEqual(a - week, self.theclass(2002, 2, 23))
651 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
652 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
653 self.assertEqual((a + week) - a, week)
654 self.assertEqual((a + day) - a, day)
655 self.assertEqual((a - week) - a, -week)
656 self.assertEqual((a - day) - a, -day)
657 self.assertEqual(a - (a + week), -week)
658 self.assertEqual(a - (a + day), -day)
659 self.assertEqual(a - (a - week), week)
660 self.assertEqual(a - (a - day), day)
661
662 # Add/sub ints, longs, floats should be illegal
663 for i in 1, 1L, 1.0:
664 self.assertRaises(TypeError, lambda: a+i)
665 self.assertRaises(TypeError, lambda: a-i)
666 self.assertRaises(TypeError, lambda: i+a)
667 self.assertRaises(TypeError, lambda: i-a)
668
669 # delta - date is senseless.
670 self.assertRaises(TypeError, lambda: day - a)
671 # mixing date and (delta or date) via * or // is senseless
672 self.assertRaises(TypeError, lambda: day * a)
673 self.assertRaises(TypeError, lambda: a * day)
674 self.assertRaises(TypeError, lambda: day // a)
675 self.assertRaises(TypeError, lambda: a // day)
676 self.assertRaises(TypeError, lambda: a * a)
677 self.assertRaises(TypeError, lambda: a // a)
678 # date + date is senseless
679 self.assertRaises(TypeError, lambda: a + a)
680
681 def test_overflow(self):
682 tiny = self.theclass.resolution
683
684 dt = self.theclass.min + tiny
685 dt -= tiny # no problem
686 self.assertRaises(OverflowError, dt.__sub__, tiny)
687 self.assertRaises(OverflowError, dt.__add__, -tiny)
688
689 dt = self.theclass.max - tiny
690 dt += tiny # no problem
691 self.assertRaises(OverflowError, dt.__add__, tiny)
692 self.assertRaises(OverflowError, dt.__sub__, -tiny)
693
694 def test_fromtimestamp(self):
695 import time
696
697 # Try an arbitrary fixed value.
698 year, month, day = 1999, 9, 19
699 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
700 d = self.theclass.fromtimestamp(ts)
701 self.assertEqual(d.year, year)
702 self.assertEqual(d.month, month)
703 self.assertEqual(d.day, day)
704
705 def test_today(self):
706 import time
707
708 # We claim that today() is like fromtimestamp(time.time()), so
709 # prove it.
710 for dummy in range(3):
711 today = self.theclass.today()
712 ts = time.time()
713 todayagain = self.theclass.fromtimestamp(ts)
714 if today == todayagain:
715 break
716 # There are several legit reasons that could fail:
717 # 1. It recently became midnight, between the today() and the
718 # time() calls.
719 # 2. The platform time() has such fine resolution that we'll
720 # never get the same value twice.
721 # 3. The platform time() has poor resolution, and we just
722 # happened to call today() right before a resolution quantum
723 # boundary.
724 # 4. The system clock got fiddled between calls.
725 # In any case, wait a little while and try again.
726 time.sleep(0.1)
727
728 # It worked or it didn't. If it didn't, assume it's reason #2, and
729 # let the test pass if they're within half a second of each other.
730 self.failUnless(today == todayagain or
731 abs(todayagain - today) < timedelta(seconds=0.5))
732
733 def test_weekday(self):
734 for i in range(7):
735 # March 4, 2002 is a Monday
736 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
737 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
738 # January 2, 1956 is a Monday
739 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
740 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
741
742 def test_isocalendar(self):
743 # Check examples from
744 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
745 for i in range(7):
746 d = self.theclass(2003, 12, 22+i)
747 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
748 d = self.theclass(2003, 12, 29) + timedelta(i)
749 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
750 d = self.theclass(2004, 1, 5+i)
751 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
752 d = self.theclass(2009, 12, 21+i)
753 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
754 d = self.theclass(2009, 12, 28) + timedelta(i)
755 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
756 d = self.theclass(2010, 1, 4+i)
757 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
758
759 def test_iso_long_years(self):
760 # Calculate long ISO years and compare to table from
761 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
762 ISO_LONG_YEARS_TABLE = """
763 4 32 60 88
764 9 37 65 93
765 15 43 71 99
766 20 48 76
767 26 54 82
768
769 105 133 161 189
770 111 139 167 195
771 116 144 172
772 122 150 178
773 128 156 184
774
775 201 229 257 285
776 207 235 263 291
777 212 240 268 296
778 218 246 274
779 224 252 280
780
781 303 331 359 387
782 308 336 364 392
783 314 342 370 398
784 320 348 376
785 325 353 381
786 """
787 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
788 iso_long_years.sort()
789 L = []
790 for i in range(400):
791 d = self.theclass(2000+i, 12, 31)
792 d1 = self.theclass(1600+i, 12, 31)
793 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
794 if d.isocalendar()[1] == 53:
795 L.append(i)
796 self.assertEqual(L, iso_long_years)
797
798 def test_isoformat(self):
799 t = self.theclass(2, 3, 2)
800 self.assertEqual(t.isoformat(), "0002-03-02")
801
802 def test_ctime(self):
803 t = self.theclass(2002, 3, 2)
804 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
805
806 def test_strftime(self):
807 t = self.theclass(2005, 3, 2)
808 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
809
810 self.assertRaises(TypeError, t.strftime) # needs an arg
811 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
812 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
813
814 # A naive object replaces %z and %Z w/ empty strings.
815 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
816
817 def test_resolution_info(self):
818 self.assert_(isinstance(self.theclass.min, self.theclass))
819 self.assert_(isinstance(self.theclass.max, self.theclass))
820 self.assert_(isinstance(self.theclass.resolution, timedelta))
821 self.assert_(self.theclass.max > self.theclass.min)
822
823 def test_extreme_timedelta(self):
824 big = self.theclass.max - self.theclass.min
825 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
826 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
827 # n == 315537897599999999 ~= 2**58.13
828 justasbig = timedelta(0, 0, n)
829 self.assertEqual(big, justasbig)
830 self.assertEqual(self.theclass.min + big, self.theclass.max)
831 self.assertEqual(self.theclass.max - big, self.theclass.min)
832
833 def test_timetuple(self):
834 for i in range(7):
835 # January 2, 1956 is a Monday (0)
836 d = self.theclass(1956, 1, 2+i)
837 t = d.timetuple()
838 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
839 # February 1, 1956 is a Wednesday (2)
840 d = self.theclass(1956, 2, 1+i)
841 t = d.timetuple()
842 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
843 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
844 # of the year.
845 d = self.theclass(1956, 3, 1+i)
846 t = d.timetuple()
847 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
848 self.assertEqual(t.tm_year, 1956)
849 self.assertEqual(t.tm_mon, 3)
850 self.assertEqual(t.tm_mday, 1+i)
851 self.assertEqual(t.tm_hour, 0)
852 self.assertEqual(t.tm_min, 0)
853 self.assertEqual(t.tm_sec, 0)
854 self.assertEqual(t.tm_wday, (3+i)%7)
855 self.assertEqual(t.tm_yday, 61+i)
856 self.assertEqual(t.tm_isdst, -1)
857
858 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000859 args = 6, 7, 23
860 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000861 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000862 green = pickler.dumps(orig, proto)
863 derived = unpickler.loads(green)
864 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000865
866 def test_compare(self):
867 t1 = self.theclass(2, 3, 4)
868 t2 = self.theclass(2, 3, 4)
869 self.failUnless(t1 == t2)
870 self.failUnless(t1 <= t2)
871 self.failUnless(t1 >= t2)
872 self.failUnless(not t1 != t2)
873 self.failUnless(not t1 < t2)
874 self.failUnless(not t1 > t2)
875 self.assertEqual(cmp(t1, t2), 0)
876 self.assertEqual(cmp(t2, t1), 0)
877
878 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
879 t2 = self.theclass(*args) # this is larger than t1
880 self.failUnless(t1 < t2)
881 self.failUnless(t2 > t1)
882 self.failUnless(t1 <= t2)
883 self.failUnless(t2 >= t1)
884 self.failUnless(t1 != t2)
885 self.failUnless(t2 != t1)
886 self.failUnless(not t1 == t2)
887 self.failUnless(not t2 == t1)
888 self.failUnless(not t1 > t2)
889 self.failUnless(not t2 < t1)
890 self.failUnless(not t1 >= t2)
891 self.failUnless(not t2 <= t1)
892 self.assertEqual(cmp(t1, t2), -1)
893 self.assertEqual(cmp(t2, t1), 1)
894
Tim Peters68124bb2003-02-08 03:46:31 +0000895 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000896 self.assertEqual(t1 == badarg, False)
897 self.assertEqual(t1 != badarg, True)
898 self.assertEqual(badarg == t1, False)
899 self.assertEqual(badarg != t1, True)
900
Tim Peters2a799bf2002-12-16 20:18:38 +0000901 self.assertRaises(TypeError, lambda: t1 < badarg)
902 self.assertRaises(TypeError, lambda: t1 > badarg)
903 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000904 self.assertRaises(TypeError, lambda: badarg <= t1)
905 self.assertRaises(TypeError, lambda: badarg < t1)
906 self.assertRaises(TypeError, lambda: badarg > t1)
907 self.assertRaises(TypeError, lambda: badarg >= t1)
908
Tim Peters8d81a012003-01-24 22:36:34 +0000909 def test_mixed_compare(self):
910 our = self.theclass(2000, 4, 5)
911 self.assertRaises(TypeError, cmp, our, 1)
912 self.assertRaises(TypeError, cmp, 1, our)
913
914 class AnotherDateTimeClass(object):
915 def __cmp__(self, other):
916 # Return "equal" so calling this can't be confused with
917 # compare-by-address (which never says "equal" for distinct
918 # objects).
919 return 0
920
921 # This still errors, because date and datetime comparison raise
922 # TypeError instead of NotImplemented when they don't know what to
923 # do, in order to stop comparison from falling back to the default
924 # compare-by-address.
925 their = AnotherDateTimeClass()
926 self.assertRaises(TypeError, cmp, our, their)
927 # Oops: The next stab raises TypeError in the C implementation,
928 # but not in the Python implementation of datetime. The difference
929 # is due to that the Python implementation defines __cmp__ but
930 # the C implementation defines tp_richcompare. This is more pain
931 # to fix than it's worth, so commenting out the test.
932 # self.assertEqual(cmp(their, our), 0)
933
934 # But date and datetime comparison return NotImplemented instead if the
935 # other object has a timetuple attr. This gives the other object a
936 # chance to do the comparison.
937 class Comparable(AnotherDateTimeClass):
938 def timetuple(self):
939 return ()
940
941 their = Comparable()
942 self.assertEqual(cmp(our, their), 0)
943 self.assertEqual(cmp(their, our), 0)
944 self.failUnless(our == their)
945 self.failUnless(their == our)
946
Tim Peters2a799bf2002-12-16 20:18:38 +0000947 def test_bool(self):
948 # All dates are considered true.
949 self.failUnless(self.theclass.min)
950 self.failUnless(self.theclass.max)
951
Tim Petersd6844152002-12-22 20:58:42 +0000952 def test_srftime_out_of_range(self):
953 # For nasty technical reasons, we can't handle years before 1900.
954 cls = self.theclass
955 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
956 for y in 1, 49, 51, 99, 100, 1000, 1899:
957 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000958
959 def test_replace(self):
960 cls = self.theclass
961 args = [1, 2, 3]
962 base = cls(*args)
963 self.assertEqual(base, base.replace())
964
965 i = 0
966 for name, newval in (("year", 2),
967 ("month", 3),
968 ("day", 4)):
969 newargs = args[:]
970 newargs[i] = newval
971 expected = cls(*newargs)
972 got = base.replace(**{name: newval})
973 self.assertEqual(expected, got)
974 i += 1
975
976 # Out of bounds.
977 base = cls(2000, 2, 29)
978 self.assertRaises(ValueError, base.replace, year=2001)
979
Tim Peters2a799bf2002-12-16 20:18:38 +0000980#############################################################################
981# datetime tests
982
983class TestDateTime(TestDate):
984
985 theclass = datetime
986
987 def test_basic_attributes(self):
988 dt = self.theclass(2002, 3, 1, 12, 0)
989 self.assertEqual(dt.year, 2002)
990 self.assertEqual(dt.month, 3)
991 self.assertEqual(dt.day, 1)
992 self.assertEqual(dt.hour, 12)
993 self.assertEqual(dt.minute, 0)
994 self.assertEqual(dt.second, 0)
995 self.assertEqual(dt.microsecond, 0)
996
997 def test_basic_attributes_nonzero(self):
998 # Make sure all attributes are non-zero so bugs in
999 # bit-shifting access show up.
1000 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1001 self.assertEqual(dt.year, 2002)
1002 self.assertEqual(dt.month, 3)
1003 self.assertEqual(dt.day, 1)
1004 self.assertEqual(dt.hour, 12)
1005 self.assertEqual(dt.minute, 59)
1006 self.assertEqual(dt.second, 59)
1007 self.assertEqual(dt.microsecond, 8000)
1008
1009 def test_roundtrip(self):
1010 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1011 self.theclass.now()):
1012 # Verify dt -> string -> datetime identity.
1013 s = repr(dt)
1014 self.failUnless(s.startswith('datetime.'))
1015 s = s[9:]
1016 dt2 = eval(s)
1017 self.assertEqual(dt, dt2)
1018
1019 # Verify identity via reconstructing from pieces.
1020 dt2 = self.theclass(dt.year, dt.month, dt.day,
1021 dt.hour, dt.minute, dt.second,
1022 dt.microsecond)
1023 self.assertEqual(dt, dt2)
1024
1025 def test_isoformat(self):
1026 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1027 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1028 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1029 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1030 # str is ISO format with the separator forced to a blank.
1031 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1032
1033 t = self.theclass(2, 3, 2)
1034 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1035 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1036 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1037 # str is ISO format with the separator forced to a blank.
1038 self.assertEqual(str(t), "0002-03-02 00:00:00")
1039
1040 def test_more_ctime(self):
1041 # Test fields that TestDate doesn't touch.
1042 import time
1043
1044 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1045 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1046 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1047 # out. The difference is that t.ctime() produces " 2" for the day,
1048 # but platform ctime() produces "02" for the day. According to
1049 # C99, t.ctime() is correct here.
1050 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1051
1052 # So test a case where that difference doesn't matter.
1053 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1054 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1055
1056 def test_tz_independent_comparing(self):
1057 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1058 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1059 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1060 self.assertEqual(dt1, dt3)
1061 self.assert_(dt2 > dt3)
1062
1063 # Make sure comparison doesn't forget microseconds, and isn't done
1064 # via comparing a float timestamp (an IEEE double doesn't have enough
1065 # precision to span microsecond resolution across years 1 thru 9999,
1066 # so comparing via timestamp necessarily calls some distinct values
1067 # equal).
1068 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1069 us = timedelta(microseconds=1)
1070 dt2 = dt1 + us
1071 self.assertEqual(dt2 - dt1, us)
1072 self.assert_(dt1 < dt2)
1073
1074 def test_bad_constructor_arguments(self):
1075 # bad years
1076 self.theclass(MINYEAR, 1, 1) # no exception
1077 self.theclass(MAXYEAR, 1, 1) # no exception
1078 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1079 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1080 # bad months
1081 self.theclass(2000, 1, 1) # no exception
1082 self.theclass(2000, 12, 1) # no exception
1083 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1084 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1085 # bad days
1086 self.theclass(2000, 2, 29) # no exception
1087 self.theclass(2004, 2, 29) # no exception
1088 self.theclass(2400, 2, 29) # no exception
1089 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1090 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1091 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1092 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1093 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1094 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1095 # bad hours
1096 self.theclass(2000, 1, 31, 0) # no exception
1097 self.theclass(2000, 1, 31, 23) # no exception
1098 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1099 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1100 # bad minutes
1101 self.theclass(2000, 1, 31, 23, 0) # no exception
1102 self.theclass(2000, 1, 31, 23, 59) # no exception
1103 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1104 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1105 # bad seconds
1106 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1107 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1108 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1109 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1110 # bad microseconds
1111 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1112 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1113 self.assertRaises(ValueError, self.theclass,
1114 2000, 1, 31, 23, 59, 59, -1)
1115 self.assertRaises(ValueError, self.theclass,
1116 2000, 1, 31, 23, 59, 59,
1117 1000000)
1118
1119 def test_hash_equality(self):
1120 d = self.theclass(2000, 12, 31, 23, 30, 17)
1121 e = self.theclass(2000, 12, 31, 23, 30, 17)
1122 self.assertEqual(d, e)
1123 self.assertEqual(hash(d), hash(e))
1124
1125 dic = {d: 1}
1126 dic[e] = 2
1127 self.assertEqual(len(dic), 1)
1128 self.assertEqual(dic[d], 2)
1129 self.assertEqual(dic[e], 2)
1130
1131 d = self.theclass(2001, 1, 1, 0, 5, 17)
1132 e = self.theclass(2001, 1, 1, 0, 5, 17)
1133 self.assertEqual(d, e)
1134 self.assertEqual(hash(d), hash(e))
1135
1136 dic = {d: 1}
1137 dic[e] = 2
1138 self.assertEqual(len(dic), 1)
1139 self.assertEqual(dic[d], 2)
1140 self.assertEqual(dic[e], 2)
1141
1142 def test_computations(self):
1143 a = self.theclass(2002, 1, 31)
1144 b = self.theclass(1956, 1, 31)
1145 diff = a-b
1146 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1147 self.assertEqual(diff.seconds, 0)
1148 self.assertEqual(diff.microseconds, 0)
1149 a = self.theclass(2002, 3, 2, 17, 6)
1150 millisec = timedelta(0, 0, 1000)
1151 hour = timedelta(0, 3600)
1152 day = timedelta(1)
1153 week = timedelta(7)
1154 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1155 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1156 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1157 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1158 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1159 self.assertEqual(a - hour, a + -hour)
1160 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1161 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1162 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1163 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1164 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1165 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1166 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1167 self.assertEqual((a + week) - a, week)
1168 self.assertEqual((a + day) - a, day)
1169 self.assertEqual((a + hour) - a, hour)
1170 self.assertEqual((a + millisec) - a, millisec)
1171 self.assertEqual((a - week) - a, -week)
1172 self.assertEqual((a - day) - a, -day)
1173 self.assertEqual((a - hour) - a, -hour)
1174 self.assertEqual((a - millisec) - a, -millisec)
1175 self.assertEqual(a - (a + week), -week)
1176 self.assertEqual(a - (a + day), -day)
1177 self.assertEqual(a - (a + hour), -hour)
1178 self.assertEqual(a - (a + millisec), -millisec)
1179 self.assertEqual(a - (a - week), week)
1180 self.assertEqual(a - (a - day), day)
1181 self.assertEqual(a - (a - hour), hour)
1182 self.assertEqual(a - (a - millisec), millisec)
1183 self.assertEqual(a + (week + day + hour + millisec),
1184 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1185 self.assertEqual(a + (week + day + hour + millisec),
1186 (((a + week) + day) + hour) + millisec)
1187 self.assertEqual(a - (week + day + hour + millisec),
1188 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1189 self.assertEqual(a - (week + day + hour + millisec),
1190 (((a - week) - day) - hour) - millisec)
1191 # Add/sub ints, longs, floats should be illegal
1192 for i in 1, 1L, 1.0:
1193 self.assertRaises(TypeError, lambda: a+i)
1194 self.assertRaises(TypeError, lambda: a-i)
1195 self.assertRaises(TypeError, lambda: i+a)
1196 self.assertRaises(TypeError, lambda: i-a)
1197
1198 # delta - datetime is senseless.
1199 self.assertRaises(TypeError, lambda: day - a)
1200 # mixing datetime and (delta or datetime) via * or // is senseless
1201 self.assertRaises(TypeError, lambda: day * a)
1202 self.assertRaises(TypeError, lambda: a * day)
1203 self.assertRaises(TypeError, lambda: day // a)
1204 self.assertRaises(TypeError, lambda: a // day)
1205 self.assertRaises(TypeError, lambda: a * a)
1206 self.assertRaises(TypeError, lambda: a // a)
1207 # datetime + datetime is senseless
1208 self.assertRaises(TypeError, lambda: a + a)
1209
1210 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001211 args = 6, 7, 23, 20, 59, 1, 64**2
1212 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001213 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001214 green = pickler.dumps(orig, proto)
1215 derived = unpickler.loads(green)
1216 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001217
Guido van Rossum275666f2003-02-07 21:49:01 +00001218 def test_more_pickling(self):
1219 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1220 s = pickle.dumps(a)
1221 b = pickle.loads(s)
1222 self.assertEqual(b.year, 2003)
1223 self.assertEqual(b.month, 2)
1224 self.assertEqual(b.day, 7)
1225
Tim Peters2a799bf2002-12-16 20:18:38 +00001226 def test_more_compare(self):
1227 # The test_compare() inherited from TestDate covers the error cases.
1228 # We just want to test lexicographic ordering on the members datetime
1229 # has that date lacks.
1230 args = [2000, 11, 29, 20, 58, 16, 999998]
1231 t1 = self.theclass(*args)
1232 t2 = self.theclass(*args)
1233 self.failUnless(t1 == t2)
1234 self.failUnless(t1 <= t2)
1235 self.failUnless(t1 >= t2)
1236 self.failUnless(not t1 != t2)
1237 self.failUnless(not t1 < t2)
1238 self.failUnless(not t1 > t2)
1239 self.assertEqual(cmp(t1, t2), 0)
1240 self.assertEqual(cmp(t2, t1), 0)
1241
1242 for i in range(len(args)):
1243 newargs = args[:]
1244 newargs[i] = args[i] + 1
1245 t2 = self.theclass(*newargs) # this is larger than t1
1246 self.failUnless(t1 < t2)
1247 self.failUnless(t2 > t1)
1248 self.failUnless(t1 <= t2)
1249 self.failUnless(t2 >= t1)
1250 self.failUnless(t1 != t2)
1251 self.failUnless(t2 != t1)
1252 self.failUnless(not t1 == t2)
1253 self.failUnless(not t2 == t1)
1254 self.failUnless(not t1 > t2)
1255 self.failUnless(not t2 < t1)
1256 self.failUnless(not t1 >= t2)
1257 self.failUnless(not t2 <= t1)
1258 self.assertEqual(cmp(t1, t2), -1)
1259 self.assertEqual(cmp(t2, t1), 1)
1260
1261
1262 # A helper for timestamp constructor tests.
1263 def verify_field_equality(self, expected, got):
1264 self.assertEqual(expected.tm_year, got.year)
1265 self.assertEqual(expected.tm_mon, got.month)
1266 self.assertEqual(expected.tm_mday, got.day)
1267 self.assertEqual(expected.tm_hour, got.hour)
1268 self.assertEqual(expected.tm_min, got.minute)
1269 self.assertEqual(expected.tm_sec, got.second)
1270
1271 def test_fromtimestamp(self):
1272 import time
1273
1274 ts = time.time()
1275 expected = time.localtime(ts)
1276 got = self.theclass.fromtimestamp(ts)
1277 self.verify_field_equality(expected, got)
1278
1279 def test_utcfromtimestamp(self):
1280 import time
1281
1282 ts = time.time()
1283 expected = time.gmtime(ts)
1284 got = self.theclass.utcfromtimestamp(ts)
1285 self.verify_field_equality(expected, got)
1286
1287 def test_utcnow(self):
1288 import time
1289
1290 # Call it a success if utcnow() and utcfromtimestamp() are within
1291 # a second of each other.
1292 tolerance = timedelta(seconds=1)
1293 for dummy in range(3):
1294 from_now = self.theclass.utcnow()
1295 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1296 if abs(from_timestamp - from_now) <= tolerance:
1297 break
1298 # Else try again a few times.
1299 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1300
1301 def test_more_timetuple(self):
1302 # This tests fields beyond those tested by the TestDate.test_timetuple.
1303 t = self.theclass(2004, 12, 31, 6, 22, 33)
1304 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1305 self.assertEqual(t.timetuple(),
1306 (t.year, t.month, t.day,
1307 t.hour, t.minute, t.second,
1308 t.weekday(),
1309 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1310 -1))
1311 tt = t.timetuple()
1312 self.assertEqual(tt.tm_year, t.year)
1313 self.assertEqual(tt.tm_mon, t.month)
1314 self.assertEqual(tt.tm_mday, t.day)
1315 self.assertEqual(tt.tm_hour, t.hour)
1316 self.assertEqual(tt.tm_min, t.minute)
1317 self.assertEqual(tt.tm_sec, t.second)
1318 self.assertEqual(tt.tm_wday, t.weekday())
1319 self.assertEqual(tt.tm_yday, t.toordinal() -
1320 date(t.year, 1, 1).toordinal() + 1)
1321 self.assertEqual(tt.tm_isdst, -1)
1322
1323 def test_more_strftime(self):
1324 # This tests fields beyond those tested by the TestDate.test_strftime.
1325 t = self.theclass(2004, 12, 31, 6, 22, 33)
1326 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1327 "12 31 04 33 22 06 366")
1328
1329 def test_extract(self):
1330 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1331 self.assertEqual(dt.date(), date(2002, 3, 4))
1332 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1333
1334 def test_combine(self):
1335 d = date(2002, 3, 4)
1336 t = time(18, 45, 3, 1234)
1337 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1338 combine = self.theclass.combine
1339 dt = combine(d, t)
1340 self.assertEqual(dt, expected)
1341
1342 dt = combine(time=t, date=d)
1343 self.assertEqual(dt, expected)
1344
1345 self.assertEqual(d, dt.date())
1346 self.assertEqual(t, dt.time())
1347 self.assertEqual(dt, combine(dt.date(), dt.time()))
1348
1349 self.assertRaises(TypeError, combine) # need an arg
1350 self.assertRaises(TypeError, combine, d) # need two args
1351 self.assertRaises(TypeError, combine, t, d) # args reversed
1352 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1353 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1354
Tim Peters12bf3392002-12-24 05:41:27 +00001355 def test_replace(self):
1356 cls = self.theclass
1357 args = [1, 2, 3, 4, 5, 6, 7]
1358 base = cls(*args)
1359 self.assertEqual(base, base.replace())
1360
1361 i = 0
1362 for name, newval in (("year", 2),
1363 ("month", 3),
1364 ("day", 4),
1365 ("hour", 5),
1366 ("minute", 6),
1367 ("second", 7),
1368 ("microsecond", 8)):
1369 newargs = args[:]
1370 newargs[i] = newval
1371 expected = cls(*newargs)
1372 got = base.replace(**{name: newval})
1373 self.assertEqual(expected, got)
1374 i += 1
1375
1376 # Out of bounds.
1377 base = cls(2000, 2, 29)
1378 self.assertRaises(ValueError, base.replace, year=2001)
1379
Tim Peters80475bb2002-12-25 07:40:55 +00001380 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001381 # Pretty boring! The TZ test is more interesting here. astimezone()
1382 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001383 dt = self.theclass.now()
1384 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001385 self.assertRaises(TypeError, dt.astimezone) # not enough args
1386 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1387 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001388 self.assertRaises(ValueError, dt.astimezone, f) # naive
1389 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001390
Tim Peters52dcce22003-01-23 16:36:11 +00001391 class Bogus(tzinfo):
1392 def utcoffset(self, dt): return None
1393 def dst(self, dt): return timedelta(0)
1394 bog = Bogus()
1395 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1396
1397 class AlsoBogus(tzinfo):
1398 def utcoffset(self, dt): return timedelta(0)
1399 def dst(self, dt): return None
1400 alsobog = AlsoBogus()
1401 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001402
Tim Peters07534a62003-02-07 22:50:28 +00001403class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001404
1405 theclass = time
1406
1407 def test_basic_attributes(self):
1408 t = self.theclass(12, 0)
1409 self.assertEqual(t.hour, 12)
1410 self.assertEqual(t.minute, 0)
1411 self.assertEqual(t.second, 0)
1412 self.assertEqual(t.microsecond, 0)
1413
1414 def test_basic_attributes_nonzero(self):
1415 # Make sure all attributes are non-zero so bugs in
1416 # bit-shifting access show up.
1417 t = self.theclass(12, 59, 59, 8000)
1418 self.assertEqual(t.hour, 12)
1419 self.assertEqual(t.minute, 59)
1420 self.assertEqual(t.second, 59)
1421 self.assertEqual(t.microsecond, 8000)
1422
1423 def test_roundtrip(self):
1424 t = self.theclass(1, 2, 3, 4)
1425
1426 # Verify t -> string -> time identity.
1427 s = repr(t)
1428 self.failUnless(s.startswith('datetime.'))
1429 s = s[9:]
1430 t2 = eval(s)
1431 self.assertEqual(t, t2)
1432
1433 # Verify identity via reconstructing from pieces.
1434 t2 = self.theclass(t.hour, t.minute, t.second,
1435 t.microsecond)
1436 self.assertEqual(t, t2)
1437
1438 def test_comparing(self):
1439 args = [1, 2, 3, 4]
1440 t1 = self.theclass(*args)
1441 t2 = self.theclass(*args)
1442 self.failUnless(t1 == t2)
1443 self.failUnless(t1 <= t2)
1444 self.failUnless(t1 >= t2)
1445 self.failUnless(not t1 != t2)
1446 self.failUnless(not t1 < t2)
1447 self.failUnless(not t1 > t2)
1448 self.assertEqual(cmp(t1, t2), 0)
1449 self.assertEqual(cmp(t2, t1), 0)
1450
1451 for i in range(len(args)):
1452 newargs = args[:]
1453 newargs[i] = args[i] + 1
1454 t2 = self.theclass(*newargs) # this is larger than t1
1455 self.failUnless(t1 < t2)
1456 self.failUnless(t2 > t1)
1457 self.failUnless(t1 <= t2)
1458 self.failUnless(t2 >= t1)
1459 self.failUnless(t1 != t2)
1460 self.failUnless(t2 != t1)
1461 self.failUnless(not t1 == t2)
1462 self.failUnless(not t2 == t1)
1463 self.failUnless(not t1 > t2)
1464 self.failUnless(not t2 < t1)
1465 self.failUnless(not t1 >= t2)
1466 self.failUnless(not t2 <= t1)
1467 self.assertEqual(cmp(t1, t2), -1)
1468 self.assertEqual(cmp(t2, t1), 1)
1469
Tim Peters68124bb2003-02-08 03:46:31 +00001470 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001471 self.assertEqual(t1 == badarg, False)
1472 self.assertEqual(t1 != badarg, True)
1473 self.assertEqual(badarg == t1, False)
1474 self.assertEqual(badarg != t1, True)
1475
Tim Peters2a799bf2002-12-16 20:18:38 +00001476 self.assertRaises(TypeError, lambda: t1 <= badarg)
1477 self.assertRaises(TypeError, lambda: t1 < badarg)
1478 self.assertRaises(TypeError, lambda: t1 > badarg)
1479 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001480 self.assertRaises(TypeError, lambda: badarg <= t1)
1481 self.assertRaises(TypeError, lambda: badarg < t1)
1482 self.assertRaises(TypeError, lambda: badarg > t1)
1483 self.assertRaises(TypeError, lambda: badarg >= t1)
1484
1485 def test_bad_constructor_arguments(self):
1486 # bad hours
1487 self.theclass(0, 0) # no exception
1488 self.theclass(23, 0) # no exception
1489 self.assertRaises(ValueError, self.theclass, -1, 0)
1490 self.assertRaises(ValueError, self.theclass, 24, 0)
1491 # bad minutes
1492 self.theclass(23, 0) # no exception
1493 self.theclass(23, 59) # no exception
1494 self.assertRaises(ValueError, self.theclass, 23, -1)
1495 self.assertRaises(ValueError, self.theclass, 23, 60)
1496 # bad seconds
1497 self.theclass(23, 59, 0) # no exception
1498 self.theclass(23, 59, 59) # no exception
1499 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1500 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1501 # bad microseconds
1502 self.theclass(23, 59, 59, 0) # no exception
1503 self.theclass(23, 59, 59, 999999) # no exception
1504 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1505 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1506
1507 def test_hash_equality(self):
1508 d = self.theclass(23, 30, 17)
1509 e = self.theclass(23, 30, 17)
1510 self.assertEqual(d, e)
1511 self.assertEqual(hash(d), hash(e))
1512
1513 dic = {d: 1}
1514 dic[e] = 2
1515 self.assertEqual(len(dic), 1)
1516 self.assertEqual(dic[d], 2)
1517 self.assertEqual(dic[e], 2)
1518
1519 d = self.theclass(0, 5, 17)
1520 e = self.theclass(0, 5, 17)
1521 self.assertEqual(d, e)
1522 self.assertEqual(hash(d), hash(e))
1523
1524 dic = {d: 1}
1525 dic[e] = 2
1526 self.assertEqual(len(dic), 1)
1527 self.assertEqual(dic[d], 2)
1528 self.assertEqual(dic[e], 2)
1529
1530 def test_isoformat(self):
1531 t = self.theclass(4, 5, 1, 123)
1532 self.assertEqual(t.isoformat(), "04:05:01.000123")
1533 self.assertEqual(t.isoformat(), str(t))
1534
1535 t = self.theclass()
1536 self.assertEqual(t.isoformat(), "00:00:00")
1537 self.assertEqual(t.isoformat(), str(t))
1538
1539 t = self.theclass(microsecond=1)
1540 self.assertEqual(t.isoformat(), "00:00:00.000001")
1541 self.assertEqual(t.isoformat(), str(t))
1542
1543 t = self.theclass(microsecond=10)
1544 self.assertEqual(t.isoformat(), "00:00:00.000010")
1545 self.assertEqual(t.isoformat(), str(t))
1546
1547 t = self.theclass(microsecond=100)
1548 self.assertEqual(t.isoformat(), "00:00:00.000100")
1549 self.assertEqual(t.isoformat(), str(t))
1550
1551 t = self.theclass(microsecond=1000)
1552 self.assertEqual(t.isoformat(), "00:00:00.001000")
1553 self.assertEqual(t.isoformat(), str(t))
1554
1555 t = self.theclass(microsecond=10000)
1556 self.assertEqual(t.isoformat(), "00:00:00.010000")
1557 self.assertEqual(t.isoformat(), str(t))
1558
1559 t = self.theclass(microsecond=100000)
1560 self.assertEqual(t.isoformat(), "00:00:00.100000")
1561 self.assertEqual(t.isoformat(), str(t))
1562
1563 def test_strftime(self):
1564 t = self.theclass(1, 2, 3, 4)
1565 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1566 # A naive object replaces %z and %Z with empty strings.
1567 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1568
1569 def test_str(self):
1570 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1571 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1572 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1573 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1574 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1575
1576 def test_repr(self):
1577 name = 'datetime.' + self.theclass.__name__
1578 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1579 "%s(1, 2, 3, 4)" % name)
1580 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1581 "%s(10, 2, 3, 4000)" % name)
1582 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1583 "%s(0, 2, 3, 400000)" % name)
1584 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1585 "%s(12, 2, 3)" % name)
1586 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1587 "%s(23, 15)" % name)
1588
1589 def test_resolution_info(self):
1590 self.assert_(isinstance(self.theclass.min, self.theclass))
1591 self.assert_(isinstance(self.theclass.max, self.theclass))
1592 self.assert_(isinstance(self.theclass.resolution, timedelta))
1593 self.assert_(self.theclass.max > self.theclass.min)
1594
1595 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001596 args = 20, 59, 16, 64**2
1597 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001598 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001599 green = pickler.dumps(orig, proto)
1600 derived = unpickler.loads(green)
1601 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001602
1603 def test_bool(self):
1604 cls = self.theclass
1605 self.failUnless(cls(1))
1606 self.failUnless(cls(0, 1))
1607 self.failUnless(cls(0, 0, 1))
1608 self.failUnless(cls(0, 0, 0, 1))
1609 self.failUnless(not cls(0))
1610 self.failUnless(not cls())
1611
Tim Peters12bf3392002-12-24 05:41:27 +00001612 def test_replace(self):
1613 cls = self.theclass
1614 args = [1, 2, 3, 4]
1615 base = cls(*args)
1616 self.assertEqual(base, base.replace())
1617
1618 i = 0
1619 for name, newval in (("hour", 5),
1620 ("minute", 6),
1621 ("second", 7),
1622 ("microsecond", 8)):
1623 newargs = args[:]
1624 newargs[i] = newval
1625 expected = cls(*newargs)
1626 got = base.replace(**{name: newval})
1627 self.assertEqual(expected, got)
1628 i += 1
1629
1630 # Out of bounds.
1631 base = cls(1)
1632 self.assertRaises(ValueError, base.replace, hour=24)
1633 self.assertRaises(ValueError, base.replace, minute=-1)
1634 self.assertRaises(ValueError, base.replace, second=100)
1635 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1636
Tim Peters855fe882002-12-22 03:43:39 +00001637# A mixin for classes with a tzinfo= argument. Subclasses must define
1638# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001639# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001640class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001641
Tim Petersbad8ff02002-12-30 20:52:32 +00001642 def test_argument_passing(self):
1643 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001644 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001645 class introspective(tzinfo):
1646 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001647 def utcoffset(self, dt):
1648 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001649 dst = utcoffset
1650
1651 obj = cls(1, 2, 3, tzinfo=introspective())
1652
Tim Peters0bf60bd2003-01-08 20:40:01 +00001653 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001654 self.assertEqual(obj.tzname(), expected)
1655
Tim Peters0bf60bd2003-01-08 20:40:01 +00001656 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001657 self.assertEqual(obj.utcoffset(), expected)
1658 self.assertEqual(obj.dst(), expected)
1659
Tim Peters855fe882002-12-22 03:43:39 +00001660 def test_bad_tzinfo_classes(self):
1661 cls = self.theclass
1662 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001663
Tim Peters855fe882002-12-22 03:43:39 +00001664 class NiceTry(object):
1665 def __init__(self): pass
1666 def utcoffset(self, dt): pass
1667 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1668
1669 class BetterTry(tzinfo):
1670 def __init__(self): pass
1671 def utcoffset(self, dt): pass
1672 b = BetterTry()
1673 t = cls(1, 1, 1, tzinfo=b)
1674 self.failUnless(t.tzinfo is b)
1675
1676 def test_utc_offset_out_of_bounds(self):
1677 class Edgy(tzinfo):
1678 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001679 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001680 def utcoffset(self, dt):
1681 return self.offset
1682
1683 cls = self.theclass
1684 for offset, legit in ((-1440, False),
1685 (-1439, True),
1686 (1439, True),
1687 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001688 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001689 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001690 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001691 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001692 else:
1693 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001694 if legit:
1695 aofs = abs(offset)
1696 h, m = divmod(aofs, 60)
1697 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001698 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001699 t = t.timetz()
1700 self.assertEqual(str(t), "01:02:03" + tag)
1701 else:
1702 self.assertRaises(ValueError, str, t)
1703
1704 def test_tzinfo_classes(self):
1705 cls = self.theclass
1706 class C1(tzinfo):
1707 def utcoffset(self, dt): return None
1708 def dst(self, dt): return None
1709 def tzname(self, dt): return None
1710 for t in (cls(1, 1, 1),
1711 cls(1, 1, 1, tzinfo=None),
1712 cls(1, 1, 1, tzinfo=C1())):
1713 self.failUnless(t.utcoffset() is None)
1714 self.failUnless(t.dst() is None)
1715 self.failUnless(t.tzname() is None)
1716
Tim Peters855fe882002-12-22 03:43:39 +00001717 class C3(tzinfo):
1718 def utcoffset(self, dt): return timedelta(minutes=-1439)
1719 def dst(self, dt): return timedelta(minutes=1439)
1720 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001721 t = cls(1, 1, 1, tzinfo=C3())
1722 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1723 self.assertEqual(t.dst(), timedelta(minutes=1439))
1724 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001725
1726 # Wrong types.
1727 class C4(tzinfo):
1728 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001729 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001730 def tzname(self, dt): return 0
1731 t = cls(1, 1, 1, tzinfo=C4())
1732 self.assertRaises(TypeError, t.utcoffset)
1733 self.assertRaises(TypeError, t.dst)
1734 self.assertRaises(TypeError, t.tzname)
1735
1736 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001737 class C6(tzinfo):
1738 def utcoffset(self, dt): return timedelta(hours=-24)
1739 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001740 t = cls(1, 1, 1, tzinfo=C6())
1741 self.assertRaises(ValueError, t.utcoffset)
1742 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001743
1744 # Not a whole number of minutes.
1745 class C7(tzinfo):
1746 def utcoffset(self, dt): return timedelta(seconds=61)
1747 def dst(self, dt): return timedelta(microseconds=-81)
1748 t = cls(1, 1, 1, tzinfo=C7())
1749 self.assertRaises(ValueError, t.utcoffset)
1750 self.assertRaises(ValueError, t.dst)
1751
Tim Peters4c0db782002-12-26 05:01:19 +00001752 def test_aware_compare(self):
1753 cls = self.theclass
1754
Tim Peters60c76e42002-12-27 00:41:11 +00001755 # Ensure that utcoffset() gets ignored if the comparands have
1756 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001757 class OperandDependentOffset(tzinfo):
1758 def utcoffset(self, t):
1759 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001760 # d0 and d1 equal after adjustment
1761 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001762 else:
Tim Peters397301e2003-01-02 21:28:08 +00001763 # d2 off in the weeds
1764 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001765
1766 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1767 d0 = base.replace(minute=3)
1768 d1 = base.replace(minute=9)
1769 d2 = base.replace(minute=11)
1770 for x in d0, d1, d2:
1771 for y in d0, d1, d2:
1772 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001773 expected = cmp(x.minute, y.minute)
1774 self.assertEqual(got, expected)
1775
1776 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001777 # Note that a time can't actually have an operand-depedent offset,
1778 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1779 # so skip this test for time.
1780 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001781 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1782 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1783 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1784 for x in d0, d1, d2:
1785 for y in d0, d1, d2:
1786 got = cmp(x, y)
1787 if (x is d0 or x is d1) and (y is d0 or y is d1):
1788 expected = 0
1789 elif x is y is d2:
1790 expected = 0
1791 elif x is d2:
1792 expected = -1
1793 else:
1794 assert y is d2
1795 expected = 1
1796 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001797
Tim Peters855fe882002-12-22 03:43:39 +00001798
Tim Peters0bf60bd2003-01-08 20:40:01 +00001799# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001800class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001801 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001802
1803 def test_empty(self):
1804 t = self.theclass()
1805 self.assertEqual(t.hour, 0)
1806 self.assertEqual(t.minute, 0)
1807 self.assertEqual(t.second, 0)
1808 self.assertEqual(t.microsecond, 0)
1809 self.failUnless(t.tzinfo is None)
1810
Tim Peters2a799bf2002-12-16 20:18:38 +00001811 def test_zones(self):
1812 est = FixedOffset(-300, "EST", 1)
1813 utc = FixedOffset(0, "UTC", -2)
1814 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001815 t1 = time( 7, 47, tzinfo=est)
1816 t2 = time(12, 47, tzinfo=utc)
1817 t3 = time(13, 47, tzinfo=met)
1818 t4 = time(microsecond=40)
1819 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001820
1821 self.assertEqual(t1.tzinfo, est)
1822 self.assertEqual(t2.tzinfo, utc)
1823 self.assertEqual(t3.tzinfo, met)
1824 self.failUnless(t4.tzinfo is None)
1825 self.assertEqual(t5.tzinfo, utc)
1826
Tim Peters855fe882002-12-22 03:43:39 +00001827 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1828 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1829 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001830 self.failUnless(t4.utcoffset() is None)
1831 self.assertRaises(TypeError, t1.utcoffset, "no args")
1832
1833 self.assertEqual(t1.tzname(), "EST")
1834 self.assertEqual(t2.tzname(), "UTC")
1835 self.assertEqual(t3.tzname(), "MET")
1836 self.failUnless(t4.tzname() is None)
1837 self.assertRaises(TypeError, t1.tzname, "no args")
1838
Tim Peters855fe882002-12-22 03:43:39 +00001839 self.assertEqual(t1.dst(), timedelta(minutes=1))
1840 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1841 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001842 self.failUnless(t4.dst() is None)
1843 self.assertRaises(TypeError, t1.dst, "no args")
1844
1845 self.assertEqual(hash(t1), hash(t2))
1846 self.assertEqual(hash(t1), hash(t3))
1847 self.assertEqual(hash(t2), hash(t3))
1848
1849 self.assertEqual(t1, t2)
1850 self.assertEqual(t1, t3)
1851 self.assertEqual(t2, t3)
1852 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1853 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1854 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1855
1856 self.assertEqual(str(t1), "07:47:00-05:00")
1857 self.assertEqual(str(t2), "12:47:00+00:00")
1858 self.assertEqual(str(t3), "13:47:00+01:00")
1859 self.assertEqual(str(t4), "00:00:00.000040")
1860 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1861
1862 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1863 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1864 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1865 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1866 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1867
Tim Peters0bf60bd2003-01-08 20:40:01 +00001868 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001869 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1870 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1871 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1872 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1873 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1874
1875 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1876 "07:47:00 %Z=EST %z=-0500")
1877 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1878 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1879
1880 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001881 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001882 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1883 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1884
Tim Petersb92bb712002-12-21 17:44:07 +00001885 # Check that an invalid tzname result raises an exception.
1886 class Badtzname(tzinfo):
1887 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001888 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001889 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1890 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001891
1892 def test_hash_edge_cases(self):
1893 # Offsets that overflow a basic time.
1894 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
1895 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
1896 self.assertEqual(hash(t1), hash(t2))
1897
1898 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
1899 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
1900 self.assertEqual(hash(t1), hash(t2))
1901
Tim Peters2a799bf2002-12-16 20:18:38 +00001902 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001903 # Try one without a tzinfo.
1904 args = 20, 59, 16, 64**2
1905 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001906 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001907 green = pickler.dumps(orig, proto)
1908 derived = unpickler.loads(green)
1909 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001910
1911 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00001912 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001913 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001914 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001915 green = pickler.dumps(orig, proto)
1916 derived = unpickler.loads(green)
1917 self.assertEqual(orig, derived)
1918 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
1919 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
1920 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00001921
1922 def test_more_bool(self):
1923 # Test cases with non-None tzinfo.
1924 cls = self.theclass
1925
1926 t = cls(0, tzinfo=FixedOffset(-300, ""))
1927 self.failUnless(t)
1928
1929 t = cls(5, tzinfo=FixedOffset(-300, ""))
1930 self.failUnless(t)
1931
1932 t = cls(5, tzinfo=FixedOffset(300, ""))
1933 self.failUnless(not t)
1934
1935 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
1936 self.failUnless(not t)
1937
1938 # Mostly ensuring this doesn't overflow internally.
1939 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
1940 self.failUnless(t)
1941
1942 # But this should yield a value error -- the utcoffset is bogus.
1943 t = cls(0, tzinfo=FixedOffset(24*60, ""))
1944 self.assertRaises(ValueError, lambda: bool(t))
1945
1946 # Likewise.
1947 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
1948 self.assertRaises(ValueError, lambda: bool(t))
1949
Tim Peters12bf3392002-12-24 05:41:27 +00001950 def test_replace(self):
1951 cls = self.theclass
1952 z100 = FixedOffset(100, "+100")
1953 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
1954 args = [1, 2, 3, 4, z100]
1955 base = cls(*args)
1956 self.assertEqual(base, base.replace())
1957
1958 i = 0
1959 for name, newval in (("hour", 5),
1960 ("minute", 6),
1961 ("second", 7),
1962 ("microsecond", 8),
1963 ("tzinfo", zm200)):
1964 newargs = args[:]
1965 newargs[i] = newval
1966 expected = cls(*newargs)
1967 got = base.replace(**{name: newval})
1968 self.assertEqual(expected, got)
1969 i += 1
1970
1971 # Ensure we can get rid of a tzinfo.
1972 self.assertEqual(base.tzname(), "+100")
1973 base2 = base.replace(tzinfo=None)
1974 self.failUnless(base2.tzinfo is None)
1975 self.failUnless(base2.tzname() is None)
1976
1977 # Ensure we can add one.
1978 base3 = base2.replace(tzinfo=z100)
1979 self.assertEqual(base, base3)
1980 self.failUnless(base.tzinfo is base3.tzinfo)
1981
1982 # Out of bounds.
1983 base = cls(1)
1984 self.assertRaises(ValueError, base.replace, hour=24)
1985 self.assertRaises(ValueError, base.replace, minute=-1)
1986 self.assertRaises(ValueError, base.replace, second=100)
1987 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1988
Tim Peters60c76e42002-12-27 00:41:11 +00001989 def test_mixed_compare(self):
1990 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001991 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00001992 self.assertEqual(t1, t2)
1993 t2 = t2.replace(tzinfo=None)
1994 self.assertEqual(t1, t2)
1995 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
1996 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00001997 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
1998 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00001999
Tim Peters0bf60bd2003-01-08 20:40:01 +00002000 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002001 class Varies(tzinfo):
2002 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002003 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002004 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002005 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002006 return self.offset
2007
2008 v = Varies()
2009 t1 = t2.replace(tzinfo=v)
2010 t2 = t2.replace(tzinfo=v)
2011 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2012 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2013 self.assertEqual(t1, t2)
2014
2015 # But if they're not identical, it isn't ignored.
2016 t2 = t2.replace(tzinfo=Varies())
2017 self.failUnless(t1 < t2) # t1's offset counter still going up
2018
Tim Peters4c0db782002-12-26 05:01:19 +00002019
Tim Peters0bf60bd2003-01-08 20:40:01 +00002020# Testing datetime objects with a non-None tzinfo.
2021
Tim Peters855fe882002-12-22 03:43:39 +00002022class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002023 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002024
2025 def test_trivial(self):
2026 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2027 self.assertEqual(dt.year, 1)
2028 self.assertEqual(dt.month, 2)
2029 self.assertEqual(dt.day, 3)
2030 self.assertEqual(dt.hour, 4)
2031 self.assertEqual(dt.minute, 5)
2032 self.assertEqual(dt.second, 6)
2033 self.assertEqual(dt.microsecond, 7)
2034 self.assertEqual(dt.tzinfo, None)
2035
2036 def test_even_more_compare(self):
2037 # The test_compare() and test_more_compare() inherited from TestDate
2038 # and TestDateTime covered non-tzinfo cases.
2039
2040 # Smallest possible after UTC adjustment.
2041 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2042 # Largest possible after UTC adjustment.
2043 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2044 tzinfo=FixedOffset(-1439, ""))
2045
2046 # Make sure those compare correctly, and w/o overflow.
2047 self.failUnless(t1 < t2)
2048 self.failUnless(t1 != t2)
2049 self.failUnless(t2 > t1)
2050
2051 self.failUnless(t1 == t1)
2052 self.failUnless(t2 == t2)
2053
2054 # Equal afer adjustment.
2055 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2056 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2057 self.assertEqual(t1, t2)
2058
2059 # Change t1 not to subtract a minute, and t1 should be larger.
2060 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2061 self.failUnless(t1 > t2)
2062
2063 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2064 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2065 self.failUnless(t1 < t2)
2066
2067 # Back to the original t1, but make seconds resolve it.
2068 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2069 second=1)
2070 self.failUnless(t1 > t2)
2071
2072 # Likewise, but make microseconds resolve it.
2073 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2074 microsecond=1)
2075 self.failUnless(t1 > t2)
2076
2077 # Make t2 naive and it should fail.
2078 t2 = self.theclass.min
2079 self.assertRaises(TypeError, lambda: t1 == t2)
2080 self.assertEqual(t2, t2)
2081
2082 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2083 class Naive(tzinfo):
2084 def utcoffset(self, dt): return None
2085 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2086 self.assertRaises(TypeError, lambda: t1 == t2)
2087 self.assertEqual(t2, t2)
2088
2089 # OTOH, it's OK to compare two of these mixing the two ways of being
2090 # naive.
2091 t1 = self.theclass(5, 6, 7)
2092 self.assertEqual(t1, t2)
2093
2094 # Try a bogus uctoffset.
2095 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002096 def utcoffset(self, dt):
2097 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002098 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2099 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002100 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002101
Tim Peters2a799bf2002-12-16 20:18:38 +00002102 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002103 # Try one without a tzinfo.
2104 args = 6, 7, 23, 20, 59, 1, 64**2
2105 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002106 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002107 green = pickler.dumps(orig, proto)
2108 derived = unpickler.loads(green)
2109 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002110
2111 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002112 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002113 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002114 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002115 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002116 green = pickler.dumps(orig, proto)
2117 derived = unpickler.loads(green)
2118 self.assertEqual(orig, derived)
2119 self.failUnless(isinstance(derived.tzinfo,
2120 PicklableFixedOffset))
2121 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2122 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002123
2124 def test_extreme_hashes(self):
2125 # If an attempt is made to hash these via subtracting the offset
2126 # then hashing a datetime object, OverflowError results. The
2127 # Python implementation used to blow up here.
2128 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2129 hash(t)
2130 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2131 tzinfo=FixedOffset(-1439, ""))
2132 hash(t)
2133
2134 # OTOH, an OOB offset should blow up.
2135 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2136 self.assertRaises(ValueError, hash, t)
2137
2138 def test_zones(self):
2139 est = FixedOffset(-300, "EST")
2140 utc = FixedOffset(0, "UTC")
2141 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002142 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2143 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2144 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002145 self.assertEqual(t1.tzinfo, est)
2146 self.assertEqual(t2.tzinfo, utc)
2147 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002148 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2149 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2150 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002151 self.assertEqual(t1.tzname(), "EST")
2152 self.assertEqual(t2.tzname(), "UTC")
2153 self.assertEqual(t3.tzname(), "MET")
2154 self.assertEqual(hash(t1), hash(t2))
2155 self.assertEqual(hash(t1), hash(t3))
2156 self.assertEqual(hash(t2), hash(t3))
2157 self.assertEqual(t1, t2)
2158 self.assertEqual(t1, t3)
2159 self.assertEqual(t2, t3)
2160 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2161 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2162 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002163 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002164 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2165 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2166 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2167
2168 def test_combine(self):
2169 met = FixedOffset(60, "MET")
2170 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002171 tz = time(18, 45, 3, 1234, tzinfo=met)
2172 dt = datetime.combine(d, tz)
2173 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002174 tzinfo=met))
2175
2176 def test_extract(self):
2177 met = FixedOffset(60, "MET")
2178 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2179 self.assertEqual(dt.date(), date(2002, 3, 4))
2180 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002181 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002182
2183 def test_tz_aware_arithmetic(self):
2184 import random
2185
2186 now = self.theclass.now()
2187 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002188 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002189 nowaware = self.theclass.combine(now.date(), timeaware)
2190 self.failUnless(nowaware.tzinfo is tz55)
2191 self.assertEqual(nowaware.timetz(), timeaware)
2192
2193 # Can't mix aware and non-aware.
2194 self.assertRaises(TypeError, lambda: now - nowaware)
2195 self.assertRaises(TypeError, lambda: nowaware - now)
2196
Tim Peters0bf60bd2003-01-08 20:40:01 +00002197 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002198 self.assertRaises(TypeError, lambda: now + nowaware)
2199 self.assertRaises(TypeError, lambda: nowaware + now)
2200 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2201
2202 # Subtracting should yield 0.
2203 self.assertEqual(now - now, timedelta(0))
2204 self.assertEqual(nowaware - nowaware, timedelta(0))
2205
2206 # Adding a delta should preserve tzinfo.
2207 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2208 nowawareplus = nowaware + delta
2209 self.failUnless(nowaware.tzinfo is tz55)
2210 nowawareplus2 = delta + nowaware
2211 self.failUnless(nowawareplus2.tzinfo is tz55)
2212 self.assertEqual(nowawareplus, nowawareplus2)
2213
2214 # that - delta should be what we started with, and that - what we
2215 # started with should be delta.
2216 diff = nowawareplus - delta
2217 self.failUnless(diff.tzinfo is tz55)
2218 self.assertEqual(nowaware, diff)
2219 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2220 self.assertEqual(nowawareplus - nowaware, delta)
2221
2222 # Make up a random timezone.
2223 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002224 # Attach it to nowawareplus.
2225 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002226 self.failUnless(nowawareplus.tzinfo is tzr)
2227 # Make sure the difference takes the timezone adjustments into account.
2228 got = nowaware - nowawareplus
2229 # Expected: (nowaware base - nowaware offset) -
2230 # (nowawareplus base - nowawareplus offset) =
2231 # (nowaware base - nowawareplus base) +
2232 # (nowawareplus offset - nowaware offset) =
2233 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002234 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002235 self.assertEqual(got, expected)
2236
2237 # Try max possible difference.
2238 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2239 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2240 tzinfo=FixedOffset(-1439, "max"))
2241 maxdiff = max - min
2242 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2243 timedelta(minutes=2*1439))
2244
2245 def test_tzinfo_now(self):
2246 meth = self.theclass.now
2247 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2248 base = meth()
2249 # Try with and without naming the keyword.
2250 off42 = FixedOffset(42, "42")
2251 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002252 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002253 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002254 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002255 # Bad argument with and w/o naming the keyword.
2256 self.assertRaises(TypeError, meth, 16)
2257 self.assertRaises(TypeError, meth, tzinfo=16)
2258 # Bad keyword name.
2259 self.assertRaises(TypeError, meth, tinfo=off42)
2260 # Too many args.
2261 self.assertRaises(TypeError, meth, off42, off42)
2262
Tim Peters10cadce2003-01-23 19:58:02 +00002263 # We don't know which time zone we're in, and don't have a tzinfo
2264 # class to represent it, so seeing whether a tz argument actually
2265 # does a conversion is tricky.
2266 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2267 utc = FixedOffset(0, "utc", 0)
2268 for dummy in range(3):
2269 now = datetime.now(weirdtz)
2270 self.failUnless(now.tzinfo is weirdtz)
2271 utcnow = datetime.utcnow().replace(tzinfo=utc)
2272 now2 = utcnow.astimezone(weirdtz)
2273 if abs(now - now2) < timedelta(seconds=30):
2274 break
2275 # Else the code is broken, or more than 30 seconds passed between
2276 # calls; assuming the latter, just try again.
2277 else:
2278 # Three strikes and we're out.
2279 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2280
Tim Peters2a799bf2002-12-16 20:18:38 +00002281 def test_tzinfo_fromtimestamp(self):
2282 import time
2283 meth = self.theclass.fromtimestamp
2284 ts = time.time()
2285 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2286 base = meth(ts)
2287 # Try with and without naming the keyword.
2288 off42 = FixedOffset(42, "42")
2289 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002290 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002291 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002292 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002293 # Bad argument with and w/o naming the keyword.
2294 self.assertRaises(TypeError, meth, ts, 16)
2295 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2296 # Bad keyword name.
2297 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2298 # Too many args.
2299 self.assertRaises(TypeError, meth, ts, off42, off42)
2300 # Too few args.
2301 self.assertRaises(TypeError, meth)
2302
Tim Peters2a44a8d2003-01-23 20:53:10 +00002303 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002304 timestamp = 1000000000
2305 utcdatetime = datetime.utcfromtimestamp(timestamp)
2306 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2307 # But on some flavor of Mac, it's nowhere near that. So we can't have
2308 # any idea here what time that actually is, we can only test that
2309 # relative changes match.
2310 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2311 tz = FixedOffset(utcoffset, "tz", 0)
2312 expected = utcdatetime + utcoffset
2313 got = datetime.fromtimestamp(timestamp, tz)
2314 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002315
Tim Peters2a799bf2002-12-16 20:18:38 +00002316 def test_tzinfo_utcnow(self):
2317 meth = self.theclass.utcnow
2318 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2319 base = meth()
2320 # Try with and without naming the keyword; for whatever reason,
2321 # utcnow() doesn't accept a tzinfo argument.
2322 off42 = FixedOffset(42, "42")
2323 self.assertRaises(TypeError, meth, off42)
2324 self.assertRaises(TypeError, meth, tzinfo=off42)
2325
2326 def test_tzinfo_utcfromtimestamp(self):
2327 import time
2328 meth = self.theclass.utcfromtimestamp
2329 ts = time.time()
2330 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2331 base = meth(ts)
2332 # Try with and without naming the keyword; for whatever reason,
2333 # utcfromtimestamp() doesn't accept a tzinfo argument.
2334 off42 = FixedOffset(42, "42")
2335 self.assertRaises(TypeError, meth, ts, off42)
2336 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2337
2338 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002339 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002340 # DST flag.
2341 class DST(tzinfo):
2342 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002343 if isinstance(dstvalue, int):
2344 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002345 self.dstvalue = dstvalue
2346 def dst(self, dt):
2347 return self.dstvalue
2348
2349 cls = self.theclass
2350 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2351 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2352 t = d.timetuple()
2353 self.assertEqual(1, t.tm_year)
2354 self.assertEqual(1, t.tm_mon)
2355 self.assertEqual(1, t.tm_mday)
2356 self.assertEqual(10, t.tm_hour)
2357 self.assertEqual(20, t.tm_min)
2358 self.assertEqual(30, t.tm_sec)
2359 self.assertEqual(0, t.tm_wday)
2360 self.assertEqual(1, t.tm_yday)
2361 self.assertEqual(flag, t.tm_isdst)
2362
2363 # dst() returns wrong type.
2364 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2365
2366 # dst() at the edge.
2367 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2368 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2369
2370 # dst() out of range.
2371 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2372 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2373
2374 def test_utctimetuple(self):
2375 class DST(tzinfo):
2376 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002377 if isinstance(dstvalue, int):
2378 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002379 self.dstvalue = dstvalue
2380 def dst(self, dt):
2381 return self.dstvalue
2382
2383 cls = self.theclass
2384 # This can't work: DST didn't implement utcoffset.
2385 self.assertRaises(NotImplementedError,
2386 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2387
2388 class UOFS(DST):
2389 def __init__(self, uofs, dofs=None):
2390 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002391 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002392 def utcoffset(self, dt):
2393 return self.uofs
2394
2395 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2396 # in effect for a UTC time.
2397 for dstvalue in -33, 33, 0, None:
2398 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2399 t = d.utctimetuple()
2400 self.assertEqual(d.year, t.tm_year)
2401 self.assertEqual(d.month, t.tm_mon)
2402 self.assertEqual(d.day, t.tm_mday)
2403 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2404 self.assertEqual(13, t.tm_min)
2405 self.assertEqual(d.second, t.tm_sec)
2406 self.assertEqual(d.weekday(), t.tm_wday)
2407 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2408 t.tm_yday)
2409 self.assertEqual(0, t.tm_isdst)
2410
2411 # At the edges, UTC adjustment can normalize into years out-of-range
2412 # for a datetime object. Ensure that a correct timetuple is
2413 # created anyway.
2414 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2415 # That goes back 1 minute less than a full day.
2416 t = tiny.utctimetuple()
2417 self.assertEqual(t.tm_year, MINYEAR-1)
2418 self.assertEqual(t.tm_mon, 12)
2419 self.assertEqual(t.tm_mday, 31)
2420 self.assertEqual(t.tm_hour, 0)
2421 self.assertEqual(t.tm_min, 1)
2422 self.assertEqual(t.tm_sec, 37)
2423 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2424 self.assertEqual(t.tm_isdst, 0)
2425
2426 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2427 # That goes forward 1 minute less than a full day.
2428 t = huge.utctimetuple()
2429 self.assertEqual(t.tm_year, MAXYEAR+1)
2430 self.assertEqual(t.tm_mon, 1)
2431 self.assertEqual(t.tm_mday, 1)
2432 self.assertEqual(t.tm_hour, 23)
2433 self.assertEqual(t.tm_min, 58)
2434 self.assertEqual(t.tm_sec, 37)
2435 self.assertEqual(t.tm_yday, 1)
2436 self.assertEqual(t.tm_isdst, 0)
2437
2438 def test_tzinfo_isoformat(self):
2439 zero = FixedOffset(0, "+00:00")
2440 plus = FixedOffset(220, "+03:40")
2441 minus = FixedOffset(-231, "-03:51")
2442 unknown = FixedOffset(None, "")
2443
2444 cls = self.theclass
2445 datestr = '0001-02-03'
2446 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002447 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002448 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2449 timestr = '04:05:59' + (us and '.987001' or '')
2450 ofsstr = ofs is not None and d.tzname() or ''
2451 tailstr = timestr + ofsstr
2452 iso = d.isoformat()
2453 self.assertEqual(iso, datestr + 'T' + tailstr)
2454 self.assertEqual(iso, d.isoformat('T'))
2455 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2456 self.assertEqual(str(d), datestr + ' ' + tailstr)
2457
Tim Peters12bf3392002-12-24 05:41:27 +00002458 def test_replace(self):
2459 cls = self.theclass
2460 z100 = FixedOffset(100, "+100")
2461 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2462 args = [1, 2, 3, 4, 5, 6, 7, z100]
2463 base = cls(*args)
2464 self.assertEqual(base, base.replace())
2465
2466 i = 0
2467 for name, newval in (("year", 2),
2468 ("month", 3),
2469 ("day", 4),
2470 ("hour", 5),
2471 ("minute", 6),
2472 ("second", 7),
2473 ("microsecond", 8),
2474 ("tzinfo", zm200)):
2475 newargs = args[:]
2476 newargs[i] = newval
2477 expected = cls(*newargs)
2478 got = base.replace(**{name: newval})
2479 self.assertEqual(expected, got)
2480 i += 1
2481
2482 # Ensure we can get rid of a tzinfo.
2483 self.assertEqual(base.tzname(), "+100")
2484 base2 = base.replace(tzinfo=None)
2485 self.failUnless(base2.tzinfo is None)
2486 self.failUnless(base2.tzname() is None)
2487
2488 # Ensure we can add one.
2489 base3 = base2.replace(tzinfo=z100)
2490 self.assertEqual(base, base3)
2491 self.failUnless(base.tzinfo is base3.tzinfo)
2492
2493 # Out of bounds.
2494 base = cls(2000, 2, 29)
2495 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002496
Tim Peters80475bb2002-12-25 07:40:55 +00002497 def test_more_astimezone(self):
2498 # The inherited test_astimezone covered some trivial and error cases.
2499 fnone = FixedOffset(None, "None")
2500 f44m = FixedOffset(44, "44")
2501 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2502
Tim Peters10cadce2003-01-23 19:58:02 +00002503 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002504 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002505 # Replacing with degenerate tzinfo raises an exception.
2506 self.assertRaises(ValueError, dt.astimezone, fnone)
2507 # Ditto with None tz.
2508 self.assertRaises(TypeError, dt.astimezone, None)
2509 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002510 x = dt.astimezone(dt.tzinfo)
2511 self.failUnless(x.tzinfo is f44m)
2512 self.assertEqual(x.date(), dt.date())
2513 self.assertEqual(x.time(), dt.time())
2514
2515 # Replacing with different tzinfo does adjust.
2516 got = dt.astimezone(fm5h)
2517 self.failUnless(got.tzinfo is fm5h)
2518 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2519 expected = dt - dt.utcoffset() # in effect, convert to UTC
2520 expected += fm5h.utcoffset(dt) # and from there to local time
2521 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2522 self.assertEqual(got.date(), expected.date())
2523 self.assertEqual(got.time(), expected.time())
2524 self.assertEqual(got.timetz(), expected.timetz())
2525 self.failUnless(got.tzinfo is expected.tzinfo)
2526 self.assertEqual(got, expected)
2527
Tim Peters4c0db782002-12-26 05:01:19 +00002528 def test_aware_subtract(self):
2529 cls = self.theclass
2530
Tim Peters60c76e42002-12-27 00:41:11 +00002531 # Ensure that utcoffset() is ignored when the operands have the
2532 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002533 class OperandDependentOffset(tzinfo):
2534 def utcoffset(self, t):
2535 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002536 # d0 and d1 equal after adjustment
2537 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002538 else:
Tim Peters397301e2003-01-02 21:28:08 +00002539 # d2 off in the weeds
2540 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002541
2542 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2543 d0 = base.replace(minute=3)
2544 d1 = base.replace(minute=9)
2545 d2 = base.replace(minute=11)
2546 for x in d0, d1, d2:
2547 for y in d0, d1, d2:
2548 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002549 expected = timedelta(minutes=x.minute - y.minute)
2550 self.assertEqual(got, expected)
2551
2552 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2553 # ignored.
2554 base = cls(8, 9, 10, 11, 12, 13, 14)
2555 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2556 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2557 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2558 for x in d0, d1, d2:
2559 for y in d0, d1, d2:
2560 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002561 if (x is d0 or x is d1) and (y is d0 or y is d1):
2562 expected = timedelta(0)
2563 elif x is y is d2:
2564 expected = timedelta(0)
2565 elif x is d2:
2566 expected = timedelta(minutes=(11-59)-0)
2567 else:
2568 assert y is d2
2569 expected = timedelta(minutes=0-(11-59))
2570 self.assertEqual(got, expected)
2571
Tim Peters60c76e42002-12-27 00:41:11 +00002572 def test_mixed_compare(self):
2573 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002574 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002575 self.assertEqual(t1, t2)
2576 t2 = t2.replace(tzinfo=None)
2577 self.assertEqual(t1, t2)
2578 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2579 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002580 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2581 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002582
Tim Peters0bf60bd2003-01-08 20:40:01 +00002583 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002584 class Varies(tzinfo):
2585 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002586 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002587 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002588 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002589 return self.offset
2590
2591 v = Varies()
2592 t1 = t2.replace(tzinfo=v)
2593 t2 = t2.replace(tzinfo=v)
2594 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2595 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2596 self.assertEqual(t1, t2)
2597
2598 # But if they're not identical, it isn't ignored.
2599 t2 = t2.replace(tzinfo=Varies())
2600 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002601
Tim Peters621818b2002-12-29 23:44:49 +00002602# Pain to set up DST-aware tzinfo classes.
2603
2604def first_sunday_on_or_after(dt):
2605 days_to_go = 6 - dt.weekday()
2606 if days_to_go:
2607 dt += timedelta(days_to_go)
2608 return dt
2609
2610ZERO = timedelta(0)
2611HOUR = timedelta(hours=1)
2612DAY = timedelta(days=1)
2613# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2614DSTSTART = datetime(1, 4, 1, 2)
2615# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002616# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2617# being standard time on that day, there is no spelling in local time of
2618# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2619DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002620
2621class USTimeZone(tzinfo):
2622
2623 def __init__(self, hours, reprname, stdname, dstname):
2624 self.stdoffset = timedelta(hours=hours)
2625 self.reprname = reprname
2626 self.stdname = stdname
2627 self.dstname = dstname
2628
2629 def __repr__(self):
2630 return self.reprname
2631
2632 def tzname(self, dt):
2633 if self.dst(dt):
2634 return self.dstname
2635 else:
2636 return self.stdname
2637
2638 def utcoffset(self, dt):
2639 return self.stdoffset + self.dst(dt)
2640
2641 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002642 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002643 # An exception instead may be sensible here, in one or more of
2644 # the cases.
2645 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002646 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002647
2648 # Find first Sunday in April.
2649 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2650 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2651
2652 # Find last Sunday in October.
2653 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2654 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2655
Tim Peters621818b2002-12-29 23:44:49 +00002656 # Can't compare naive to aware objects, so strip the timezone from
2657 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002658 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002659 return HOUR
2660 else:
2661 return ZERO
2662
Tim Peters521fc152002-12-31 17:36:56 +00002663Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2664Central = USTimeZone(-6, "Central", "CST", "CDT")
2665Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2666Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002667utc_real = FixedOffset(0, "UTC", 0)
2668# For better test coverage, we want another flavor of UTC that's west of
2669# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002670utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002671
2672class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002673 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002674 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002675 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002676
Tim Peters0bf60bd2003-01-08 20:40:01 +00002677 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002678
Tim Peters521fc152002-12-31 17:36:56 +00002679 # Check a time that's inside DST.
2680 def checkinside(self, dt, tz, utc, dston, dstoff):
2681 self.assertEqual(dt.dst(), HOUR)
2682
2683 # Conversion to our own timezone is always an identity.
2684 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002685
2686 asutc = dt.astimezone(utc)
2687 there_and_back = asutc.astimezone(tz)
2688
2689 # Conversion to UTC and back isn't always an identity here,
2690 # because there are redundant spellings (in local time) of
2691 # UTC time when DST begins: the clock jumps from 1:59:59
2692 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2693 # make sense then. The classes above treat 2:MM:SS as
2694 # daylight time then (it's "after 2am"), really an alias
2695 # for 1:MM:SS standard time. The latter form is what
2696 # conversion back from UTC produces.
2697 if dt.date() == dston.date() and dt.hour == 2:
2698 # We're in the redundant hour, and coming back from
2699 # UTC gives the 1:MM:SS standard-time spelling.
2700 self.assertEqual(there_and_back + HOUR, dt)
2701 # Although during was considered to be in daylight
2702 # time, there_and_back is not.
2703 self.assertEqual(there_and_back.dst(), ZERO)
2704 # They're the same times in UTC.
2705 self.assertEqual(there_and_back.astimezone(utc),
2706 dt.astimezone(utc))
2707 else:
2708 # We're not in the redundant hour.
2709 self.assertEqual(dt, there_and_back)
2710
Tim Peters327098a2003-01-20 22:54:38 +00002711 # Because we have a redundant spelling when DST begins, there is
2712 # (unforunately) an hour when DST ends that can't be spelled at all in
2713 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2714 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2715 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2716 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2717 # expressed in local time. Nevertheless, we want conversion back
2718 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002719 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002720 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002721 if dt.date() == dstoff.date() and dt.hour == 0:
2722 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002723 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002724 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2725 nexthour_utc += HOUR
2726 nexthour_tz = nexthour_utc.astimezone(tz)
2727 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002728 else:
Tim Peters327098a2003-01-20 22:54:38 +00002729 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002730
2731 # Check a time that's outside DST.
2732 def checkoutside(self, dt, tz, utc):
2733 self.assertEqual(dt.dst(), ZERO)
2734
2735 # Conversion to our own timezone is always an identity.
2736 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002737
2738 # Converting to UTC and back is an identity too.
2739 asutc = dt.astimezone(utc)
2740 there_and_back = asutc.astimezone(tz)
2741 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002742
Tim Peters1024bf82002-12-30 17:09:40 +00002743 def convert_between_tz_and_utc(self, tz, utc):
2744 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002745 # Because 1:MM on the day DST ends is taken as being standard time,
2746 # there is no spelling in tz for the last hour of daylight time.
2747 # For purposes of the test, the last hour of DST is 0:MM, which is
2748 # taken as being daylight time (and 1:MM is taken as being standard
2749 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002750 dstoff = self.dstoff.replace(tzinfo=tz)
2751 for delta in (timedelta(weeks=13),
2752 DAY,
2753 HOUR,
2754 timedelta(minutes=1),
2755 timedelta(microseconds=1)):
2756
Tim Peters521fc152002-12-31 17:36:56 +00002757 self.checkinside(dston, tz, utc, dston, dstoff)
2758 for during in dston + delta, dstoff - delta:
2759 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002760
Tim Peters521fc152002-12-31 17:36:56 +00002761 self.checkoutside(dstoff, tz, utc)
2762 for outside in dston - delta, dstoff + delta:
2763 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002764
Tim Peters621818b2002-12-29 23:44:49 +00002765 def test_easy(self):
2766 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002767 self.convert_between_tz_and_utc(Eastern, utc_real)
2768 self.convert_between_tz_and_utc(Pacific, utc_real)
2769 self.convert_between_tz_and_utc(Eastern, utc_fake)
2770 self.convert_between_tz_and_utc(Pacific, utc_fake)
2771 # The next is really dancing near the edge. It works because
2772 # Pacific and Eastern are far enough apart that their "problem
2773 # hours" don't overlap.
2774 self.convert_between_tz_and_utc(Eastern, Pacific)
2775 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002776 # OTOH, these fail! Don't enable them. The difficulty is that
2777 # the edge case tests assume that every hour is representable in
2778 # the "utc" class. This is always true for a fixed-offset tzinfo
2779 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2780 # For these adjacent DST-aware time zones, the range of time offsets
2781 # tested ends up creating hours in the one that aren't representable
2782 # in the other. For the same reason, we would see failures in the
2783 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2784 # offset deltas in convert_between_tz_and_utc().
2785 #
2786 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2787 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002788
Tim Petersf3615152003-01-01 21:51:37 +00002789 def test_tricky(self):
2790 # 22:00 on day before daylight starts.
2791 fourback = self.dston - timedelta(hours=4)
2792 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002793 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002794 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2795 # 2", we should get the 3 spelling.
2796 # If we plug 22:00 the day before into Eastern, it "looks like std
2797 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2798 # to 22:00 lands on 2:00, which makes no sense in local time (the
2799 # local clock jumps from 1 to 3). The point here is to make sure we
2800 # get the 3 spelling.
2801 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002802 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002803 self.assertEqual(expected, got)
2804
2805 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2806 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002807 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002808 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2809 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2810 # spelling.
2811 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002812 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002813 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002814
Tim Petersadf64202003-01-04 06:03:15 +00002815 # Now on the day DST ends, we want "repeat an hour" behavior.
2816 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2817 # EST 23:MM 0:MM 1:MM 2:MM
2818 # EDT 0:MM 1:MM 2:MM 3:MM
2819 # wall 0:MM 1:MM 1:MM 2:MM against these
2820 for utc in utc_real, utc_fake:
2821 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002822 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002823 # Convert that to UTC.
2824 first_std_hour -= tz.utcoffset(None)
2825 # Adjust for possibly fake UTC.
2826 asutc = first_std_hour + utc.utcoffset(None)
2827 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2828 # tz=Eastern.
2829 asutcbase = asutc.replace(tzinfo=utc)
2830 for tzhour in (0, 1, 1, 2):
2831 expectedbase = self.dstoff.replace(hour=tzhour)
2832 for minute in 0, 30, 59:
2833 expected = expectedbase.replace(minute=minute)
2834 asutc = asutcbase.replace(minute=minute)
2835 astz = asutc.astimezone(tz)
2836 self.assertEqual(astz.replace(tzinfo=None), expected)
2837 asutcbase += HOUR
2838
2839
Tim Peters710fb152003-01-02 19:35:54 +00002840 def test_bogus_dst(self):
2841 class ok(tzinfo):
2842 def utcoffset(self, dt): return HOUR
2843 def dst(self, dt): return HOUR
2844
2845 now = self.theclass.now().replace(tzinfo=utc_real)
2846 # Doesn't blow up.
2847 now.astimezone(ok())
2848
2849 # Does blow up.
2850 class notok(ok):
2851 def dst(self, dt): return None
2852 self.assertRaises(ValueError, now.astimezone, notok())
2853
Tim Peters52dcce22003-01-23 16:36:11 +00002854 def test_fromutc(self):
2855 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
2856 now = datetime.utcnow().replace(tzinfo=utc_real)
2857 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
2858 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
2859 enow = Eastern.fromutc(now) # doesn't blow up
2860 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
2861 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
2862 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
2863
2864 # Always converts UTC to standard time.
2865 class FauxUSTimeZone(USTimeZone):
2866 def fromutc(self, dt):
2867 return dt + self.stdoffset
2868 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
2869
2870 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
2871 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
2872 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
2873
2874 # Check around DST start.
2875 start = self.dston.replace(hour=4, tzinfo=Eastern)
2876 fstart = start.replace(tzinfo=FEastern)
2877 for wall in 23, 0, 1, 3, 4, 5:
2878 expected = start.replace(hour=wall)
2879 if wall == 23:
2880 expected -= timedelta(days=1)
2881 got = Eastern.fromutc(start)
2882 self.assertEqual(expected, got)
2883
2884 expected = fstart + FEastern.stdoffset
2885 got = FEastern.fromutc(fstart)
2886 self.assertEqual(expected, got)
2887
2888 # Ensure astimezone() calls fromutc() too.
2889 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2890 self.assertEqual(expected, got)
2891
2892 start += HOUR
2893 fstart += HOUR
2894
2895 # Check around DST end.
2896 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
2897 fstart = start.replace(tzinfo=FEastern)
2898 for wall in 0, 1, 1, 2, 3, 4:
2899 expected = start.replace(hour=wall)
2900 got = Eastern.fromutc(start)
2901 self.assertEqual(expected, got)
2902
2903 expected = fstart + FEastern.stdoffset
2904 got = FEastern.fromutc(fstart)
2905 self.assertEqual(expected, got)
2906
2907 # Ensure astimezone() calls fromutc() too.
2908 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
2909 self.assertEqual(expected, got)
2910
2911 start += HOUR
2912 fstart += HOUR
2913
Tim Peters710fb152003-01-02 19:35:54 +00002914
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002915def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00002916 allsuites = [unittest.makeSuite(klass, 'test')
2917 for klass in (TestModule,
2918 TestTZInfo,
2919 TestTimeDelta,
2920 TestDateOnly,
2921 TestDate,
2922 TestDateTime,
2923 TestTime,
2924 TestTimeTZ,
2925 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00002926 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00002927 )
2928 ]
2929 return unittest.TestSuite(allsuites)
2930
2931def test_main():
2932 import gc
2933 import sys
2934
Tim Peterscfd4a8b2002-12-16 21:12:37 +00002935 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00002936 lastrc = None
2937 while True:
2938 test_support.run_suite(thesuite)
2939 if 1: # change to 0, under a debug build, for some leak detection
2940 break
2941 gc.collect()
2942 if gc.garbage:
2943 raise SystemError("gc.garbage not empty after test run: %r" %
2944 gc.garbage)
2945 if hasattr(sys, 'gettotalrefcount'):
2946 thisrc = sys.gettotalrefcount()
2947 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
2948 if lastrc:
2949 print >> sys.stderr, 'delta:', thisrc - lastrc
2950 else:
2951 print >> sys.stderr
2952 lastrc = thisrc
2953
2954if __name__ == "__main__":
2955 test_main()