blob: 347b1a9d8643b8587665a3b60c64b2a33770b4e4 [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
Tim Petersb0c854d2003-05-17 15:57:00 +0000446 def test_subclass_timedelta(self):
447
448 class T(timedelta):
449 def from_td(td):
450 return T(td.days, td.seconds, td.microseconds)
451 from_td = staticmethod(from_td)
452
453 def as_hours(self):
454 sum = (self.days * 24 +
455 self.seconds / 3600.0 +
456 self.microseconds / 3600e6)
457 return round(sum)
458
459 t1 = T(days=1)
460 self.assert_(type(t1) is T)
461 self.assertEqual(t1.as_hours(), 24)
462
463 t2 = T(days=-1, seconds=-3600)
464 self.assert_(type(t2) is T)
465 self.assertEqual(t2.as_hours(), -25)
466
467 t3 = t1 + t2
468 self.assert_(type(t3) is timedelta)
469 t4 = T.from_td(t3)
470 self.assert_(type(t4) is T)
471 self.assertEqual(t3.days, t4.days)
472 self.assertEqual(t3.seconds, t4.seconds)
473 self.assertEqual(t3.microseconds, t4.microseconds)
474 self.assertEqual(str(t3), str(t4))
475 self.assertEqual(t4.as_hours(), -1)
476
Tim Peters2a799bf2002-12-16 20:18:38 +0000477#############################################################################
478# date tests
479
480class TestDateOnly(unittest.TestCase):
481 # Tests here won't pass if also run on datetime objects, so don't
482 # subclass this to test datetimes too.
483
484 def test_delta_non_days_ignored(self):
485 dt = date(2000, 1, 2)
486 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
487 microseconds=5)
488 days = timedelta(delta.days)
489 self.assertEqual(days, timedelta(1))
490
491 dt2 = dt + delta
492 self.assertEqual(dt2, dt + days)
493
494 dt2 = delta + dt
495 self.assertEqual(dt2, dt + days)
496
497 dt2 = dt - delta
498 self.assertEqual(dt2, dt - days)
499
500 delta = -delta
501 days = timedelta(delta.days)
502 self.assertEqual(days, timedelta(-2))
503
504 dt2 = dt + delta
505 self.assertEqual(dt2, dt + days)
506
507 dt2 = delta + dt
508 self.assertEqual(dt2, dt + days)
509
510 dt2 = dt - delta
511 self.assertEqual(dt2, dt - days)
512
Tim Peters07534a62003-02-07 22:50:28 +0000513class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000514 # Tests here should pass for both dates and datetimes, except for a
515 # few tests that TestDateTime overrides.
516
517 theclass = date
518
519 def test_basic_attributes(self):
520 dt = self.theclass(2002, 3, 1)
521 self.assertEqual(dt.year, 2002)
522 self.assertEqual(dt.month, 3)
523 self.assertEqual(dt.day, 1)
524
525 def test_roundtrip(self):
526 for dt in (self.theclass(1, 2, 3),
527 self.theclass.today()):
528 # Verify dt -> string -> date identity.
529 s = repr(dt)
530 self.failUnless(s.startswith('datetime.'))
531 s = s[9:]
532 dt2 = eval(s)
533 self.assertEqual(dt, dt2)
534
535 # Verify identity via reconstructing from pieces.
536 dt2 = self.theclass(dt.year, dt.month, dt.day)
537 self.assertEqual(dt, dt2)
538
539 def test_ordinal_conversions(self):
540 # Check some fixed values.
541 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
542 (1, 12, 31, 365),
543 (2, 1, 1, 366),
544 # first example from "Calendrical Calculations"
545 (1945, 11, 12, 710347)]:
546 d = self.theclass(y, m, d)
547 self.assertEqual(n, d.toordinal())
548 fromord = self.theclass.fromordinal(n)
549 self.assertEqual(d, fromord)
550 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000551 # if we're checking something fancier than a date, verify
552 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000553 self.assertEqual(fromord.hour, 0)
554 self.assertEqual(fromord.minute, 0)
555 self.assertEqual(fromord.second, 0)
556 self.assertEqual(fromord.microsecond, 0)
557
Tim Peters0bf60bd2003-01-08 20:40:01 +0000558 # Check first and last days of year spottily across the whole
559 # range of years supported.
560 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000561 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
562 d = self.theclass(year, 1, 1)
563 n = d.toordinal()
564 d2 = self.theclass.fromordinal(n)
565 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000566 # Verify that moving back a day gets to the end of year-1.
567 if year > 1:
568 d = self.theclass.fromordinal(n-1)
569 d2 = self.theclass(year-1, 12, 31)
570 self.assertEqual(d, d2)
571 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000572
573 # Test every day in a leap-year and a non-leap year.
574 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
575 for year, isleap in (2000, True), (2002, False):
576 n = self.theclass(year, 1, 1).toordinal()
577 for month, maxday in zip(range(1, 13), dim):
578 if month == 2 and isleap:
579 maxday += 1
580 for day in range(1, maxday+1):
581 d = self.theclass(year, month, day)
582 self.assertEqual(d.toordinal(), n)
583 self.assertEqual(d, self.theclass.fromordinal(n))
584 n += 1
585
586 def test_extreme_ordinals(self):
587 a = self.theclass.min
588 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
589 aord = a.toordinal()
590 b = a.fromordinal(aord)
591 self.assertEqual(a, b)
592
593 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
594
595 b = a + timedelta(days=1)
596 self.assertEqual(b.toordinal(), aord + 1)
597 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
598
599 a = self.theclass.max
600 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
601 aord = a.toordinal()
602 b = a.fromordinal(aord)
603 self.assertEqual(a, b)
604
605 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
606
607 b = a - timedelta(days=1)
608 self.assertEqual(b.toordinal(), aord - 1)
609 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
610
611 def test_bad_constructor_arguments(self):
612 # bad years
613 self.theclass(MINYEAR, 1, 1) # no exception
614 self.theclass(MAXYEAR, 1, 1) # no exception
615 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
616 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
617 # bad months
618 self.theclass(2000, 1, 1) # no exception
619 self.theclass(2000, 12, 1) # no exception
620 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
621 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
622 # bad days
623 self.theclass(2000, 2, 29) # no exception
624 self.theclass(2004, 2, 29) # no exception
625 self.theclass(2400, 2, 29) # no exception
626 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
627 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
628 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
629 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
630 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
631 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
632
633 def test_hash_equality(self):
634 d = self.theclass(2000, 12, 31)
635 # same thing
636 e = self.theclass(2000, 12, 31)
637 self.assertEqual(d, e)
638 self.assertEqual(hash(d), hash(e))
639
640 dic = {d: 1}
641 dic[e] = 2
642 self.assertEqual(len(dic), 1)
643 self.assertEqual(dic[d], 2)
644 self.assertEqual(dic[e], 2)
645
646 d = self.theclass(2001, 1, 1)
647 # same thing
648 e = self.theclass(2001, 1, 1)
649 self.assertEqual(d, e)
650 self.assertEqual(hash(d), hash(e))
651
652 dic = {d: 1}
653 dic[e] = 2
654 self.assertEqual(len(dic), 1)
655 self.assertEqual(dic[d], 2)
656 self.assertEqual(dic[e], 2)
657
658 def test_computations(self):
659 a = self.theclass(2002, 1, 31)
660 b = self.theclass(1956, 1, 31)
661
662 diff = a-b
663 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
664 self.assertEqual(diff.seconds, 0)
665 self.assertEqual(diff.microseconds, 0)
666
667 day = timedelta(1)
668 week = timedelta(7)
669 a = self.theclass(2002, 3, 2)
670 self.assertEqual(a + day, self.theclass(2002, 3, 3))
671 self.assertEqual(day + a, self.theclass(2002, 3, 3))
672 self.assertEqual(a - day, self.theclass(2002, 3, 1))
673 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
674 self.assertEqual(a + week, self.theclass(2002, 3, 9))
675 self.assertEqual(a - week, self.theclass(2002, 2, 23))
676 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
677 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
678 self.assertEqual((a + week) - a, week)
679 self.assertEqual((a + day) - a, day)
680 self.assertEqual((a - week) - a, -week)
681 self.assertEqual((a - day) - a, -day)
682 self.assertEqual(a - (a + week), -week)
683 self.assertEqual(a - (a + day), -day)
684 self.assertEqual(a - (a - week), week)
685 self.assertEqual(a - (a - day), day)
686
687 # Add/sub ints, longs, floats should be illegal
688 for i in 1, 1L, 1.0:
689 self.assertRaises(TypeError, lambda: a+i)
690 self.assertRaises(TypeError, lambda: a-i)
691 self.assertRaises(TypeError, lambda: i+a)
692 self.assertRaises(TypeError, lambda: i-a)
693
694 # delta - date is senseless.
695 self.assertRaises(TypeError, lambda: day - a)
696 # mixing date and (delta or date) via * or // is senseless
697 self.assertRaises(TypeError, lambda: day * a)
698 self.assertRaises(TypeError, lambda: a * day)
699 self.assertRaises(TypeError, lambda: day // a)
700 self.assertRaises(TypeError, lambda: a // day)
701 self.assertRaises(TypeError, lambda: a * a)
702 self.assertRaises(TypeError, lambda: a // a)
703 # date + date is senseless
704 self.assertRaises(TypeError, lambda: a + a)
705
706 def test_overflow(self):
707 tiny = self.theclass.resolution
708
709 dt = self.theclass.min + tiny
710 dt -= tiny # no problem
711 self.assertRaises(OverflowError, dt.__sub__, tiny)
712 self.assertRaises(OverflowError, dt.__add__, -tiny)
713
714 dt = self.theclass.max - tiny
715 dt += tiny # no problem
716 self.assertRaises(OverflowError, dt.__add__, tiny)
717 self.assertRaises(OverflowError, dt.__sub__, -tiny)
718
719 def test_fromtimestamp(self):
720 import time
721
722 # Try an arbitrary fixed value.
723 year, month, day = 1999, 9, 19
724 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
725 d = self.theclass.fromtimestamp(ts)
726 self.assertEqual(d.year, year)
727 self.assertEqual(d.month, month)
728 self.assertEqual(d.day, day)
729
730 def test_today(self):
731 import time
732
733 # We claim that today() is like fromtimestamp(time.time()), so
734 # prove it.
735 for dummy in range(3):
736 today = self.theclass.today()
737 ts = time.time()
738 todayagain = self.theclass.fromtimestamp(ts)
739 if today == todayagain:
740 break
741 # There are several legit reasons that could fail:
742 # 1. It recently became midnight, between the today() and the
743 # time() calls.
744 # 2. The platform time() has such fine resolution that we'll
745 # never get the same value twice.
746 # 3. The platform time() has poor resolution, and we just
747 # happened to call today() right before a resolution quantum
748 # boundary.
749 # 4. The system clock got fiddled between calls.
750 # In any case, wait a little while and try again.
751 time.sleep(0.1)
752
753 # It worked or it didn't. If it didn't, assume it's reason #2, and
754 # let the test pass if they're within half a second of each other.
755 self.failUnless(today == todayagain or
756 abs(todayagain - today) < timedelta(seconds=0.5))
757
758 def test_weekday(self):
759 for i in range(7):
760 # March 4, 2002 is a Monday
761 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
762 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
763 # January 2, 1956 is a Monday
764 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
765 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
766
767 def test_isocalendar(self):
768 # Check examples from
769 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
770 for i in range(7):
771 d = self.theclass(2003, 12, 22+i)
772 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
773 d = self.theclass(2003, 12, 29) + timedelta(i)
774 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
775 d = self.theclass(2004, 1, 5+i)
776 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
777 d = self.theclass(2009, 12, 21+i)
778 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
779 d = self.theclass(2009, 12, 28) + timedelta(i)
780 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
781 d = self.theclass(2010, 1, 4+i)
782 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
783
784 def test_iso_long_years(self):
785 # Calculate long ISO years and compare to table from
786 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
787 ISO_LONG_YEARS_TABLE = """
788 4 32 60 88
789 9 37 65 93
790 15 43 71 99
791 20 48 76
792 26 54 82
793
794 105 133 161 189
795 111 139 167 195
796 116 144 172
797 122 150 178
798 128 156 184
799
800 201 229 257 285
801 207 235 263 291
802 212 240 268 296
803 218 246 274
804 224 252 280
805
806 303 331 359 387
807 308 336 364 392
808 314 342 370 398
809 320 348 376
810 325 353 381
811 """
812 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
813 iso_long_years.sort()
814 L = []
815 for i in range(400):
816 d = self.theclass(2000+i, 12, 31)
817 d1 = self.theclass(1600+i, 12, 31)
818 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
819 if d.isocalendar()[1] == 53:
820 L.append(i)
821 self.assertEqual(L, iso_long_years)
822
823 def test_isoformat(self):
824 t = self.theclass(2, 3, 2)
825 self.assertEqual(t.isoformat(), "0002-03-02")
826
827 def test_ctime(self):
828 t = self.theclass(2002, 3, 2)
829 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
830
831 def test_strftime(self):
832 t = self.theclass(2005, 3, 2)
833 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000834 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000835
836 self.assertRaises(TypeError, t.strftime) # needs an arg
837 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
838 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
839
840 # A naive object replaces %z and %Z w/ empty strings.
841 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
842
843 def test_resolution_info(self):
844 self.assert_(isinstance(self.theclass.min, self.theclass))
845 self.assert_(isinstance(self.theclass.max, self.theclass))
846 self.assert_(isinstance(self.theclass.resolution, timedelta))
847 self.assert_(self.theclass.max > self.theclass.min)
848
849 def test_extreme_timedelta(self):
850 big = self.theclass.max - self.theclass.min
851 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
852 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
853 # n == 315537897599999999 ~= 2**58.13
854 justasbig = timedelta(0, 0, n)
855 self.assertEqual(big, justasbig)
856 self.assertEqual(self.theclass.min + big, self.theclass.max)
857 self.assertEqual(self.theclass.max - big, self.theclass.min)
858
859 def test_timetuple(self):
860 for i in range(7):
861 # January 2, 1956 is a Monday (0)
862 d = self.theclass(1956, 1, 2+i)
863 t = d.timetuple()
864 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
865 # February 1, 1956 is a Wednesday (2)
866 d = self.theclass(1956, 2, 1+i)
867 t = d.timetuple()
868 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
869 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
870 # of the year.
871 d = self.theclass(1956, 3, 1+i)
872 t = d.timetuple()
873 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
874 self.assertEqual(t.tm_year, 1956)
875 self.assertEqual(t.tm_mon, 3)
876 self.assertEqual(t.tm_mday, 1+i)
877 self.assertEqual(t.tm_hour, 0)
878 self.assertEqual(t.tm_min, 0)
879 self.assertEqual(t.tm_sec, 0)
880 self.assertEqual(t.tm_wday, (3+i)%7)
881 self.assertEqual(t.tm_yday, 61+i)
882 self.assertEqual(t.tm_isdst, -1)
883
884 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000885 args = 6, 7, 23
886 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000887 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000888 green = pickler.dumps(orig, proto)
889 derived = unpickler.loads(green)
890 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000891
892 def test_compare(self):
893 t1 = self.theclass(2, 3, 4)
894 t2 = self.theclass(2, 3, 4)
895 self.failUnless(t1 == t2)
896 self.failUnless(t1 <= t2)
897 self.failUnless(t1 >= t2)
898 self.failUnless(not t1 != t2)
899 self.failUnless(not t1 < t2)
900 self.failUnless(not t1 > t2)
901 self.assertEqual(cmp(t1, t2), 0)
902 self.assertEqual(cmp(t2, t1), 0)
903
904 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
905 t2 = self.theclass(*args) # this is larger than t1
906 self.failUnless(t1 < t2)
907 self.failUnless(t2 > t1)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t2 >= t1)
910 self.failUnless(t1 != t2)
911 self.failUnless(t2 != t1)
912 self.failUnless(not t1 == t2)
913 self.failUnless(not t2 == t1)
914 self.failUnless(not t1 > t2)
915 self.failUnless(not t2 < t1)
916 self.failUnless(not t1 >= t2)
917 self.failUnless(not t2 <= t1)
918 self.assertEqual(cmp(t1, t2), -1)
919 self.assertEqual(cmp(t2, t1), 1)
920
Tim Peters68124bb2003-02-08 03:46:31 +0000921 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000922 self.assertEqual(t1 == badarg, False)
923 self.assertEqual(t1 != badarg, True)
924 self.assertEqual(badarg == t1, False)
925 self.assertEqual(badarg != t1, True)
926
Tim Peters2a799bf2002-12-16 20:18:38 +0000927 self.assertRaises(TypeError, lambda: t1 < badarg)
928 self.assertRaises(TypeError, lambda: t1 > badarg)
929 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000930 self.assertRaises(TypeError, lambda: badarg <= t1)
931 self.assertRaises(TypeError, lambda: badarg < t1)
932 self.assertRaises(TypeError, lambda: badarg > t1)
933 self.assertRaises(TypeError, lambda: badarg >= t1)
934
Tim Peters8d81a012003-01-24 22:36:34 +0000935 def test_mixed_compare(self):
936 our = self.theclass(2000, 4, 5)
937 self.assertRaises(TypeError, cmp, our, 1)
938 self.assertRaises(TypeError, cmp, 1, our)
939
940 class AnotherDateTimeClass(object):
941 def __cmp__(self, other):
942 # Return "equal" so calling this can't be confused with
943 # compare-by-address (which never says "equal" for distinct
944 # objects).
945 return 0
946
947 # This still errors, because date and datetime comparison raise
948 # TypeError instead of NotImplemented when they don't know what to
949 # do, in order to stop comparison from falling back to the default
950 # compare-by-address.
951 their = AnotherDateTimeClass()
952 self.assertRaises(TypeError, cmp, our, their)
953 # Oops: The next stab raises TypeError in the C implementation,
954 # but not in the Python implementation of datetime. The difference
955 # is due to that the Python implementation defines __cmp__ but
956 # the C implementation defines tp_richcompare. This is more pain
957 # to fix than it's worth, so commenting out the test.
958 # self.assertEqual(cmp(their, our), 0)
959
960 # But date and datetime comparison return NotImplemented instead if the
961 # other object has a timetuple attr. This gives the other object a
962 # chance to do the comparison.
963 class Comparable(AnotherDateTimeClass):
964 def timetuple(self):
965 return ()
966
967 their = Comparable()
968 self.assertEqual(cmp(our, their), 0)
969 self.assertEqual(cmp(their, our), 0)
970 self.failUnless(our == their)
971 self.failUnless(their == our)
972
Tim Peters2a799bf2002-12-16 20:18:38 +0000973 def test_bool(self):
974 # All dates are considered true.
975 self.failUnless(self.theclass.min)
976 self.failUnless(self.theclass.max)
977
Tim Petersd6844152002-12-22 20:58:42 +0000978 def test_srftime_out_of_range(self):
979 # For nasty technical reasons, we can't handle years before 1900.
980 cls = self.theclass
981 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
982 for y in 1, 49, 51, 99, 100, 1000, 1899:
983 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000984
985 def test_replace(self):
986 cls = self.theclass
987 args = [1, 2, 3]
988 base = cls(*args)
989 self.assertEqual(base, base.replace())
990
991 i = 0
992 for name, newval in (("year", 2),
993 ("month", 3),
994 ("day", 4)):
995 newargs = args[:]
996 newargs[i] = newval
997 expected = cls(*newargs)
998 got = base.replace(**{name: newval})
999 self.assertEqual(expected, got)
1000 i += 1
1001
1002 # Out of bounds.
1003 base = cls(2000, 2, 29)
1004 self.assertRaises(ValueError, base.replace, year=2001)
1005
Tim Petersa98924a2003-05-17 05:55:19 +00001006 def test_subclass_date(self):
1007
1008 class C(self.theclass):
1009 theAnswer = 42
1010
1011 def __new__(cls, *args, **kws):
1012 temp = kws.copy()
1013 extra = temp.pop('extra')
1014 result = self.theclass.__new__(cls, *args, **temp)
1015 result.extra = extra
1016 return result
1017
1018 def newmeth(self, start):
1019 return start + self.year + self.month
1020
1021 args = 2003, 4, 14
1022
1023 dt1 = self.theclass(*args)
1024 dt2 = C(*args, **{'extra': 7})
1025
1026 self.assertEqual(dt2.__class__, C)
1027 self.assertEqual(dt2.theAnswer, 42)
1028 self.assertEqual(dt2.extra, 7)
1029 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1030 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1031
Tim Peters3f606292004-03-21 23:38:41 +00001032 def test_backdoor_resistance(self):
1033 # For fast unpickling, the constructor accepts a pickle string.
1034 # This is a low-overhead backdoor. A user can (by intent or
1035 # mistake) pass a string directly, which (if it's the right length)
1036 # will get treated like a pickle, and bypass the normal sanity
1037 # checks in the constructor. This can create insane objects.
1038 # The constructor doesn't want to burn the time to validate all
1039 # fields, but does check the month field. This stops, e.g.,
1040 # datetime.datetime('1995-03-25') from yielding an insane object.
1041 base = '1995-03-25'
1042 if not issubclass(self.theclass, datetime):
1043 base = base[:4]
1044 for month_byte in '9', chr(0), chr(13), '\xff':
1045 self.assertRaises(TypeError, self.theclass,
1046 base[:2] + month_byte + base[3:])
1047 for ord_byte in range(1, 13):
1048 # This shouldn't blow up because of the month byte alone. If
1049 # the implementation changes to do more-careful checking, it may
1050 # blow up because other fields are insane.
1051 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001052
Tim Peters2a799bf2002-12-16 20:18:38 +00001053#############################################################################
1054# datetime tests
1055
1056class TestDateTime(TestDate):
1057
1058 theclass = datetime
1059
1060 def test_basic_attributes(self):
1061 dt = self.theclass(2002, 3, 1, 12, 0)
1062 self.assertEqual(dt.year, 2002)
1063 self.assertEqual(dt.month, 3)
1064 self.assertEqual(dt.day, 1)
1065 self.assertEqual(dt.hour, 12)
1066 self.assertEqual(dt.minute, 0)
1067 self.assertEqual(dt.second, 0)
1068 self.assertEqual(dt.microsecond, 0)
1069
1070 def test_basic_attributes_nonzero(self):
1071 # Make sure all attributes are non-zero so bugs in
1072 # bit-shifting access show up.
1073 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1074 self.assertEqual(dt.year, 2002)
1075 self.assertEqual(dt.month, 3)
1076 self.assertEqual(dt.day, 1)
1077 self.assertEqual(dt.hour, 12)
1078 self.assertEqual(dt.minute, 59)
1079 self.assertEqual(dt.second, 59)
1080 self.assertEqual(dt.microsecond, 8000)
1081
1082 def test_roundtrip(self):
1083 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1084 self.theclass.now()):
1085 # Verify dt -> string -> datetime identity.
1086 s = repr(dt)
1087 self.failUnless(s.startswith('datetime.'))
1088 s = s[9:]
1089 dt2 = eval(s)
1090 self.assertEqual(dt, dt2)
1091
1092 # Verify identity via reconstructing from pieces.
1093 dt2 = self.theclass(dt.year, dt.month, dt.day,
1094 dt.hour, dt.minute, dt.second,
1095 dt.microsecond)
1096 self.assertEqual(dt, dt2)
1097
1098 def test_isoformat(self):
1099 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1100 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1101 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1102 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1103 # str is ISO format with the separator forced to a blank.
1104 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1105
1106 t = self.theclass(2, 3, 2)
1107 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1108 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1109 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1110 # str is ISO format with the separator forced to a blank.
1111 self.assertEqual(str(t), "0002-03-02 00:00:00")
1112
1113 def test_more_ctime(self):
1114 # Test fields that TestDate doesn't touch.
1115 import time
1116
1117 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1118 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1119 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1120 # out. The difference is that t.ctime() produces " 2" for the day,
1121 # but platform ctime() produces "02" for the day. According to
1122 # C99, t.ctime() is correct here.
1123 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1124
1125 # So test a case where that difference doesn't matter.
1126 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1127 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1128
1129 def test_tz_independent_comparing(self):
1130 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1131 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1132 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1133 self.assertEqual(dt1, dt3)
1134 self.assert_(dt2 > dt3)
1135
1136 # Make sure comparison doesn't forget microseconds, and isn't done
1137 # via comparing a float timestamp (an IEEE double doesn't have enough
1138 # precision to span microsecond resolution across years 1 thru 9999,
1139 # so comparing via timestamp necessarily calls some distinct values
1140 # equal).
1141 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1142 us = timedelta(microseconds=1)
1143 dt2 = dt1 + us
1144 self.assertEqual(dt2 - dt1, us)
1145 self.assert_(dt1 < dt2)
1146
1147 def test_bad_constructor_arguments(self):
1148 # bad years
1149 self.theclass(MINYEAR, 1, 1) # no exception
1150 self.theclass(MAXYEAR, 1, 1) # no exception
1151 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1152 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1153 # bad months
1154 self.theclass(2000, 1, 1) # no exception
1155 self.theclass(2000, 12, 1) # no exception
1156 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1157 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1158 # bad days
1159 self.theclass(2000, 2, 29) # no exception
1160 self.theclass(2004, 2, 29) # no exception
1161 self.theclass(2400, 2, 29) # no exception
1162 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1163 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1164 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1165 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1166 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1167 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1168 # bad hours
1169 self.theclass(2000, 1, 31, 0) # no exception
1170 self.theclass(2000, 1, 31, 23) # no exception
1171 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1172 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1173 # bad minutes
1174 self.theclass(2000, 1, 31, 23, 0) # no exception
1175 self.theclass(2000, 1, 31, 23, 59) # no exception
1176 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1177 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1178 # bad seconds
1179 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1180 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1181 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1182 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1183 # bad microseconds
1184 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1185 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1186 self.assertRaises(ValueError, self.theclass,
1187 2000, 1, 31, 23, 59, 59, -1)
1188 self.assertRaises(ValueError, self.theclass,
1189 2000, 1, 31, 23, 59, 59,
1190 1000000)
1191
1192 def test_hash_equality(self):
1193 d = self.theclass(2000, 12, 31, 23, 30, 17)
1194 e = self.theclass(2000, 12, 31, 23, 30, 17)
1195 self.assertEqual(d, e)
1196 self.assertEqual(hash(d), hash(e))
1197
1198 dic = {d: 1}
1199 dic[e] = 2
1200 self.assertEqual(len(dic), 1)
1201 self.assertEqual(dic[d], 2)
1202 self.assertEqual(dic[e], 2)
1203
1204 d = self.theclass(2001, 1, 1, 0, 5, 17)
1205 e = self.theclass(2001, 1, 1, 0, 5, 17)
1206 self.assertEqual(d, e)
1207 self.assertEqual(hash(d), hash(e))
1208
1209 dic = {d: 1}
1210 dic[e] = 2
1211 self.assertEqual(len(dic), 1)
1212 self.assertEqual(dic[d], 2)
1213 self.assertEqual(dic[e], 2)
1214
1215 def test_computations(self):
1216 a = self.theclass(2002, 1, 31)
1217 b = self.theclass(1956, 1, 31)
1218 diff = a-b
1219 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1220 self.assertEqual(diff.seconds, 0)
1221 self.assertEqual(diff.microseconds, 0)
1222 a = self.theclass(2002, 3, 2, 17, 6)
1223 millisec = timedelta(0, 0, 1000)
1224 hour = timedelta(0, 3600)
1225 day = timedelta(1)
1226 week = timedelta(7)
1227 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1228 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1229 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1230 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1231 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1232 self.assertEqual(a - hour, a + -hour)
1233 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1234 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1235 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1236 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1237 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1238 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1239 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1240 self.assertEqual((a + week) - a, week)
1241 self.assertEqual((a + day) - a, day)
1242 self.assertEqual((a + hour) - a, hour)
1243 self.assertEqual((a + millisec) - a, millisec)
1244 self.assertEqual((a - week) - a, -week)
1245 self.assertEqual((a - day) - a, -day)
1246 self.assertEqual((a - hour) - a, -hour)
1247 self.assertEqual((a - millisec) - a, -millisec)
1248 self.assertEqual(a - (a + week), -week)
1249 self.assertEqual(a - (a + day), -day)
1250 self.assertEqual(a - (a + hour), -hour)
1251 self.assertEqual(a - (a + millisec), -millisec)
1252 self.assertEqual(a - (a - week), week)
1253 self.assertEqual(a - (a - day), day)
1254 self.assertEqual(a - (a - hour), hour)
1255 self.assertEqual(a - (a - millisec), millisec)
1256 self.assertEqual(a + (week + day + hour + millisec),
1257 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1258 self.assertEqual(a + (week + day + hour + millisec),
1259 (((a + week) + day) + hour) + millisec)
1260 self.assertEqual(a - (week + day + hour + millisec),
1261 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1262 self.assertEqual(a - (week + day + hour + millisec),
1263 (((a - week) - day) - hour) - millisec)
1264 # Add/sub ints, longs, floats should be illegal
1265 for i in 1, 1L, 1.0:
1266 self.assertRaises(TypeError, lambda: a+i)
1267 self.assertRaises(TypeError, lambda: a-i)
1268 self.assertRaises(TypeError, lambda: i+a)
1269 self.assertRaises(TypeError, lambda: i-a)
1270
1271 # delta - datetime is senseless.
1272 self.assertRaises(TypeError, lambda: day - a)
1273 # mixing datetime and (delta or datetime) via * or // is senseless
1274 self.assertRaises(TypeError, lambda: day * a)
1275 self.assertRaises(TypeError, lambda: a * day)
1276 self.assertRaises(TypeError, lambda: day // a)
1277 self.assertRaises(TypeError, lambda: a // day)
1278 self.assertRaises(TypeError, lambda: a * a)
1279 self.assertRaises(TypeError, lambda: a // a)
1280 # datetime + datetime is senseless
1281 self.assertRaises(TypeError, lambda: a + a)
1282
1283 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001284 args = 6, 7, 23, 20, 59, 1, 64**2
1285 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001286 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001287 green = pickler.dumps(orig, proto)
1288 derived = unpickler.loads(green)
1289 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001290
Guido van Rossum275666f2003-02-07 21:49:01 +00001291 def test_more_pickling(self):
1292 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1293 s = pickle.dumps(a)
1294 b = pickle.loads(s)
1295 self.assertEqual(b.year, 2003)
1296 self.assertEqual(b.month, 2)
1297 self.assertEqual(b.day, 7)
1298
Tim Peters2a799bf2002-12-16 20:18:38 +00001299 def test_more_compare(self):
1300 # The test_compare() inherited from TestDate covers the error cases.
1301 # We just want to test lexicographic ordering on the members datetime
1302 # has that date lacks.
1303 args = [2000, 11, 29, 20, 58, 16, 999998]
1304 t1 = self.theclass(*args)
1305 t2 = self.theclass(*args)
1306 self.failUnless(t1 == t2)
1307 self.failUnless(t1 <= t2)
1308 self.failUnless(t1 >= t2)
1309 self.failUnless(not t1 != t2)
1310 self.failUnless(not t1 < t2)
1311 self.failUnless(not t1 > t2)
1312 self.assertEqual(cmp(t1, t2), 0)
1313 self.assertEqual(cmp(t2, t1), 0)
1314
1315 for i in range(len(args)):
1316 newargs = args[:]
1317 newargs[i] = args[i] + 1
1318 t2 = self.theclass(*newargs) # this is larger than t1
1319 self.failUnless(t1 < t2)
1320 self.failUnless(t2 > t1)
1321 self.failUnless(t1 <= t2)
1322 self.failUnless(t2 >= t1)
1323 self.failUnless(t1 != t2)
1324 self.failUnless(t2 != t1)
1325 self.failUnless(not t1 == t2)
1326 self.failUnless(not t2 == t1)
1327 self.failUnless(not t1 > t2)
1328 self.failUnless(not t2 < t1)
1329 self.failUnless(not t1 >= t2)
1330 self.failUnless(not t2 <= t1)
1331 self.assertEqual(cmp(t1, t2), -1)
1332 self.assertEqual(cmp(t2, t1), 1)
1333
1334
1335 # A helper for timestamp constructor tests.
1336 def verify_field_equality(self, expected, got):
1337 self.assertEqual(expected.tm_year, got.year)
1338 self.assertEqual(expected.tm_mon, got.month)
1339 self.assertEqual(expected.tm_mday, got.day)
1340 self.assertEqual(expected.tm_hour, got.hour)
1341 self.assertEqual(expected.tm_min, got.minute)
1342 self.assertEqual(expected.tm_sec, got.second)
1343
1344 def test_fromtimestamp(self):
1345 import time
1346
1347 ts = time.time()
1348 expected = time.localtime(ts)
1349 got = self.theclass.fromtimestamp(ts)
1350 self.verify_field_equality(expected, got)
1351
1352 def test_utcfromtimestamp(self):
1353 import time
1354
1355 ts = time.time()
1356 expected = time.gmtime(ts)
1357 got = self.theclass.utcfromtimestamp(ts)
1358 self.verify_field_equality(expected, got)
1359
1360 def test_utcnow(self):
1361 import time
1362
1363 # Call it a success if utcnow() and utcfromtimestamp() are within
1364 # a second of each other.
1365 tolerance = timedelta(seconds=1)
1366 for dummy in range(3):
1367 from_now = self.theclass.utcnow()
1368 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1369 if abs(from_timestamp - from_now) <= tolerance:
1370 break
1371 # Else try again a few times.
1372 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1373
1374 def test_more_timetuple(self):
1375 # This tests fields beyond those tested by the TestDate.test_timetuple.
1376 t = self.theclass(2004, 12, 31, 6, 22, 33)
1377 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1378 self.assertEqual(t.timetuple(),
1379 (t.year, t.month, t.day,
1380 t.hour, t.minute, t.second,
1381 t.weekday(),
1382 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1383 -1))
1384 tt = t.timetuple()
1385 self.assertEqual(tt.tm_year, t.year)
1386 self.assertEqual(tt.tm_mon, t.month)
1387 self.assertEqual(tt.tm_mday, t.day)
1388 self.assertEqual(tt.tm_hour, t.hour)
1389 self.assertEqual(tt.tm_min, t.minute)
1390 self.assertEqual(tt.tm_sec, t.second)
1391 self.assertEqual(tt.tm_wday, t.weekday())
1392 self.assertEqual(tt.tm_yday, t.toordinal() -
1393 date(t.year, 1, 1).toordinal() + 1)
1394 self.assertEqual(tt.tm_isdst, -1)
1395
1396 def test_more_strftime(self):
1397 # This tests fields beyond those tested by the TestDate.test_strftime.
1398 t = self.theclass(2004, 12, 31, 6, 22, 33)
1399 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1400 "12 31 04 33 22 06 366")
1401
1402 def test_extract(self):
1403 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1404 self.assertEqual(dt.date(), date(2002, 3, 4))
1405 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1406
1407 def test_combine(self):
1408 d = date(2002, 3, 4)
1409 t = time(18, 45, 3, 1234)
1410 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1411 combine = self.theclass.combine
1412 dt = combine(d, t)
1413 self.assertEqual(dt, expected)
1414
1415 dt = combine(time=t, date=d)
1416 self.assertEqual(dt, expected)
1417
1418 self.assertEqual(d, dt.date())
1419 self.assertEqual(t, dt.time())
1420 self.assertEqual(dt, combine(dt.date(), dt.time()))
1421
1422 self.assertRaises(TypeError, combine) # need an arg
1423 self.assertRaises(TypeError, combine, d) # need two args
1424 self.assertRaises(TypeError, combine, t, d) # args reversed
1425 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1426 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1427
Tim Peters12bf3392002-12-24 05:41:27 +00001428 def test_replace(self):
1429 cls = self.theclass
1430 args = [1, 2, 3, 4, 5, 6, 7]
1431 base = cls(*args)
1432 self.assertEqual(base, base.replace())
1433
1434 i = 0
1435 for name, newval in (("year", 2),
1436 ("month", 3),
1437 ("day", 4),
1438 ("hour", 5),
1439 ("minute", 6),
1440 ("second", 7),
1441 ("microsecond", 8)):
1442 newargs = args[:]
1443 newargs[i] = newval
1444 expected = cls(*newargs)
1445 got = base.replace(**{name: newval})
1446 self.assertEqual(expected, got)
1447 i += 1
1448
1449 # Out of bounds.
1450 base = cls(2000, 2, 29)
1451 self.assertRaises(ValueError, base.replace, year=2001)
1452
Tim Peters80475bb2002-12-25 07:40:55 +00001453 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001454 # Pretty boring! The TZ test is more interesting here. astimezone()
1455 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001456 dt = self.theclass.now()
1457 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001458 self.assertRaises(TypeError, dt.astimezone) # not enough args
1459 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1460 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001461 self.assertRaises(ValueError, dt.astimezone, f) # naive
1462 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001463
Tim Peters52dcce22003-01-23 16:36:11 +00001464 class Bogus(tzinfo):
1465 def utcoffset(self, dt): return None
1466 def dst(self, dt): return timedelta(0)
1467 bog = Bogus()
1468 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1469
1470 class AlsoBogus(tzinfo):
1471 def utcoffset(self, dt): return timedelta(0)
1472 def dst(self, dt): return None
1473 alsobog = AlsoBogus()
1474 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001475
Tim Petersa98924a2003-05-17 05:55:19 +00001476 def test_subclass_datetime(self):
1477
1478 class C(self.theclass):
1479 theAnswer = 42
1480
1481 def __new__(cls, *args, **kws):
1482 temp = kws.copy()
1483 extra = temp.pop('extra')
1484 result = self.theclass.__new__(cls, *args, **temp)
1485 result.extra = extra
1486 return result
1487
1488 def newmeth(self, start):
1489 return start + self.year + self.month + self.second
1490
1491 args = 2003, 4, 14, 12, 13, 41
1492
1493 dt1 = self.theclass(*args)
1494 dt2 = C(*args, **{'extra': 7})
1495
1496 self.assertEqual(dt2.__class__, C)
1497 self.assertEqual(dt2.theAnswer, 42)
1498 self.assertEqual(dt2.extra, 7)
1499 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1500 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1501 dt1.second - 7)
1502
Tim Peters07534a62003-02-07 22:50:28 +00001503class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001504
1505 theclass = time
1506
1507 def test_basic_attributes(self):
1508 t = self.theclass(12, 0)
1509 self.assertEqual(t.hour, 12)
1510 self.assertEqual(t.minute, 0)
1511 self.assertEqual(t.second, 0)
1512 self.assertEqual(t.microsecond, 0)
1513
1514 def test_basic_attributes_nonzero(self):
1515 # Make sure all attributes are non-zero so bugs in
1516 # bit-shifting access show up.
1517 t = self.theclass(12, 59, 59, 8000)
1518 self.assertEqual(t.hour, 12)
1519 self.assertEqual(t.minute, 59)
1520 self.assertEqual(t.second, 59)
1521 self.assertEqual(t.microsecond, 8000)
1522
1523 def test_roundtrip(self):
1524 t = self.theclass(1, 2, 3, 4)
1525
1526 # Verify t -> string -> time identity.
1527 s = repr(t)
1528 self.failUnless(s.startswith('datetime.'))
1529 s = s[9:]
1530 t2 = eval(s)
1531 self.assertEqual(t, t2)
1532
1533 # Verify identity via reconstructing from pieces.
1534 t2 = self.theclass(t.hour, t.minute, t.second,
1535 t.microsecond)
1536 self.assertEqual(t, t2)
1537
1538 def test_comparing(self):
1539 args = [1, 2, 3, 4]
1540 t1 = self.theclass(*args)
1541 t2 = self.theclass(*args)
1542 self.failUnless(t1 == t2)
1543 self.failUnless(t1 <= t2)
1544 self.failUnless(t1 >= t2)
1545 self.failUnless(not t1 != t2)
1546 self.failUnless(not t1 < t2)
1547 self.failUnless(not t1 > t2)
1548 self.assertEqual(cmp(t1, t2), 0)
1549 self.assertEqual(cmp(t2, t1), 0)
1550
1551 for i in range(len(args)):
1552 newargs = args[:]
1553 newargs[i] = args[i] + 1
1554 t2 = self.theclass(*newargs) # this is larger than t1
1555 self.failUnless(t1 < t2)
1556 self.failUnless(t2 > t1)
1557 self.failUnless(t1 <= t2)
1558 self.failUnless(t2 >= t1)
1559 self.failUnless(t1 != t2)
1560 self.failUnless(t2 != t1)
1561 self.failUnless(not t1 == t2)
1562 self.failUnless(not t2 == t1)
1563 self.failUnless(not t1 > t2)
1564 self.failUnless(not t2 < t1)
1565 self.failUnless(not t1 >= t2)
1566 self.failUnless(not t2 <= t1)
1567 self.assertEqual(cmp(t1, t2), -1)
1568 self.assertEqual(cmp(t2, t1), 1)
1569
Tim Peters68124bb2003-02-08 03:46:31 +00001570 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001571 self.assertEqual(t1 == badarg, False)
1572 self.assertEqual(t1 != badarg, True)
1573 self.assertEqual(badarg == t1, False)
1574 self.assertEqual(badarg != t1, True)
1575
Tim Peters2a799bf2002-12-16 20:18:38 +00001576 self.assertRaises(TypeError, lambda: t1 <= badarg)
1577 self.assertRaises(TypeError, lambda: t1 < badarg)
1578 self.assertRaises(TypeError, lambda: t1 > badarg)
1579 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001580 self.assertRaises(TypeError, lambda: badarg <= t1)
1581 self.assertRaises(TypeError, lambda: badarg < t1)
1582 self.assertRaises(TypeError, lambda: badarg > t1)
1583 self.assertRaises(TypeError, lambda: badarg >= t1)
1584
1585 def test_bad_constructor_arguments(self):
1586 # bad hours
1587 self.theclass(0, 0) # no exception
1588 self.theclass(23, 0) # no exception
1589 self.assertRaises(ValueError, self.theclass, -1, 0)
1590 self.assertRaises(ValueError, self.theclass, 24, 0)
1591 # bad minutes
1592 self.theclass(23, 0) # no exception
1593 self.theclass(23, 59) # no exception
1594 self.assertRaises(ValueError, self.theclass, 23, -1)
1595 self.assertRaises(ValueError, self.theclass, 23, 60)
1596 # bad seconds
1597 self.theclass(23, 59, 0) # no exception
1598 self.theclass(23, 59, 59) # no exception
1599 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1600 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1601 # bad microseconds
1602 self.theclass(23, 59, 59, 0) # no exception
1603 self.theclass(23, 59, 59, 999999) # no exception
1604 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1605 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1606
1607 def test_hash_equality(self):
1608 d = self.theclass(23, 30, 17)
1609 e = self.theclass(23, 30, 17)
1610 self.assertEqual(d, e)
1611 self.assertEqual(hash(d), hash(e))
1612
1613 dic = {d: 1}
1614 dic[e] = 2
1615 self.assertEqual(len(dic), 1)
1616 self.assertEqual(dic[d], 2)
1617 self.assertEqual(dic[e], 2)
1618
1619 d = self.theclass(0, 5, 17)
1620 e = self.theclass(0, 5, 17)
1621 self.assertEqual(d, e)
1622 self.assertEqual(hash(d), hash(e))
1623
1624 dic = {d: 1}
1625 dic[e] = 2
1626 self.assertEqual(len(dic), 1)
1627 self.assertEqual(dic[d], 2)
1628 self.assertEqual(dic[e], 2)
1629
1630 def test_isoformat(self):
1631 t = self.theclass(4, 5, 1, 123)
1632 self.assertEqual(t.isoformat(), "04:05:01.000123")
1633 self.assertEqual(t.isoformat(), str(t))
1634
1635 t = self.theclass()
1636 self.assertEqual(t.isoformat(), "00:00:00")
1637 self.assertEqual(t.isoformat(), str(t))
1638
1639 t = self.theclass(microsecond=1)
1640 self.assertEqual(t.isoformat(), "00:00:00.000001")
1641 self.assertEqual(t.isoformat(), str(t))
1642
1643 t = self.theclass(microsecond=10)
1644 self.assertEqual(t.isoformat(), "00:00:00.000010")
1645 self.assertEqual(t.isoformat(), str(t))
1646
1647 t = self.theclass(microsecond=100)
1648 self.assertEqual(t.isoformat(), "00:00:00.000100")
1649 self.assertEqual(t.isoformat(), str(t))
1650
1651 t = self.theclass(microsecond=1000)
1652 self.assertEqual(t.isoformat(), "00:00:00.001000")
1653 self.assertEqual(t.isoformat(), str(t))
1654
1655 t = self.theclass(microsecond=10000)
1656 self.assertEqual(t.isoformat(), "00:00:00.010000")
1657 self.assertEqual(t.isoformat(), str(t))
1658
1659 t = self.theclass(microsecond=100000)
1660 self.assertEqual(t.isoformat(), "00:00:00.100000")
1661 self.assertEqual(t.isoformat(), str(t))
1662
1663 def test_strftime(self):
1664 t = self.theclass(1, 2, 3, 4)
1665 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1666 # A naive object replaces %z and %Z with empty strings.
1667 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1668
1669 def test_str(self):
1670 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1671 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1672 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1673 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1674 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1675
1676 def test_repr(self):
1677 name = 'datetime.' + self.theclass.__name__
1678 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1679 "%s(1, 2, 3, 4)" % name)
1680 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1681 "%s(10, 2, 3, 4000)" % name)
1682 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1683 "%s(0, 2, 3, 400000)" % name)
1684 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1685 "%s(12, 2, 3)" % name)
1686 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1687 "%s(23, 15)" % name)
1688
1689 def test_resolution_info(self):
1690 self.assert_(isinstance(self.theclass.min, self.theclass))
1691 self.assert_(isinstance(self.theclass.max, self.theclass))
1692 self.assert_(isinstance(self.theclass.resolution, timedelta))
1693 self.assert_(self.theclass.max > self.theclass.min)
1694
1695 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001696 args = 20, 59, 16, 64**2
1697 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001698 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001699 green = pickler.dumps(orig, proto)
1700 derived = unpickler.loads(green)
1701 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001702
1703 def test_bool(self):
1704 cls = self.theclass
1705 self.failUnless(cls(1))
1706 self.failUnless(cls(0, 1))
1707 self.failUnless(cls(0, 0, 1))
1708 self.failUnless(cls(0, 0, 0, 1))
1709 self.failUnless(not cls(0))
1710 self.failUnless(not cls())
1711
Tim Peters12bf3392002-12-24 05:41:27 +00001712 def test_replace(self):
1713 cls = self.theclass
1714 args = [1, 2, 3, 4]
1715 base = cls(*args)
1716 self.assertEqual(base, base.replace())
1717
1718 i = 0
1719 for name, newval in (("hour", 5),
1720 ("minute", 6),
1721 ("second", 7),
1722 ("microsecond", 8)):
1723 newargs = args[:]
1724 newargs[i] = newval
1725 expected = cls(*newargs)
1726 got = base.replace(**{name: newval})
1727 self.assertEqual(expected, got)
1728 i += 1
1729
1730 # Out of bounds.
1731 base = cls(1)
1732 self.assertRaises(ValueError, base.replace, hour=24)
1733 self.assertRaises(ValueError, base.replace, minute=-1)
1734 self.assertRaises(ValueError, base.replace, second=100)
1735 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1736
Tim Petersa98924a2003-05-17 05:55:19 +00001737 def test_subclass_time(self):
1738
1739 class C(self.theclass):
1740 theAnswer = 42
1741
1742 def __new__(cls, *args, **kws):
1743 temp = kws.copy()
1744 extra = temp.pop('extra')
1745 result = self.theclass.__new__(cls, *args, **temp)
1746 result.extra = extra
1747 return result
1748
1749 def newmeth(self, start):
1750 return start + self.hour + self.second
1751
1752 args = 4, 5, 6
1753
1754 dt1 = self.theclass(*args)
1755 dt2 = C(*args, **{'extra': 7})
1756
1757 self.assertEqual(dt2.__class__, C)
1758 self.assertEqual(dt2.theAnswer, 42)
1759 self.assertEqual(dt2.extra, 7)
1760 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1761 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1762
Tim Peters855fe882002-12-22 03:43:39 +00001763# A mixin for classes with a tzinfo= argument. Subclasses must define
1764# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001765# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001766class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001767
Tim Petersbad8ff02002-12-30 20:52:32 +00001768 def test_argument_passing(self):
1769 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001770 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001771 class introspective(tzinfo):
1772 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001773 def utcoffset(self, dt):
1774 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001775 dst = utcoffset
1776
1777 obj = cls(1, 2, 3, tzinfo=introspective())
1778
Tim Peters0bf60bd2003-01-08 20:40:01 +00001779 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001780 self.assertEqual(obj.tzname(), expected)
1781
Tim Peters0bf60bd2003-01-08 20:40:01 +00001782 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001783 self.assertEqual(obj.utcoffset(), expected)
1784 self.assertEqual(obj.dst(), expected)
1785
Tim Peters855fe882002-12-22 03:43:39 +00001786 def test_bad_tzinfo_classes(self):
1787 cls = self.theclass
1788 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001789
Tim Peters855fe882002-12-22 03:43:39 +00001790 class NiceTry(object):
1791 def __init__(self): pass
1792 def utcoffset(self, dt): pass
1793 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1794
1795 class BetterTry(tzinfo):
1796 def __init__(self): pass
1797 def utcoffset(self, dt): pass
1798 b = BetterTry()
1799 t = cls(1, 1, 1, tzinfo=b)
1800 self.failUnless(t.tzinfo is b)
1801
1802 def test_utc_offset_out_of_bounds(self):
1803 class Edgy(tzinfo):
1804 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001805 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001806 def utcoffset(self, dt):
1807 return self.offset
1808
1809 cls = self.theclass
1810 for offset, legit in ((-1440, False),
1811 (-1439, True),
1812 (1439, True),
1813 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001814 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001815 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001816 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001817 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001818 else:
1819 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001820 if legit:
1821 aofs = abs(offset)
1822 h, m = divmod(aofs, 60)
1823 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001824 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001825 t = t.timetz()
1826 self.assertEqual(str(t), "01:02:03" + tag)
1827 else:
1828 self.assertRaises(ValueError, str, t)
1829
1830 def test_tzinfo_classes(self):
1831 cls = self.theclass
1832 class C1(tzinfo):
1833 def utcoffset(self, dt): return None
1834 def dst(self, dt): return None
1835 def tzname(self, dt): return None
1836 for t in (cls(1, 1, 1),
1837 cls(1, 1, 1, tzinfo=None),
1838 cls(1, 1, 1, tzinfo=C1())):
1839 self.failUnless(t.utcoffset() is None)
1840 self.failUnless(t.dst() is None)
1841 self.failUnless(t.tzname() is None)
1842
Tim Peters855fe882002-12-22 03:43:39 +00001843 class C3(tzinfo):
1844 def utcoffset(self, dt): return timedelta(minutes=-1439)
1845 def dst(self, dt): return timedelta(minutes=1439)
1846 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001847 t = cls(1, 1, 1, tzinfo=C3())
1848 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1849 self.assertEqual(t.dst(), timedelta(minutes=1439))
1850 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001851
1852 # Wrong types.
1853 class C4(tzinfo):
1854 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001855 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001856 def tzname(self, dt): return 0
1857 t = cls(1, 1, 1, tzinfo=C4())
1858 self.assertRaises(TypeError, t.utcoffset)
1859 self.assertRaises(TypeError, t.dst)
1860 self.assertRaises(TypeError, t.tzname)
1861
1862 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001863 class C6(tzinfo):
1864 def utcoffset(self, dt): return timedelta(hours=-24)
1865 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001866 t = cls(1, 1, 1, tzinfo=C6())
1867 self.assertRaises(ValueError, t.utcoffset)
1868 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001869
1870 # Not a whole number of minutes.
1871 class C7(tzinfo):
1872 def utcoffset(self, dt): return timedelta(seconds=61)
1873 def dst(self, dt): return timedelta(microseconds=-81)
1874 t = cls(1, 1, 1, tzinfo=C7())
1875 self.assertRaises(ValueError, t.utcoffset)
1876 self.assertRaises(ValueError, t.dst)
1877
Tim Peters4c0db782002-12-26 05:01:19 +00001878 def test_aware_compare(self):
1879 cls = self.theclass
1880
Tim Peters60c76e42002-12-27 00:41:11 +00001881 # Ensure that utcoffset() gets ignored if the comparands have
1882 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001883 class OperandDependentOffset(tzinfo):
1884 def utcoffset(self, t):
1885 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001886 # d0 and d1 equal after adjustment
1887 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001888 else:
Tim Peters397301e2003-01-02 21:28:08 +00001889 # d2 off in the weeds
1890 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001891
1892 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1893 d0 = base.replace(minute=3)
1894 d1 = base.replace(minute=9)
1895 d2 = base.replace(minute=11)
1896 for x in d0, d1, d2:
1897 for y in d0, d1, d2:
1898 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001899 expected = cmp(x.minute, y.minute)
1900 self.assertEqual(got, expected)
1901
1902 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001903 # Note that a time can't actually have an operand-depedent offset,
1904 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1905 # so skip this test for time.
1906 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001907 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1908 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1909 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1910 for x in d0, d1, d2:
1911 for y in d0, d1, d2:
1912 got = cmp(x, y)
1913 if (x is d0 or x is d1) and (y is d0 or y is d1):
1914 expected = 0
1915 elif x is y is d2:
1916 expected = 0
1917 elif x is d2:
1918 expected = -1
1919 else:
1920 assert y is d2
1921 expected = 1
1922 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001923
Tim Peters855fe882002-12-22 03:43:39 +00001924
Tim Peters0bf60bd2003-01-08 20:40:01 +00001925# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001926class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001927 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001928
1929 def test_empty(self):
1930 t = self.theclass()
1931 self.assertEqual(t.hour, 0)
1932 self.assertEqual(t.minute, 0)
1933 self.assertEqual(t.second, 0)
1934 self.assertEqual(t.microsecond, 0)
1935 self.failUnless(t.tzinfo is None)
1936
Tim Peters2a799bf2002-12-16 20:18:38 +00001937 def test_zones(self):
1938 est = FixedOffset(-300, "EST", 1)
1939 utc = FixedOffset(0, "UTC", -2)
1940 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001941 t1 = time( 7, 47, tzinfo=est)
1942 t2 = time(12, 47, tzinfo=utc)
1943 t3 = time(13, 47, tzinfo=met)
1944 t4 = time(microsecond=40)
1945 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001946
1947 self.assertEqual(t1.tzinfo, est)
1948 self.assertEqual(t2.tzinfo, utc)
1949 self.assertEqual(t3.tzinfo, met)
1950 self.failUnless(t4.tzinfo is None)
1951 self.assertEqual(t5.tzinfo, utc)
1952
Tim Peters855fe882002-12-22 03:43:39 +00001953 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1954 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1955 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001956 self.failUnless(t4.utcoffset() is None)
1957 self.assertRaises(TypeError, t1.utcoffset, "no args")
1958
1959 self.assertEqual(t1.tzname(), "EST")
1960 self.assertEqual(t2.tzname(), "UTC")
1961 self.assertEqual(t3.tzname(), "MET")
1962 self.failUnless(t4.tzname() is None)
1963 self.assertRaises(TypeError, t1.tzname, "no args")
1964
Tim Peters855fe882002-12-22 03:43:39 +00001965 self.assertEqual(t1.dst(), timedelta(minutes=1))
1966 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1967 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001968 self.failUnless(t4.dst() is None)
1969 self.assertRaises(TypeError, t1.dst, "no args")
1970
1971 self.assertEqual(hash(t1), hash(t2))
1972 self.assertEqual(hash(t1), hash(t3))
1973 self.assertEqual(hash(t2), hash(t3))
1974
1975 self.assertEqual(t1, t2)
1976 self.assertEqual(t1, t3)
1977 self.assertEqual(t2, t3)
1978 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1979 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1980 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1981
1982 self.assertEqual(str(t1), "07:47:00-05:00")
1983 self.assertEqual(str(t2), "12:47:00+00:00")
1984 self.assertEqual(str(t3), "13:47:00+01:00")
1985 self.assertEqual(str(t4), "00:00:00.000040")
1986 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1987
1988 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1989 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1990 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1991 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1992 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1993
Tim Peters0bf60bd2003-01-08 20:40:01 +00001994 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001995 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1996 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1997 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1998 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1999 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2000
2001 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2002 "07:47:00 %Z=EST %z=-0500")
2003 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2004 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2005
2006 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002007 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002008 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2009 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2010
Tim Petersb92bb712002-12-21 17:44:07 +00002011 # Check that an invalid tzname result raises an exception.
2012 class Badtzname(tzinfo):
2013 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002014 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002015 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2016 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002017
2018 def test_hash_edge_cases(self):
2019 # Offsets that overflow a basic time.
2020 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2021 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2022 self.assertEqual(hash(t1), hash(t2))
2023
2024 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2025 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2026 self.assertEqual(hash(t1), hash(t2))
2027
Tim Peters2a799bf2002-12-16 20:18:38 +00002028 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002029 # Try one without a tzinfo.
2030 args = 20, 59, 16, 64**2
2031 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002032 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002033 green = pickler.dumps(orig, proto)
2034 derived = unpickler.loads(green)
2035 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002036
2037 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002038 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002039 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002040 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002041 green = pickler.dumps(orig, proto)
2042 derived = unpickler.loads(green)
2043 self.assertEqual(orig, derived)
2044 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2045 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2046 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002047
2048 def test_more_bool(self):
2049 # Test cases with non-None tzinfo.
2050 cls = self.theclass
2051
2052 t = cls(0, tzinfo=FixedOffset(-300, ""))
2053 self.failUnless(t)
2054
2055 t = cls(5, tzinfo=FixedOffset(-300, ""))
2056 self.failUnless(t)
2057
2058 t = cls(5, tzinfo=FixedOffset(300, ""))
2059 self.failUnless(not t)
2060
2061 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2062 self.failUnless(not t)
2063
2064 # Mostly ensuring this doesn't overflow internally.
2065 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2066 self.failUnless(t)
2067
2068 # But this should yield a value error -- the utcoffset is bogus.
2069 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2070 self.assertRaises(ValueError, lambda: bool(t))
2071
2072 # Likewise.
2073 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2074 self.assertRaises(ValueError, lambda: bool(t))
2075
Tim Peters12bf3392002-12-24 05:41:27 +00002076 def test_replace(self):
2077 cls = self.theclass
2078 z100 = FixedOffset(100, "+100")
2079 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2080 args = [1, 2, 3, 4, z100]
2081 base = cls(*args)
2082 self.assertEqual(base, base.replace())
2083
2084 i = 0
2085 for name, newval in (("hour", 5),
2086 ("minute", 6),
2087 ("second", 7),
2088 ("microsecond", 8),
2089 ("tzinfo", zm200)):
2090 newargs = args[:]
2091 newargs[i] = newval
2092 expected = cls(*newargs)
2093 got = base.replace(**{name: newval})
2094 self.assertEqual(expected, got)
2095 i += 1
2096
2097 # Ensure we can get rid of a tzinfo.
2098 self.assertEqual(base.tzname(), "+100")
2099 base2 = base.replace(tzinfo=None)
2100 self.failUnless(base2.tzinfo is None)
2101 self.failUnless(base2.tzname() is None)
2102
2103 # Ensure we can add one.
2104 base3 = base2.replace(tzinfo=z100)
2105 self.assertEqual(base, base3)
2106 self.failUnless(base.tzinfo is base3.tzinfo)
2107
2108 # Out of bounds.
2109 base = cls(1)
2110 self.assertRaises(ValueError, base.replace, hour=24)
2111 self.assertRaises(ValueError, base.replace, minute=-1)
2112 self.assertRaises(ValueError, base.replace, second=100)
2113 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2114
Tim Peters60c76e42002-12-27 00:41:11 +00002115 def test_mixed_compare(self):
2116 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002117 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002118 self.assertEqual(t1, t2)
2119 t2 = t2.replace(tzinfo=None)
2120 self.assertEqual(t1, t2)
2121 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2122 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002123 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2124 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002125
Tim Peters0bf60bd2003-01-08 20:40:01 +00002126 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002127 class Varies(tzinfo):
2128 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002129 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002130 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002131 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002132 return self.offset
2133
2134 v = Varies()
2135 t1 = t2.replace(tzinfo=v)
2136 t2 = t2.replace(tzinfo=v)
2137 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2138 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2139 self.assertEqual(t1, t2)
2140
2141 # But if they're not identical, it isn't ignored.
2142 t2 = t2.replace(tzinfo=Varies())
2143 self.failUnless(t1 < t2) # t1's offset counter still going up
2144
Tim Petersa98924a2003-05-17 05:55:19 +00002145 def test_subclass_timetz(self):
2146
2147 class C(self.theclass):
2148 theAnswer = 42
2149
2150 def __new__(cls, *args, **kws):
2151 temp = kws.copy()
2152 extra = temp.pop('extra')
2153 result = self.theclass.__new__(cls, *args, **temp)
2154 result.extra = extra
2155 return result
2156
2157 def newmeth(self, start):
2158 return start + self.hour + self.second
2159
2160 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2161
2162 dt1 = self.theclass(*args)
2163 dt2 = C(*args, **{'extra': 7})
2164
2165 self.assertEqual(dt2.__class__, C)
2166 self.assertEqual(dt2.theAnswer, 42)
2167 self.assertEqual(dt2.extra, 7)
2168 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2169 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2170
Tim Peters4c0db782002-12-26 05:01:19 +00002171
Tim Peters0bf60bd2003-01-08 20:40:01 +00002172# Testing datetime objects with a non-None tzinfo.
2173
Tim Peters855fe882002-12-22 03:43:39 +00002174class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002175 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002176
2177 def test_trivial(self):
2178 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2179 self.assertEqual(dt.year, 1)
2180 self.assertEqual(dt.month, 2)
2181 self.assertEqual(dt.day, 3)
2182 self.assertEqual(dt.hour, 4)
2183 self.assertEqual(dt.minute, 5)
2184 self.assertEqual(dt.second, 6)
2185 self.assertEqual(dt.microsecond, 7)
2186 self.assertEqual(dt.tzinfo, None)
2187
2188 def test_even_more_compare(self):
2189 # The test_compare() and test_more_compare() inherited from TestDate
2190 # and TestDateTime covered non-tzinfo cases.
2191
2192 # Smallest possible after UTC adjustment.
2193 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2194 # Largest possible after UTC adjustment.
2195 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2196 tzinfo=FixedOffset(-1439, ""))
2197
2198 # Make sure those compare correctly, and w/o overflow.
2199 self.failUnless(t1 < t2)
2200 self.failUnless(t1 != t2)
2201 self.failUnless(t2 > t1)
2202
2203 self.failUnless(t1 == t1)
2204 self.failUnless(t2 == t2)
2205
2206 # Equal afer adjustment.
2207 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2208 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2209 self.assertEqual(t1, t2)
2210
2211 # Change t1 not to subtract a minute, and t1 should be larger.
2212 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2213 self.failUnless(t1 > t2)
2214
2215 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2216 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2217 self.failUnless(t1 < t2)
2218
2219 # Back to the original t1, but make seconds resolve it.
2220 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2221 second=1)
2222 self.failUnless(t1 > t2)
2223
2224 # Likewise, but make microseconds resolve it.
2225 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2226 microsecond=1)
2227 self.failUnless(t1 > t2)
2228
2229 # Make t2 naive and it should fail.
2230 t2 = self.theclass.min
2231 self.assertRaises(TypeError, lambda: t1 == t2)
2232 self.assertEqual(t2, t2)
2233
2234 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2235 class Naive(tzinfo):
2236 def utcoffset(self, dt): return None
2237 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2238 self.assertRaises(TypeError, lambda: t1 == t2)
2239 self.assertEqual(t2, t2)
2240
2241 # OTOH, it's OK to compare two of these mixing the two ways of being
2242 # naive.
2243 t1 = self.theclass(5, 6, 7)
2244 self.assertEqual(t1, t2)
2245
2246 # Try a bogus uctoffset.
2247 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002248 def utcoffset(self, dt):
2249 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002250 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2251 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002252 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002253
Tim Peters2a799bf2002-12-16 20:18:38 +00002254 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002255 # Try one without a tzinfo.
2256 args = 6, 7, 23, 20, 59, 1, 64**2
2257 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002258 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002259 green = pickler.dumps(orig, proto)
2260 derived = unpickler.loads(green)
2261 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002262
2263 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002264 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002265 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002266 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002267 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002268 green = pickler.dumps(orig, proto)
2269 derived = unpickler.loads(green)
2270 self.assertEqual(orig, derived)
2271 self.failUnless(isinstance(derived.tzinfo,
2272 PicklableFixedOffset))
2273 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2274 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002275
2276 def test_extreme_hashes(self):
2277 # If an attempt is made to hash these via subtracting the offset
2278 # then hashing a datetime object, OverflowError results. The
2279 # Python implementation used to blow up here.
2280 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2281 hash(t)
2282 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2283 tzinfo=FixedOffset(-1439, ""))
2284 hash(t)
2285
2286 # OTOH, an OOB offset should blow up.
2287 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2288 self.assertRaises(ValueError, hash, t)
2289
2290 def test_zones(self):
2291 est = FixedOffset(-300, "EST")
2292 utc = FixedOffset(0, "UTC")
2293 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002294 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2295 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2296 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002297 self.assertEqual(t1.tzinfo, est)
2298 self.assertEqual(t2.tzinfo, utc)
2299 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002300 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2301 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2302 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002303 self.assertEqual(t1.tzname(), "EST")
2304 self.assertEqual(t2.tzname(), "UTC")
2305 self.assertEqual(t3.tzname(), "MET")
2306 self.assertEqual(hash(t1), hash(t2))
2307 self.assertEqual(hash(t1), hash(t3))
2308 self.assertEqual(hash(t2), hash(t3))
2309 self.assertEqual(t1, t2)
2310 self.assertEqual(t1, t3)
2311 self.assertEqual(t2, t3)
2312 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2313 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2314 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002315 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002316 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2317 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2318 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2319
2320 def test_combine(self):
2321 met = FixedOffset(60, "MET")
2322 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002323 tz = time(18, 45, 3, 1234, tzinfo=met)
2324 dt = datetime.combine(d, tz)
2325 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002326 tzinfo=met))
2327
2328 def test_extract(self):
2329 met = FixedOffset(60, "MET")
2330 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2331 self.assertEqual(dt.date(), date(2002, 3, 4))
2332 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002333 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002334
2335 def test_tz_aware_arithmetic(self):
2336 import random
2337
2338 now = self.theclass.now()
2339 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002340 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002341 nowaware = self.theclass.combine(now.date(), timeaware)
2342 self.failUnless(nowaware.tzinfo is tz55)
2343 self.assertEqual(nowaware.timetz(), timeaware)
2344
2345 # Can't mix aware and non-aware.
2346 self.assertRaises(TypeError, lambda: now - nowaware)
2347 self.assertRaises(TypeError, lambda: nowaware - now)
2348
Tim Peters0bf60bd2003-01-08 20:40:01 +00002349 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002350 self.assertRaises(TypeError, lambda: now + nowaware)
2351 self.assertRaises(TypeError, lambda: nowaware + now)
2352 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2353
2354 # Subtracting should yield 0.
2355 self.assertEqual(now - now, timedelta(0))
2356 self.assertEqual(nowaware - nowaware, timedelta(0))
2357
2358 # Adding a delta should preserve tzinfo.
2359 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2360 nowawareplus = nowaware + delta
2361 self.failUnless(nowaware.tzinfo is tz55)
2362 nowawareplus2 = delta + nowaware
2363 self.failUnless(nowawareplus2.tzinfo is tz55)
2364 self.assertEqual(nowawareplus, nowawareplus2)
2365
2366 # that - delta should be what we started with, and that - what we
2367 # started with should be delta.
2368 diff = nowawareplus - delta
2369 self.failUnless(diff.tzinfo is tz55)
2370 self.assertEqual(nowaware, diff)
2371 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2372 self.assertEqual(nowawareplus - nowaware, delta)
2373
2374 # Make up a random timezone.
2375 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002376 # Attach it to nowawareplus.
2377 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002378 self.failUnless(nowawareplus.tzinfo is tzr)
2379 # Make sure the difference takes the timezone adjustments into account.
2380 got = nowaware - nowawareplus
2381 # Expected: (nowaware base - nowaware offset) -
2382 # (nowawareplus base - nowawareplus offset) =
2383 # (nowaware base - nowawareplus base) +
2384 # (nowawareplus offset - nowaware offset) =
2385 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002386 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002387 self.assertEqual(got, expected)
2388
2389 # Try max possible difference.
2390 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2391 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2392 tzinfo=FixedOffset(-1439, "max"))
2393 maxdiff = max - min
2394 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2395 timedelta(minutes=2*1439))
2396
2397 def test_tzinfo_now(self):
2398 meth = self.theclass.now
2399 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2400 base = meth()
2401 # Try with and without naming the keyword.
2402 off42 = FixedOffset(42, "42")
2403 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002404 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002405 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002406 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002407 # Bad argument with and w/o naming the keyword.
2408 self.assertRaises(TypeError, meth, 16)
2409 self.assertRaises(TypeError, meth, tzinfo=16)
2410 # Bad keyword name.
2411 self.assertRaises(TypeError, meth, tinfo=off42)
2412 # Too many args.
2413 self.assertRaises(TypeError, meth, off42, off42)
2414
Tim Peters10cadce2003-01-23 19:58:02 +00002415 # We don't know which time zone we're in, and don't have a tzinfo
2416 # class to represent it, so seeing whether a tz argument actually
2417 # does a conversion is tricky.
2418 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2419 utc = FixedOffset(0, "utc", 0)
2420 for dummy in range(3):
2421 now = datetime.now(weirdtz)
2422 self.failUnless(now.tzinfo is weirdtz)
2423 utcnow = datetime.utcnow().replace(tzinfo=utc)
2424 now2 = utcnow.astimezone(weirdtz)
2425 if abs(now - now2) < timedelta(seconds=30):
2426 break
2427 # Else the code is broken, or more than 30 seconds passed between
2428 # calls; assuming the latter, just try again.
2429 else:
2430 # Three strikes and we're out.
2431 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2432
Tim Peters2a799bf2002-12-16 20:18:38 +00002433 def test_tzinfo_fromtimestamp(self):
2434 import time
2435 meth = self.theclass.fromtimestamp
2436 ts = time.time()
2437 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2438 base = meth(ts)
2439 # Try with and without naming the keyword.
2440 off42 = FixedOffset(42, "42")
2441 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002442 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002443 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002444 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002445 # Bad argument with and w/o naming the keyword.
2446 self.assertRaises(TypeError, meth, ts, 16)
2447 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2448 # Bad keyword name.
2449 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2450 # Too many args.
2451 self.assertRaises(TypeError, meth, ts, off42, off42)
2452 # Too few args.
2453 self.assertRaises(TypeError, meth)
2454
Tim Peters2a44a8d2003-01-23 20:53:10 +00002455 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002456 timestamp = 1000000000
2457 utcdatetime = datetime.utcfromtimestamp(timestamp)
2458 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2459 # But on some flavor of Mac, it's nowhere near that. So we can't have
2460 # any idea here what time that actually is, we can only test that
2461 # relative changes match.
2462 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2463 tz = FixedOffset(utcoffset, "tz", 0)
2464 expected = utcdatetime + utcoffset
2465 got = datetime.fromtimestamp(timestamp, tz)
2466 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002467
Tim Peters2a799bf2002-12-16 20:18:38 +00002468 def test_tzinfo_utcnow(self):
2469 meth = self.theclass.utcnow
2470 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2471 base = meth()
2472 # Try with and without naming the keyword; for whatever reason,
2473 # utcnow() doesn't accept a tzinfo argument.
2474 off42 = FixedOffset(42, "42")
2475 self.assertRaises(TypeError, meth, off42)
2476 self.assertRaises(TypeError, meth, tzinfo=off42)
2477
2478 def test_tzinfo_utcfromtimestamp(self):
2479 import time
2480 meth = self.theclass.utcfromtimestamp
2481 ts = time.time()
2482 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2483 base = meth(ts)
2484 # Try with and without naming the keyword; for whatever reason,
2485 # utcfromtimestamp() doesn't accept a tzinfo argument.
2486 off42 = FixedOffset(42, "42")
2487 self.assertRaises(TypeError, meth, ts, off42)
2488 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2489
2490 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002491 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002492 # DST flag.
2493 class DST(tzinfo):
2494 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002495 if isinstance(dstvalue, int):
2496 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002497 self.dstvalue = dstvalue
2498 def dst(self, dt):
2499 return self.dstvalue
2500
2501 cls = self.theclass
2502 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2503 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2504 t = d.timetuple()
2505 self.assertEqual(1, t.tm_year)
2506 self.assertEqual(1, t.tm_mon)
2507 self.assertEqual(1, t.tm_mday)
2508 self.assertEqual(10, t.tm_hour)
2509 self.assertEqual(20, t.tm_min)
2510 self.assertEqual(30, t.tm_sec)
2511 self.assertEqual(0, t.tm_wday)
2512 self.assertEqual(1, t.tm_yday)
2513 self.assertEqual(flag, t.tm_isdst)
2514
2515 # dst() returns wrong type.
2516 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2517
2518 # dst() at the edge.
2519 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2520 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2521
2522 # dst() out of range.
2523 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2524 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2525
2526 def test_utctimetuple(self):
2527 class DST(tzinfo):
2528 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002529 if isinstance(dstvalue, int):
2530 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002531 self.dstvalue = dstvalue
2532 def dst(self, dt):
2533 return self.dstvalue
2534
2535 cls = self.theclass
2536 # This can't work: DST didn't implement utcoffset.
2537 self.assertRaises(NotImplementedError,
2538 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2539
2540 class UOFS(DST):
2541 def __init__(self, uofs, dofs=None):
2542 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002543 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002544 def utcoffset(self, dt):
2545 return self.uofs
2546
2547 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2548 # in effect for a UTC time.
2549 for dstvalue in -33, 33, 0, None:
2550 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2551 t = d.utctimetuple()
2552 self.assertEqual(d.year, t.tm_year)
2553 self.assertEqual(d.month, t.tm_mon)
2554 self.assertEqual(d.day, t.tm_mday)
2555 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2556 self.assertEqual(13, t.tm_min)
2557 self.assertEqual(d.second, t.tm_sec)
2558 self.assertEqual(d.weekday(), t.tm_wday)
2559 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2560 t.tm_yday)
2561 self.assertEqual(0, t.tm_isdst)
2562
2563 # At the edges, UTC adjustment can normalize into years out-of-range
2564 # for a datetime object. Ensure that a correct timetuple is
2565 # created anyway.
2566 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2567 # That goes back 1 minute less than a full day.
2568 t = tiny.utctimetuple()
2569 self.assertEqual(t.tm_year, MINYEAR-1)
2570 self.assertEqual(t.tm_mon, 12)
2571 self.assertEqual(t.tm_mday, 31)
2572 self.assertEqual(t.tm_hour, 0)
2573 self.assertEqual(t.tm_min, 1)
2574 self.assertEqual(t.tm_sec, 37)
2575 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2576 self.assertEqual(t.tm_isdst, 0)
2577
2578 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2579 # That goes forward 1 minute less than a full day.
2580 t = huge.utctimetuple()
2581 self.assertEqual(t.tm_year, MAXYEAR+1)
2582 self.assertEqual(t.tm_mon, 1)
2583 self.assertEqual(t.tm_mday, 1)
2584 self.assertEqual(t.tm_hour, 23)
2585 self.assertEqual(t.tm_min, 58)
2586 self.assertEqual(t.tm_sec, 37)
2587 self.assertEqual(t.tm_yday, 1)
2588 self.assertEqual(t.tm_isdst, 0)
2589
2590 def test_tzinfo_isoformat(self):
2591 zero = FixedOffset(0, "+00:00")
2592 plus = FixedOffset(220, "+03:40")
2593 minus = FixedOffset(-231, "-03:51")
2594 unknown = FixedOffset(None, "")
2595
2596 cls = self.theclass
2597 datestr = '0001-02-03'
2598 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002599 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002600 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2601 timestr = '04:05:59' + (us and '.987001' or '')
2602 ofsstr = ofs is not None and d.tzname() or ''
2603 tailstr = timestr + ofsstr
2604 iso = d.isoformat()
2605 self.assertEqual(iso, datestr + 'T' + tailstr)
2606 self.assertEqual(iso, d.isoformat('T'))
2607 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2608 self.assertEqual(str(d), datestr + ' ' + tailstr)
2609
Tim Peters12bf3392002-12-24 05:41:27 +00002610 def test_replace(self):
2611 cls = self.theclass
2612 z100 = FixedOffset(100, "+100")
2613 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2614 args = [1, 2, 3, 4, 5, 6, 7, z100]
2615 base = cls(*args)
2616 self.assertEqual(base, base.replace())
2617
2618 i = 0
2619 for name, newval in (("year", 2),
2620 ("month", 3),
2621 ("day", 4),
2622 ("hour", 5),
2623 ("minute", 6),
2624 ("second", 7),
2625 ("microsecond", 8),
2626 ("tzinfo", zm200)):
2627 newargs = args[:]
2628 newargs[i] = newval
2629 expected = cls(*newargs)
2630 got = base.replace(**{name: newval})
2631 self.assertEqual(expected, got)
2632 i += 1
2633
2634 # Ensure we can get rid of a tzinfo.
2635 self.assertEqual(base.tzname(), "+100")
2636 base2 = base.replace(tzinfo=None)
2637 self.failUnless(base2.tzinfo is None)
2638 self.failUnless(base2.tzname() is None)
2639
2640 # Ensure we can add one.
2641 base3 = base2.replace(tzinfo=z100)
2642 self.assertEqual(base, base3)
2643 self.failUnless(base.tzinfo is base3.tzinfo)
2644
2645 # Out of bounds.
2646 base = cls(2000, 2, 29)
2647 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002648
Tim Peters80475bb2002-12-25 07:40:55 +00002649 def test_more_astimezone(self):
2650 # The inherited test_astimezone covered some trivial and error cases.
2651 fnone = FixedOffset(None, "None")
2652 f44m = FixedOffset(44, "44")
2653 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2654
Tim Peters10cadce2003-01-23 19:58:02 +00002655 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002656 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002657 # Replacing with degenerate tzinfo raises an exception.
2658 self.assertRaises(ValueError, dt.astimezone, fnone)
2659 # Ditto with None tz.
2660 self.assertRaises(TypeError, dt.astimezone, None)
2661 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002662 x = dt.astimezone(dt.tzinfo)
2663 self.failUnless(x.tzinfo is f44m)
2664 self.assertEqual(x.date(), dt.date())
2665 self.assertEqual(x.time(), dt.time())
2666
2667 # Replacing with different tzinfo does adjust.
2668 got = dt.astimezone(fm5h)
2669 self.failUnless(got.tzinfo is fm5h)
2670 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2671 expected = dt - dt.utcoffset() # in effect, convert to UTC
2672 expected += fm5h.utcoffset(dt) # and from there to local time
2673 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2674 self.assertEqual(got.date(), expected.date())
2675 self.assertEqual(got.time(), expected.time())
2676 self.assertEqual(got.timetz(), expected.timetz())
2677 self.failUnless(got.tzinfo is expected.tzinfo)
2678 self.assertEqual(got, expected)
2679
Tim Peters4c0db782002-12-26 05:01:19 +00002680 def test_aware_subtract(self):
2681 cls = self.theclass
2682
Tim Peters60c76e42002-12-27 00:41:11 +00002683 # Ensure that utcoffset() is ignored when the operands have the
2684 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002685 class OperandDependentOffset(tzinfo):
2686 def utcoffset(self, t):
2687 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002688 # d0 and d1 equal after adjustment
2689 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002690 else:
Tim Peters397301e2003-01-02 21:28:08 +00002691 # d2 off in the weeds
2692 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002693
2694 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2695 d0 = base.replace(minute=3)
2696 d1 = base.replace(minute=9)
2697 d2 = base.replace(minute=11)
2698 for x in d0, d1, d2:
2699 for y in d0, d1, d2:
2700 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002701 expected = timedelta(minutes=x.minute - y.minute)
2702 self.assertEqual(got, expected)
2703
2704 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2705 # ignored.
2706 base = cls(8, 9, 10, 11, 12, 13, 14)
2707 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2708 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2709 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2710 for x in d0, d1, d2:
2711 for y in d0, d1, d2:
2712 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002713 if (x is d0 or x is d1) and (y is d0 or y is d1):
2714 expected = timedelta(0)
2715 elif x is y is d2:
2716 expected = timedelta(0)
2717 elif x is d2:
2718 expected = timedelta(minutes=(11-59)-0)
2719 else:
2720 assert y is d2
2721 expected = timedelta(minutes=0-(11-59))
2722 self.assertEqual(got, expected)
2723
Tim Peters60c76e42002-12-27 00:41:11 +00002724 def test_mixed_compare(self):
2725 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002726 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002727 self.assertEqual(t1, t2)
2728 t2 = t2.replace(tzinfo=None)
2729 self.assertEqual(t1, t2)
2730 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2731 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002732 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2733 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002734
Tim Peters0bf60bd2003-01-08 20:40:01 +00002735 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002736 class Varies(tzinfo):
2737 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002738 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002739 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002740 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002741 return self.offset
2742
2743 v = Varies()
2744 t1 = t2.replace(tzinfo=v)
2745 t2 = t2.replace(tzinfo=v)
2746 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2747 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2748 self.assertEqual(t1, t2)
2749
2750 # But if they're not identical, it isn't ignored.
2751 t2 = t2.replace(tzinfo=Varies())
2752 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002753
Tim Petersa98924a2003-05-17 05:55:19 +00002754 def test_subclass_datetimetz(self):
2755
2756 class C(self.theclass):
2757 theAnswer = 42
2758
2759 def __new__(cls, *args, **kws):
2760 temp = kws.copy()
2761 extra = temp.pop('extra')
2762 result = self.theclass.__new__(cls, *args, **temp)
2763 result.extra = extra
2764 return result
2765
2766 def newmeth(self, start):
2767 return start + self.hour + self.year
2768
2769 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2770
2771 dt1 = self.theclass(*args)
2772 dt2 = C(*args, **{'extra': 7})
2773
2774 self.assertEqual(dt2.__class__, C)
2775 self.assertEqual(dt2.theAnswer, 42)
2776 self.assertEqual(dt2.extra, 7)
2777 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2778 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2779
Tim Peters621818b2002-12-29 23:44:49 +00002780# Pain to set up DST-aware tzinfo classes.
2781
2782def first_sunday_on_or_after(dt):
2783 days_to_go = 6 - dt.weekday()
2784 if days_to_go:
2785 dt += timedelta(days_to_go)
2786 return dt
2787
2788ZERO = timedelta(0)
2789HOUR = timedelta(hours=1)
2790DAY = timedelta(days=1)
2791# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2792DSTSTART = datetime(1, 4, 1, 2)
2793# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002794# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2795# being standard time on that day, there is no spelling in local time of
2796# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2797DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002798
2799class USTimeZone(tzinfo):
2800
2801 def __init__(self, hours, reprname, stdname, dstname):
2802 self.stdoffset = timedelta(hours=hours)
2803 self.reprname = reprname
2804 self.stdname = stdname
2805 self.dstname = dstname
2806
2807 def __repr__(self):
2808 return self.reprname
2809
2810 def tzname(self, dt):
2811 if self.dst(dt):
2812 return self.dstname
2813 else:
2814 return self.stdname
2815
2816 def utcoffset(self, dt):
2817 return self.stdoffset + self.dst(dt)
2818
2819 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002820 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002821 # An exception instead may be sensible here, in one or more of
2822 # the cases.
2823 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002824 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002825
2826 # Find first Sunday in April.
2827 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2828 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2829
2830 # Find last Sunday in October.
2831 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2832 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2833
Tim Peters621818b2002-12-29 23:44:49 +00002834 # Can't compare naive to aware objects, so strip the timezone from
2835 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002836 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002837 return HOUR
2838 else:
2839 return ZERO
2840
Tim Peters521fc152002-12-31 17:36:56 +00002841Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2842Central = USTimeZone(-6, "Central", "CST", "CDT")
2843Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2844Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002845utc_real = FixedOffset(0, "UTC", 0)
2846# For better test coverage, we want another flavor of UTC that's west of
2847# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002848utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002849
2850class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002851 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002852 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002853 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002854
Tim Peters0bf60bd2003-01-08 20:40:01 +00002855 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002856
Tim Peters521fc152002-12-31 17:36:56 +00002857 # Check a time that's inside DST.
2858 def checkinside(self, dt, tz, utc, dston, dstoff):
2859 self.assertEqual(dt.dst(), HOUR)
2860
2861 # Conversion to our own timezone is always an identity.
2862 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002863
2864 asutc = dt.astimezone(utc)
2865 there_and_back = asutc.astimezone(tz)
2866
2867 # Conversion to UTC and back isn't always an identity here,
2868 # because there are redundant spellings (in local time) of
2869 # UTC time when DST begins: the clock jumps from 1:59:59
2870 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2871 # make sense then. The classes above treat 2:MM:SS as
2872 # daylight time then (it's "after 2am"), really an alias
2873 # for 1:MM:SS standard time. The latter form is what
2874 # conversion back from UTC produces.
2875 if dt.date() == dston.date() and dt.hour == 2:
2876 # We're in the redundant hour, and coming back from
2877 # UTC gives the 1:MM:SS standard-time spelling.
2878 self.assertEqual(there_and_back + HOUR, dt)
2879 # Although during was considered to be in daylight
2880 # time, there_and_back is not.
2881 self.assertEqual(there_and_back.dst(), ZERO)
2882 # They're the same times in UTC.
2883 self.assertEqual(there_and_back.astimezone(utc),
2884 dt.astimezone(utc))
2885 else:
2886 # We're not in the redundant hour.
2887 self.assertEqual(dt, there_and_back)
2888
Tim Peters327098a2003-01-20 22:54:38 +00002889 # Because we have a redundant spelling when DST begins, there is
2890 # (unforunately) an hour when DST ends that can't be spelled at all in
2891 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2892 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2893 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2894 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2895 # expressed in local time. Nevertheless, we want conversion back
2896 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002897 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002898 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002899 if dt.date() == dstoff.date() and dt.hour == 0:
2900 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002901 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002902 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2903 nexthour_utc += HOUR
2904 nexthour_tz = nexthour_utc.astimezone(tz)
2905 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002906 else:
Tim Peters327098a2003-01-20 22:54:38 +00002907 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002908
2909 # Check a time that's outside DST.
2910 def checkoutside(self, dt, tz, utc):
2911 self.assertEqual(dt.dst(), ZERO)
2912
2913 # Conversion to our own timezone is always an identity.
2914 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002915
2916 # Converting to UTC and back is an identity too.
2917 asutc = dt.astimezone(utc)
2918 there_and_back = asutc.astimezone(tz)
2919 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002920
Tim Peters1024bf82002-12-30 17:09:40 +00002921 def convert_between_tz_and_utc(self, tz, utc):
2922 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002923 # Because 1:MM on the day DST ends is taken as being standard time,
2924 # there is no spelling in tz for the last hour of daylight time.
2925 # For purposes of the test, the last hour of DST is 0:MM, which is
2926 # taken as being daylight time (and 1:MM is taken as being standard
2927 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002928 dstoff = self.dstoff.replace(tzinfo=tz)
2929 for delta in (timedelta(weeks=13),
2930 DAY,
2931 HOUR,
2932 timedelta(minutes=1),
2933 timedelta(microseconds=1)):
2934
Tim Peters521fc152002-12-31 17:36:56 +00002935 self.checkinside(dston, tz, utc, dston, dstoff)
2936 for during in dston + delta, dstoff - delta:
2937 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002938
Tim Peters521fc152002-12-31 17:36:56 +00002939 self.checkoutside(dstoff, tz, utc)
2940 for outside in dston - delta, dstoff + delta:
2941 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002942
Tim Peters621818b2002-12-29 23:44:49 +00002943 def test_easy(self):
2944 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002945 self.convert_between_tz_and_utc(Eastern, utc_real)
2946 self.convert_between_tz_and_utc(Pacific, utc_real)
2947 self.convert_between_tz_and_utc(Eastern, utc_fake)
2948 self.convert_between_tz_and_utc(Pacific, utc_fake)
2949 # The next is really dancing near the edge. It works because
2950 # Pacific and Eastern are far enough apart that their "problem
2951 # hours" don't overlap.
2952 self.convert_between_tz_and_utc(Eastern, Pacific)
2953 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002954 # OTOH, these fail! Don't enable them. The difficulty is that
2955 # the edge case tests assume that every hour is representable in
2956 # the "utc" class. This is always true for a fixed-offset tzinfo
2957 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2958 # For these adjacent DST-aware time zones, the range of time offsets
2959 # tested ends up creating hours in the one that aren't representable
2960 # in the other. For the same reason, we would see failures in the
2961 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2962 # offset deltas in convert_between_tz_and_utc().
2963 #
2964 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2965 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002966
Tim Petersf3615152003-01-01 21:51:37 +00002967 def test_tricky(self):
2968 # 22:00 on day before daylight starts.
2969 fourback = self.dston - timedelta(hours=4)
2970 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002971 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002972 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2973 # 2", we should get the 3 spelling.
2974 # If we plug 22:00 the day before into Eastern, it "looks like std
2975 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2976 # to 22:00 lands on 2:00, which makes no sense in local time (the
2977 # local clock jumps from 1 to 3). The point here is to make sure we
2978 # get the 3 spelling.
2979 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002980 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002981 self.assertEqual(expected, got)
2982
2983 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2984 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002985 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002986 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2987 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2988 # spelling.
2989 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002990 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002991 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002992
Tim Petersadf64202003-01-04 06:03:15 +00002993 # Now on the day DST ends, we want "repeat an hour" behavior.
2994 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2995 # EST 23:MM 0:MM 1:MM 2:MM
2996 # EDT 0:MM 1:MM 2:MM 3:MM
2997 # wall 0:MM 1:MM 1:MM 2:MM against these
2998 for utc in utc_real, utc_fake:
2999 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003000 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003001 # Convert that to UTC.
3002 first_std_hour -= tz.utcoffset(None)
3003 # Adjust for possibly fake UTC.
3004 asutc = first_std_hour + utc.utcoffset(None)
3005 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3006 # tz=Eastern.
3007 asutcbase = asutc.replace(tzinfo=utc)
3008 for tzhour in (0, 1, 1, 2):
3009 expectedbase = self.dstoff.replace(hour=tzhour)
3010 for minute in 0, 30, 59:
3011 expected = expectedbase.replace(minute=minute)
3012 asutc = asutcbase.replace(minute=minute)
3013 astz = asutc.astimezone(tz)
3014 self.assertEqual(astz.replace(tzinfo=None), expected)
3015 asutcbase += HOUR
3016
3017
Tim Peters710fb152003-01-02 19:35:54 +00003018 def test_bogus_dst(self):
3019 class ok(tzinfo):
3020 def utcoffset(self, dt): return HOUR
3021 def dst(self, dt): return HOUR
3022
3023 now = self.theclass.now().replace(tzinfo=utc_real)
3024 # Doesn't blow up.
3025 now.astimezone(ok())
3026
3027 # Does blow up.
3028 class notok(ok):
3029 def dst(self, dt): return None
3030 self.assertRaises(ValueError, now.astimezone, notok())
3031
Tim Peters52dcce22003-01-23 16:36:11 +00003032 def test_fromutc(self):
3033 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3034 now = datetime.utcnow().replace(tzinfo=utc_real)
3035 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3036 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3037 enow = Eastern.fromutc(now) # doesn't blow up
3038 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3039 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3040 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3041
3042 # Always converts UTC to standard time.
3043 class FauxUSTimeZone(USTimeZone):
3044 def fromutc(self, dt):
3045 return dt + self.stdoffset
3046 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3047
3048 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3049 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3050 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3051
3052 # Check around DST start.
3053 start = self.dston.replace(hour=4, tzinfo=Eastern)
3054 fstart = start.replace(tzinfo=FEastern)
3055 for wall in 23, 0, 1, 3, 4, 5:
3056 expected = start.replace(hour=wall)
3057 if wall == 23:
3058 expected -= timedelta(days=1)
3059 got = Eastern.fromutc(start)
3060 self.assertEqual(expected, got)
3061
3062 expected = fstart + FEastern.stdoffset
3063 got = FEastern.fromutc(fstart)
3064 self.assertEqual(expected, got)
3065
3066 # Ensure astimezone() calls fromutc() too.
3067 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3068 self.assertEqual(expected, got)
3069
3070 start += HOUR
3071 fstart += HOUR
3072
3073 # Check around DST end.
3074 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3075 fstart = start.replace(tzinfo=FEastern)
3076 for wall in 0, 1, 1, 2, 3, 4:
3077 expected = start.replace(hour=wall)
3078 got = Eastern.fromutc(start)
3079 self.assertEqual(expected, got)
3080
3081 expected = fstart + FEastern.stdoffset
3082 got = FEastern.fromutc(fstart)
3083 self.assertEqual(expected, got)
3084
3085 # Ensure astimezone() calls fromutc() too.
3086 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3087 self.assertEqual(expected, got)
3088
3089 start += HOUR
3090 fstart += HOUR
3091
Tim Peters710fb152003-01-02 19:35:54 +00003092
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003093def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003094 allsuites = [unittest.makeSuite(klass, 'test')
3095 for klass in (TestModule,
3096 TestTZInfo,
3097 TestTimeDelta,
3098 TestDateOnly,
3099 TestDate,
3100 TestDateTime,
3101 TestTime,
3102 TestTimeTZ,
3103 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003104 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00003105 )
3106 ]
3107 return unittest.TestSuite(allsuites)
3108
3109def test_main():
3110 import gc
3111 import sys
3112
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003113 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003114 lastrc = None
3115 while True:
3116 test_support.run_suite(thesuite)
3117 if 1: # change to 0, under a debug build, for some leak detection
3118 break
3119 gc.collect()
3120 if gc.garbage:
3121 raise SystemError("gc.garbage not empty after test run: %r" %
3122 gc.garbage)
3123 if hasattr(sys, 'gettotalrefcount'):
3124 thisrc = sys.gettotalrefcount()
3125 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3126 if lastrc:
3127 print >> sys.stderr, 'delta:', thisrc - lastrc
3128 else:
3129 print >> sys.stderr
3130 lastrc = thisrc
3131
3132if __name__ == "__main__":
3133 test_main()