blob: cca0c9d9c59f96333e568e8552d68bf1bd20a38f [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")
834
835 self.assertRaises(TypeError, t.strftime) # needs an arg
836 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
837 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
838
839 # A naive object replaces %z and %Z w/ empty strings.
840 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
841
842 def test_resolution_info(self):
843 self.assert_(isinstance(self.theclass.min, self.theclass))
844 self.assert_(isinstance(self.theclass.max, self.theclass))
845 self.assert_(isinstance(self.theclass.resolution, timedelta))
846 self.assert_(self.theclass.max > self.theclass.min)
847
848 def test_extreme_timedelta(self):
849 big = self.theclass.max - self.theclass.min
850 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
851 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
852 # n == 315537897599999999 ~= 2**58.13
853 justasbig = timedelta(0, 0, n)
854 self.assertEqual(big, justasbig)
855 self.assertEqual(self.theclass.min + big, self.theclass.max)
856 self.assertEqual(self.theclass.max - big, self.theclass.min)
857
858 def test_timetuple(self):
859 for i in range(7):
860 # January 2, 1956 is a Monday (0)
861 d = self.theclass(1956, 1, 2+i)
862 t = d.timetuple()
863 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
864 # February 1, 1956 is a Wednesday (2)
865 d = self.theclass(1956, 2, 1+i)
866 t = d.timetuple()
867 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
868 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
869 # of the year.
870 d = self.theclass(1956, 3, 1+i)
871 t = d.timetuple()
872 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
873 self.assertEqual(t.tm_year, 1956)
874 self.assertEqual(t.tm_mon, 3)
875 self.assertEqual(t.tm_mday, 1+i)
876 self.assertEqual(t.tm_hour, 0)
877 self.assertEqual(t.tm_min, 0)
878 self.assertEqual(t.tm_sec, 0)
879 self.assertEqual(t.tm_wday, (3+i)%7)
880 self.assertEqual(t.tm_yday, 61+i)
881 self.assertEqual(t.tm_isdst, -1)
882
883 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000884 args = 6, 7, 23
885 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000886 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000887 green = pickler.dumps(orig, proto)
888 derived = unpickler.loads(green)
889 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000890
891 def test_compare(self):
892 t1 = self.theclass(2, 3, 4)
893 t2 = self.theclass(2, 3, 4)
894 self.failUnless(t1 == t2)
895 self.failUnless(t1 <= t2)
896 self.failUnless(t1 >= t2)
897 self.failUnless(not t1 != t2)
898 self.failUnless(not t1 < t2)
899 self.failUnless(not t1 > t2)
900 self.assertEqual(cmp(t1, t2), 0)
901 self.assertEqual(cmp(t2, t1), 0)
902
903 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
904 t2 = self.theclass(*args) # this is larger than t1
905 self.failUnless(t1 < t2)
906 self.failUnless(t2 > t1)
907 self.failUnless(t1 <= t2)
908 self.failUnless(t2 >= t1)
909 self.failUnless(t1 != t2)
910 self.failUnless(t2 != t1)
911 self.failUnless(not t1 == t2)
912 self.failUnless(not t2 == t1)
913 self.failUnless(not t1 > t2)
914 self.failUnless(not t2 < t1)
915 self.failUnless(not t1 >= t2)
916 self.failUnless(not t2 <= t1)
917 self.assertEqual(cmp(t1, t2), -1)
918 self.assertEqual(cmp(t2, t1), 1)
919
Tim Peters68124bb2003-02-08 03:46:31 +0000920 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000921 self.assertEqual(t1 == badarg, False)
922 self.assertEqual(t1 != badarg, True)
923 self.assertEqual(badarg == t1, False)
924 self.assertEqual(badarg != t1, True)
925
Tim Peters2a799bf2002-12-16 20:18:38 +0000926 self.assertRaises(TypeError, lambda: t1 < badarg)
927 self.assertRaises(TypeError, lambda: t1 > badarg)
928 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000929 self.assertRaises(TypeError, lambda: badarg <= t1)
930 self.assertRaises(TypeError, lambda: badarg < t1)
931 self.assertRaises(TypeError, lambda: badarg > t1)
932 self.assertRaises(TypeError, lambda: badarg >= t1)
933
Tim Peters8d81a012003-01-24 22:36:34 +0000934 def test_mixed_compare(self):
935 our = self.theclass(2000, 4, 5)
936 self.assertRaises(TypeError, cmp, our, 1)
937 self.assertRaises(TypeError, cmp, 1, our)
938
939 class AnotherDateTimeClass(object):
940 def __cmp__(self, other):
941 # Return "equal" so calling this can't be confused with
942 # compare-by-address (which never says "equal" for distinct
943 # objects).
944 return 0
945
946 # This still errors, because date and datetime comparison raise
947 # TypeError instead of NotImplemented when they don't know what to
948 # do, in order to stop comparison from falling back to the default
949 # compare-by-address.
950 their = AnotherDateTimeClass()
951 self.assertRaises(TypeError, cmp, our, their)
952 # Oops: The next stab raises TypeError in the C implementation,
953 # but not in the Python implementation of datetime. The difference
954 # is due to that the Python implementation defines __cmp__ but
955 # the C implementation defines tp_richcompare. This is more pain
956 # to fix than it's worth, so commenting out the test.
957 # self.assertEqual(cmp(their, our), 0)
958
959 # But date and datetime comparison return NotImplemented instead if the
960 # other object has a timetuple attr. This gives the other object a
961 # chance to do the comparison.
962 class Comparable(AnotherDateTimeClass):
963 def timetuple(self):
964 return ()
965
966 their = Comparable()
967 self.assertEqual(cmp(our, their), 0)
968 self.assertEqual(cmp(their, our), 0)
969 self.failUnless(our == their)
970 self.failUnless(their == our)
971
Tim Peters2a799bf2002-12-16 20:18:38 +0000972 def test_bool(self):
973 # All dates are considered true.
974 self.failUnless(self.theclass.min)
975 self.failUnless(self.theclass.max)
976
Tim Petersd6844152002-12-22 20:58:42 +0000977 def test_srftime_out_of_range(self):
978 # For nasty technical reasons, we can't handle years before 1900.
979 cls = self.theclass
980 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
981 for y in 1, 49, 51, 99, 100, 1000, 1899:
982 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000983
984 def test_replace(self):
985 cls = self.theclass
986 args = [1, 2, 3]
987 base = cls(*args)
988 self.assertEqual(base, base.replace())
989
990 i = 0
991 for name, newval in (("year", 2),
992 ("month", 3),
993 ("day", 4)):
994 newargs = args[:]
995 newargs[i] = newval
996 expected = cls(*newargs)
997 got = base.replace(**{name: newval})
998 self.assertEqual(expected, got)
999 i += 1
1000
1001 # Out of bounds.
1002 base = cls(2000, 2, 29)
1003 self.assertRaises(ValueError, base.replace, year=2001)
1004
Tim Petersa98924a2003-05-17 05:55:19 +00001005 def test_subclass_date(self):
1006
1007 class C(self.theclass):
1008 theAnswer = 42
1009
1010 def __new__(cls, *args, **kws):
1011 temp = kws.copy()
1012 extra = temp.pop('extra')
1013 result = self.theclass.__new__(cls, *args, **temp)
1014 result.extra = extra
1015 return result
1016
1017 def newmeth(self, start):
1018 return start + self.year + self.month
1019
1020 args = 2003, 4, 14
1021
1022 dt1 = self.theclass(*args)
1023 dt2 = C(*args, **{'extra': 7})
1024
1025 self.assertEqual(dt2.__class__, C)
1026 self.assertEqual(dt2.theAnswer, 42)
1027 self.assertEqual(dt2.extra, 7)
1028 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1029 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1030
Tim Peterseb1a4962003-05-17 02:25:20 +00001031
Tim Peters2a799bf2002-12-16 20:18:38 +00001032#############################################################################
1033# datetime tests
1034
1035class TestDateTime(TestDate):
1036
1037 theclass = datetime
1038
1039 def test_basic_attributes(self):
1040 dt = self.theclass(2002, 3, 1, 12, 0)
1041 self.assertEqual(dt.year, 2002)
1042 self.assertEqual(dt.month, 3)
1043 self.assertEqual(dt.day, 1)
1044 self.assertEqual(dt.hour, 12)
1045 self.assertEqual(dt.minute, 0)
1046 self.assertEqual(dt.second, 0)
1047 self.assertEqual(dt.microsecond, 0)
1048
1049 def test_basic_attributes_nonzero(self):
1050 # Make sure all attributes are non-zero so bugs in
1051 # bit-shifting access show up.
1052 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1053 self.assertEqual(dt.year, 2002)
1054 self.assertEqual(dt.month, 3)
1055 self.assertEqual(dt.day, 1)
1056 self.assertEqual(dt.hour, 12)
1057 self.assertEqual(dt.minute, 59)
1058 self.assertEqual(dt.second, 59)
1059 self.assertEqual(dt.microsecond, 8000)
1060
1061 def test_roundtrip(self):
1062 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1063 self.theclass.now()):
1064 # Verify dt -> string -> datetime identity.
1065 s = repr(dt)
1066 self.failUnless(s.startswith('datetime.'))
1067 s = s[9:]
1068 dt2 = eval(s)
1069 self.assertEqual(dt, dt2)
1070
1071 # Verify identity via reconstructing from pieces.
1072 dt2 = self.theclass(dt.year, dt.month, dt.day,
1073 dt.hour, dt.minute, dt.second,
1074 dt.microsecond)
1075 self.assertEqual(dt, dt2)
1076
1077 def test_isoformat(self):
1078 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1079 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1080 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1081 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1082 # str is ISO format with the separator forced to a blank.
1083 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1084
1085 t = self.theclass(2, 3, 2)
1086 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1087 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1088 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1089 # str is ISO format with the separator forced to a blank.
1090 self.assertEqual(str(t), "0002-03-02 00:00:00")
1091
1092 def test_more_ctime(self):
1093 # Test fields that TestDate doesn't touch.
1094 import time
1095
1096 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1097 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1098 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1099 # out. The difference is that t.ctime() produces " 2" for the day,
1100 # but platform ctime() produces "02" for the day. According to
1101 # C99, t.ctime() is correct here.
1102 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1103
1104 # So test a case where that difference doesn't matter.
1105 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1106 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1107
1108 def test_tz_independent_comparing(self):
1109 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1110 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1111 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1112 self.assertEqual(dt1, dt3)
1113 self.assert_(dt2 > dt3)
1114
1115 # Make sure comparison doesn't forget microseconds, and isn't done
1116 # via comparing a float timestamp (an IEEE double doesn't have enough
1117 # precision to span microsecond resolution across years 1 thru 9999,
1118 # so comparing via timestamp necessarily calls some distinct values
1119 # equal).
1120 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1121 us = timedelta(microseconds=1)
1122 dt2 = dt1 + us
1123 self.assertEqual(dt2 - dt1, us)
1124 self.assert_(dt1 < dt2)
1125
1126 def test_bad_constructor_arguments(self):
1127 # bad years
1128 self.theclass(MINYEAR, 1, 1) # no exception
1129 self.theclass(MAXYEAR, 1, 1) # no exception
1130 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1131 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1132 # bad months
1133 self.theclass(2000, 1, 1) # no exception
1134 self.theclass(2000, 12, 1) # no exception
1135 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1136 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1137 # bad days
1138 self.theclass(2000, 2, 29) # no exception
1139 self.theclass(2004, 2, 29) # no exception
1140 self.theclass(2400, 2, 29) # no exception
1141 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1142 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1143 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1144 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1145 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1146 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1147 # bad hours
1148 self.theclass(2000, 1, 31, 0) # no exception
1149 self.theclass(2000, 1, 31, 23) # no exception
1150 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1151 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1152 # bad minutes
1153 self.theclass(2000, 1, 31, 23, 0) # no exception
1154 self.theclass(2000, 1, 31, 23, 59) # no exception
1155 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1156 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1157 # bad seconds
1158 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1159 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1160 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1161 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1162 # bad microseconds
1163 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1164 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1165 self.assertRaises(ValueError, self.theclass,
1166 2000, 1, 31, 23, 59, 59, -1)
1167 self.assertRaises(ValueError, self.theclass,
1168 2000, 1, 31, 23, 59, 59,
1169 1000000)
1170
1171 def test_hash_equality(self):
1172 d = self.theclass(2000, 12, 31, 23, 30, 17)
1173 e = self.theclass(2000, 12, 31, 23, 30, 17)
1174 self.assertEqual(d, e)
1175 self.assertEqual(hash(d), hash(e))
1176
1177 dic = {d: 1}
1178 dic[e] = 2
1179 self.assertEqual(len(dic), 1)
1180 self.assertEqual(dic[d], 2)
1181 self.assertEqual(dic[e], 2)
1182
1183 d = self.theclass(2001, 1, 1, 0, 5, 17)
1184 e = self.theclass(2001, 1, 1, 0, 5, 17)
1185 self.assertEqual(d, e)
1186 self.assertEqual(hash(d), hash(e))
1187
1188 dic = {d: 1}
1189 dic[e] = 2
1190 self.assertEqual(len(dic), 1)
1191 self.assertEqual(dic[d], 2)
1192 self.assertEqual(dic[e], 2)
1193
1194 def test_computations(self):
1195 a = self.theclass(2002, 1, 31)
1196 b = self.theclass(1956, 1, 31)
1197 diff = a-b
1198 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1199 self.assertEqual(diff.seconds, 0)
1200 self.assertEqual(diff.microseconds, 0)
1201 a = self.theclass(2002, 3, 2, 17, 6)
1202 millisec = timedelta(0, 0, 1000)
1203 hour = timedelta(0, 3600)
1204 day = timedelta(1)
1205 week = timedelta(7)
1206 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1207 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1208 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1209 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1210 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1211 self.assertEqual(a - hour, a + -hour)
1212 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1213 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1214 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1215 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1216 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1217 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1218 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1219 self.assertEqual((a + week) - a, week)
1220 self.assertEqual((a + day) - a, day)
1221 self.assertEqual((a + hour) - a, hour)
1222 self.assertEqual((a + millisec) - a, millisec)
1223 self.assertEqual((a - week) - a, -week)
1224 self.assertEqual((a - day) - a, -day)
1225 self.assertEqual((a - hour) - a, -hour)
1226 self.assertEqual((a - millisec) - a, -millisec)
1227 self.assertEqual(a - (a + week), -week)
1228 self.assertEqual(a - (a + day), -day)
1229 self.assertEqual(a - (a + hour), -hour)
1230 self.assertEqual(a - (a + millisec), -millisec)
1231 self.assertEqual(a - (a - week), week)
1232 self.assertEqual(a - (a - day), day)
1233 self.assertEqual(a - (a - hour), hour)
1234 self.assertEqual(a - (a - millisec), millisec)
1235 self.assertEqual(a + (week + day + hour + millisec),
1236 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1237 self.assertEqual(a + (week + day + hour + millisec),
1238 (((a + week) + day) + hour) + millisec)
1239 self.assertEqual(a - (week + day + hour + millisec),
1240 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1241 self.assertEqual(a - (week + day + hour + millisec),
1242 (((a - week) - day) - hour) - millisec)
1243 # Add/sub ints, longs, floats should be illegal
1244 for i in 1, 1L, 1.0:
1245 self.assertRaises(TypeError, lambda: a+i)
1246 self.assertRaises(TypeError, lambda: a-i)
1247 self.assertRaises(TypeError, lambda: i+a)
1248 self.assertRaises(TypeError, lambda: i-a)
1249
1250 # delta - datetime is senseless.
1251 self.assertRaises(TypeError, lambda: day - a)
1252 # mixing datetime and (delta or datetime) via * or // is senseless
1253 self.assertRaises(TypeError, lambda: day * a)
1254 self.assertRaises(TypeError, lambda: a * day)
1255 self.assertRaises(TypeError, lambda: day // a)
1256 self.assertRaises(TypeError, lambda: a // day)
1257 self.assertRaises(TypeError, lambda: a * a)
1258 self.assertRaises(TypeError, lambda: a // a)
1259 # datetime + datetime is senseless
1260 self.assertRaises(TypeError, lambda: a + a)
1261
1262 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001263 args = 6, 7, 23, 20, 59, 1, 64**2
1264 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001265 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001266 green = pickler.dumps(orig, proto)
1267 derived = unpickler.loads(green)
1268 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001269
Guido van Rossum275666f2003-02-07 21:49:01 +00001270 def test_more_pickling(self):
1271 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1272 s = pickle.dumps(a)
1273 b = pickle.loads(s)
1274 self.assertEqual(b.year, 2003)
1275 self.assertEqual(b.month, 2)
1276 self.assertEqual(b.day, 7)
1277
Tim Peters2a799bf2002-12-16 20:18:38 +00001278 def test_more_compare(self):
1279 # The test_compare() inherited from TestDate covers the error cases.
1280 # We just want to test lexicographic ordering on the members datetime
1281 # has that date lacks.
1282 args = [2000, 11, 29, 20, 58, 16, 999998]
1283 t1 = self.theclass(*args)
1284 t2 = self.theclass(*args)
1285 self.failUnless(t1 == t2)
1286 self.failUnless(t1 <= t2)
1287 self.failUnless(t1 >= t2)
1288 self.failUnless(not t1 != t2)
1289 self.failUnless(not t1 < t2)
1290 self.failUnless(not t1 > t2)
1291 self.assertEqual(cmp(t1, t2), 0)
1292 self.assertEqual(cmp(t2, t1), 0)
1293
1294 for i in range(len(args)):
1295 newargs = args[:]
1296 newargs[i] = args[i] + 1
1297 t2 = self.theclass(*newargs) # this is larger than t1
1298 self.failUnless(t1 < t2)
1299 self.failUnless(t2 > t1)
1300 self.failUnless(t1 <= t2)
1301 self.failUnless(t2 >= t1)
1302 self.failUnless(t1 != t2)
1303 self.failUnless(t2 != t1)
1304 self.failUnless(not t1 == t2)
1305 self.failUnless(not t2 == t1)
1306 self.failUnless(not t1 > t2)
1307 self.failUnless(not t2 < t1)
1308 self.failUnless(not t1 >= t2)
1309 self.failUnless(not t2 <= t1)
1310 self.assertEqual(cmp(t1, t2), -1)
1311 self.assertEqual(cmp(t2, t1), 1)
1312
1313
1314 # A helper for timestamp constructor tests.
1315 def verify_field_equality(self, expected, got):
1316 self.assertEqual(expected.tm_year, got.year)
1317 self.assertEqual(expected.tm_mon, got.month)
1318 self.assertEqual(expected.tm_mday, got.day)
1319 self.assertEqual(expected.tm_hour, got.hour)
1320 self.assertEqual(expected.tm_min, got.minute)
1321 self.assertEqual(expected.tm_sec, got.second)
1322
1323 def test_fromtimestamp(self):
1324 import time
1325
1326 ts = time.time()
1327 expected = time.localtime(ts)
1328 got = self.theclass.fromtimestamp(ts)
1329 self.verify_field_equality(expected, got)
1330
1331 def test_utcfromtimestamp(self):
1332 import time
1333
1334 ts = time.time()
1335 expected = time.gmtime(ts)
1336 got = self.theclass.utcfromtimestamp(ts)
1337 self.verify_field_equality(expected, got)
1338
1339 def test_utcnow(self):
1340 import time
1341
1342 # Call it a success if utcnow() and utcfromtimestamp() are within
1343 # a second of each other.
1344 tolerance = timedelta(seconds=1)
1345 for dummy in range(3):
1346 from_now = self.theclass.utcnow()
1347 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1348 if abs(from_timestamp - from_now) <= tolerance:
1349 break
1350 # Else try again a few times.
1351 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1352
1353 def test_more_timetuple(self):
1354 # This tests fields beyond those tested by the TestDate.test_timetuple.
1355 t = self.theclass(2004, 12, 31, 6, 22, 33)
1356 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1357 self.assertEqual(t.timetuple(),
1358 (t.year, t.month, t.day,
1359 t.hour, t.minute, t.second,
1360 t.weekday(),
1361 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1362 -1))
1363 tt = t.timetuple()
1364 self.assertEqual(tt.tm_year, t.year)
1365 self.assertEqual(tt.tm_mon, t.month)
1366 self.assertEqual(tt.tm_mday, t.day)
1367 self.assertEqual(tt.tm_hour, t.hour)
1368 self.assertEqual(tt.tm_min, t.minute)
1369 self.assertEqual(tt.tm_sec, t.second)
1370 self.assertEqual(tt.tm_wday, t.weekday())
1371 self.assertEqual(tt.tm_yday, t.toordinal() -
1372 date(t.year, 1, 1).toordinal() + 1)
1373 self.assertEqual(tt.tm_isdst, -1)
1374
1375 def test_more_strftime(self):
1376 # This tests fields beyond those tested by the TestDate.test_strftime.
1377 t = self.theclass(2004, 12, 31, 6, 22, 33)
1378 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1379 "12 31 04 33 22 06 366")
1380
1381 def test_extract(self):
1382 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1383 self.assertEqual(dt.date(), date(2002, 3, 4))
1384 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1385
1386 def test_combine(self):
1387 d = date(2002, 3, 4)
1388 t = time(18, 45, 3, 1234)
1389 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1390 combine = self.theclass.combine
1391 dt = combine(d, t)
1392 self.assertEqual(dt, expected)
1393
1394 dt = combine(time=t, date=d)
1395 self.assertEqual(dt, expected)
1396
1397 self.assertEqual(d, dt.date())
1398 self.assertEqual(t, dt.time())
1399 self.assertEqual(dt, combine(dt.date(), dt.time()))
1400
1401 self.assertRaises(TypeError, combine) # need an arg
1402 self.assertRaises(TypeError, combine, d) # need two args
1403 self.assertRaises(TypeError, combine, t, d) # args reversed
1404 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1405 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1406
Tim Peters12bf3392002-12-24 05:41:27 +00001407 def test_replace(self):
1408 cls = self.theclass
1409 args = [1, 2, 3, 4, 5, 6, 7]
1410 base = cls(*args)
1411 self.assertEqual(base, base.replace())
1412
1413 i = 0
1414 for name, newval in (("year", 2),
1415 ("month", 3),
1416 ("day", 4),
1417 ("hour", 5),
1418 ("minute", 6),
1419 ("second", 7),
1420 ("microsecond", 8)):
1421 newargs = args[:]
1422 newargs[i] = newval
1423 expected = cls(*newargs)
1424 got = base.replace(**{name: newval})
1425 self.assertEqual(expected, got)
1426 i += 1
1427
1428 # Out of bounds.
1429 base = cls(2000, 2, 29)
1430 self.assertRaises(ValueError, base.replace, year=2001)
1431
Tim Peters80475bb2002-12-25 07:40:55 +00001432 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001433 # Pretty boring! The TZ test is more interesting here. astimezone()
1434 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001435 dt = self.theclass.now()
1436 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001437 self.assertRaises(TypeError, dt.astimezone) # not enough args
1438 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1439 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001440 self.assertRaises(ValueError, dt.astimezone, f) # naive
1441 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001442
Tim Peters52dcce22003-01-23 16:36:11 +00001443 class Bogus(tzinfo):
1444 def utcoffset(self, dt): return None
1445 def dst(self, dt): return timedelta(0)
1446 bog = Bogus()
1447 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1448
1449 class AlsoBogus(tzinfo):
1450 def utcoffset(self, dt): return timedelta(0)
1451 def dst(self, dt): return None
1452 alsobog = AlsoBogus()
1453 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001454
Tim Petersa98924a2003-05-17 05:55:19 +00001455 def test_subclass_datetime(self):
1456
1457 class C(self.theclass):
1458 theAnswer = 42
1459
1460 def __new__(cls, *args, **kws):
1461 temp = kws.copy()
1462 extra = temp.pop('extra')
1463 result = self.theclass.__new__(cls, *args, **temp)
1464 result.extra = extra
1465 return result
1466
1467 def newmeth(self, start):
1468 return start + self.year + self.month + self.second
1469
1470 args = 2003, 4, 14, 12, 13, 41
1471
1472 dt1 = self.theclass(*args)
1473 dt2 = C(*args, **{'extra': 7})
1474
1475 self.assertEqual(dt2.__class__, C)
1476 self.assertEqual(dt2.theAnswer, 42)
1477 self.assertEqual(dt2.extra, 7)
1478 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1479 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1480 dt1.second - 7)
1481
Tim Peters07534a62003-02-07 22:50:28 +00001482class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001483
1484 theclass = time
1485
1486 def test_basic_attributes(self):
1487 t = self.theclass(12, 0)
1488 self.assertEqual(t.hour, 12)
1489 self.assertEqual(t.minute, 0)
1490 self.assertEqual(t.second, 0)
1491 self.assertEqual(t.microsecond, 0)
1492
1493 def test_basic_attributes_nonzero(self):
1494 # Make sure all attributes are non-zero so bugs in
1495 # bit-shifting access show up.
1496 t = self.theclass(12, 59, 59, 8000)
1497 self.assertEqual(t.hour, 12)
1498 self.assertEqual(t.minute, 59)
1499 self.assertEqual(t.second, 59)
1500 self.assertEqual(t.microsecond, 8000)
1501
1502 def test_roundtrip(self):
1503 t = self.theclass(1, 2, 3, 4)
1504
1505 # Verify t -> string -> time identity.
1506 s = repr(t)
1507 self.failUnless(s.startswith('datetime.'))
1508 s = s[9:]
1509 t2 = eval(s)
1510 self.assertEqual(t, t2)
1511
1512 # Verify identity via reconstructing from pieces.
1513 t2 = self.theclass(t.hour, t.minute, t.second,
1514 t.microsecond)
1515 self.assertEqual(t, t2)
1516
1517 def test_comparing(self):
1518 args = [1, 2, 3, 4]
1519 t1 = self.theclass(*args)
1520 t2 = self.theclass(*args)
1521 self.failUnless(t1 == t2)
1522 self.failUnless(t1 <= t2)
1523 self.failUnless(t1 >= t2)
1524 self.failUnless(not t1 != t2)
1525 self.failUnless(not t1 < t2)
1526 self.failUnless(not t1 > t2)
1527 self.assertEqual(cmp(t1, t2), 0)
1528 self.assertEqual(cmp(t2, t1), 0)
1529
1530 for i in range(len(args)):
1531 newargs = args[:]
1532 newargs[i] = args[i] + 1
1533 t2 = self.theclass(*newargs) # this is larger than t1
1534 self.failUnless(t1 < t2)
1535 self.failUnless(t2 > t1)
1536 self.failUnless(t1 <= t2)
1537 self.failUnless(t2 >= t1)
1538 self.failUnless(t1 != t2)
1539 self.failUnless(t2 != t1)
1540 self.failUnless(not t1 == t2)
1541 self.failUnless(not t2 == t1)
1542 self.failUnless(not t1 > t2)
1543 self.failUnless(not t2 < t1)
1544 self.failUnless(not t1 >= t2)
1545 self.failUnless(not t2 <= t1)
1546 self.assertEqual(cmp(t1, t2), -1)
1547 self.assertEqual(cmp(t2, t1), 1)
1548
Tim Peters68124bb2003-02-08 03:46:31 +00001549 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001550 self.assertEqual(t1 == badarg, False)
1551 self.assertEqual(t1 != badarg, True)
1552 self.assertEqual(badarg == t1, False)
1553 self.assertEqual(badarg != t1, True)
1554
Tim Peters2a799bf2002-12-16 20:18:38 +00001555 self.assertRaises(TypeError, lambda: t1 <= badarg)
1556 self.assertRaises(TypeError, lambda: t1 < badarg)
1557 self.assertRaises(TypeError, lambda: t1 > badarg)
1558 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001559 self.assertRaises(TypeError, lambda: badarg <= t1)
1560 self.assertRaises(TypeError, lambda: badarg < t1)
1561 self.assertRaises(TypeError, lambda: badarg > t1)
1562 self.assertRaises(TypeError, lambda: badarg >= t1)
1563
1564 def test_bad_constructor_arguments(self):
1565 # bad hours
1566 self.theclass(0, 0) # no exception
1567 self.theclass(23, 0) # no exception
1568 self.assertRaises(ValueError, self.theclass, -1, 0)
1569 self.assertRaises(ValueError, self.theclass, 24, 0)
1570 # bad minutes
1571 self.theclass(23, 0) # no exception
1572 self.theclass(23, 59) # no exception
1573 self.assertRaises(ValueError, self.theclass, 23, -1)
1574 self.assertRaises(ValueError, self.theclass, 23, 60)
1575 # bad seconds
1576 self.theclass(23, 59, 0) # no exception
1577 self.theclass(23, 59, 59) # no exception
1578 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1579 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1580 # bad microseconds
1581 self.theclass(23, 59, 59, 0) # no exception
1582 self.theclass(23, 59, 59, 999999) # no exception
1583 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1584 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1585
1586 def test_hash_equality(self):
1587 d = self.theclass(23, 30, 17)
1588 e = self.theclass(23, 30, 17)
1589 self.assertEqual(d, e)
1590 self.assertEqual(hash(d), hash(e))
1591
1592 dic = {d: 1}
1593 dic[e] = 2
1594 self.assertEqual(len(dic), 1)
1595 self.assertEqual(dic[d], 2)
1596 self.assertEqual(dic[e], 2)
1597
1598 d = self.theclass(0, 5, 17)
1599 e = self.theclass(0, 5, 17)
1600 self.assertEqual(d, e)
1601 self.assertEqual(hash(d), hash(e))
1602
1603 dic = {d: 1}
1604 dic[e] = 2
1605 self.assertEqual(len(dic), 1)
1606 self.assertEqual(dic[d], 2)
1607 self.assertEqual(dic[e], 2)
1608
1609 def test_isoformat(self):
1610 t = self.theclass(4, 5, 1, 123)
1611 self.assertEqual(t.isoformat(), "04:05:01.000123")
1612 self.assertEqual(t.isoformat(), str(t))
1613
1614 t = self.theclass()
1615 self.assertEqual(t.isoformat(), "00:00:00")
1616 self.assertEqual(t.isoformat(), str(t))
1617
1618 t = self.theclass(microsecond=1)
1619 self.assertEqual(t.isoformat(), "00:00:00.000001")
1620 self.assertEqual(t.isoformat(), str(t))
1621
1622 t = self.theclass(microsecond=10)
1623 self.assertEqual(t.isoformat(), "00:00:00.000010")
1624 self.assertEqual(t.isoformat(), str(t))
1625
1626 t = self.theclass(microsecond=100)
1627 self.assertEqual(t.isoformat(), "00:00:00.000100")
1628 self.assertEqual(t.isoformat(), str(t))
1629
1630 t = self.theclass(microsecond=1000)
1631 self.assertEqual(t.isoformat(), "00:00:00.001000")
1632 self.assertEqual(t.isoformat(), str(t))
1633
1634 t = self.theclass(microsecond=10000)
1635 self.assertEqual(t.isoformat(), "00:00:00.010000")
1636 self.assertEqual(t.isoformat(), str(t))
1637
1638 t = self.theclass(microsecond=100000)
1639 self.assertEqual(t.isoformat(), "00:00:00.100000")
1640 self.assertEqual(t.isoformat(), str(t))
1641
1642 def test_strftime(self):
1643 t = self.theclass(1, 2, 3, 4)
1644 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1645 # A naive object replaces %z and %Z with empty strings.
1646 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1647
1648 def test_str(self):
1649 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1650 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1651 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1652 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1653 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1654
1655 def test_repr(self):
1656 name = 'datetime.' + self.theclass.__name__
1657 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1658 "%s(1, 2, 3, 4)" % name)
1659 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1660 "%s(10, 2, 3, 4000)" % name)
1661 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1662 "%s(0, 2, 3, 400000)" % name)
1663 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1664 "%s(12, 2, 3)" % name)
1665 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1666 "%s(23, 15)" % name)
1667
1668 def test_resolution_info(self):
1669 self.assert_(isinstance(self.theclass.min, self.theclass))
1670 self.assert_(isinstance(self.theclass.max, self.theclass))
1671 self.assert_(isinstance(self.theclass.resolution, timedelta))
1672 self.assert_(self.theclass.max > self.theclass.min)
1673
1674 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001675 args = 20, 59, 16, 64**2
1676 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001677 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001678 green = pickler.dumps(orig, proto)
1679 derived = unpickler.loads(green)
1680 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001681
1682 def test_bool(self):
1683 cls = self.theclass
1684 self.failUnless(cls(1))
1685 self.failUnless(cls(0, 1))
1686 self.failUnless(cls(0, 0, 1))
1687 self.failUnless(cls(0, 0, 0, 1))
1688 self.failUnless(not cls(0))
1689 self.failUnless(not cls())
1690
Tim Peters12bf3392002-12-24 05:41:27 +00001691 def test_replace(self):
1692 cls = self.theclass
1693 args = [1, 2, 3, 4]
1694 base = cls(*args)
1695 self.assertEqual(base, base.replace())
1696
1697 i = 0
1698 for name, newval in (("hour", 5),
1699 ("minute", 6),
1700 ("second", 7),
1701 ("microsecond", 8)):
1702 newargs = args[:]
1703 newargs[i] = newval
1704 expected = cls(*newargs)
1705 got = base.replace(**{name: newval})
1706 self.assertEqual(expected, got)
1707 i += 1
1708
1709 # Out of bounds.
1710 base = cls(1)
1711 self.assertRaises(ValueError, base.replace, hour=24)
1712 self.assertRaises(ValueError, base.replace, minute=-1)
1713 self.assertRaises(ValueError, base.replace, second=100)
1714 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1715
Tim Petersa98924a2003-05-17 05:55:19 +00001716 def test_subclass_time(self):
1717
1718 class C(self.theclass):
1719 theAnswer = 42
1720
1721 def __new__(cls, *args, **kws):
1722 temp = kws.copy()
1723 extra = temp.pop('extra')
1724 result = self.theclass.__new__(cls, *args, **temp)
1725 result.extra = extra
1726 return result
1727
1728 def newmeth(self, start):
1729 return start + self.hour + self.second
1730
1731 args = 4, 5, 6
1732
1733 dt1 = self.theclass(*args)
1734 dt2 = C(*args, **{'extra': 7})
1735
1736 self.assertEqual(dt2.__class__, C)
1737 self.assertEqual(dt2.theAnswer, 42)
1738 self.assertEqual(dt2.extra, 7)
1739 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1740 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1741
Tim Peters855fe882002-12-22 03:43:39 +00001742# A mixin for classes with a tzinfo= argument. Subclasses must define
1743# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001744# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001745class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001746
Tim Petersbad8ff02002-12-30 20:52:32 +00001747 def test_argument_passing(self):
1748 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001749 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001750 class introspective(tzinfo):
1751 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001752 def utcoffset(self, dt):
1753 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001754 dst = utcoffset
1755
1756 obj = cls(1, 2, 3, tzinfo=introspective())
1757
Tim Peters0bf60bd2003-01-08 20:40:01 +00001758 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001759 self.assertEqual(obj.tzname(), expected)
1760
Tim Peters0bf60bd2003-01-08 20:40:01 +00001761 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001762 self.assertEqual(obj.utcoffset(), expected)
1763 self.assertEqual(obj.dst(), expected)
1764
Tim Peters855fe882002-12-22 03:43:39 +00001765 def test_bad_tzinfo_classes(self):
1766 cls = self.theclass
1767 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001768
Tim Peters855fe882002-12-22 03:43:39 +00001769 class NiceTry(object):
1770 def __init__(self): pass
1771 def utcoffset(self, dt): pass
1772 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1773
1774 class BetterTry(tzinfo):
1775 def __init__(self): pass
1776 def utcoffset(self, dt): pass
1777 b = BetterTry()
1778 t = cls(1, 1, 1, tzinfo=b)
1779 self.failUnless(t.tzinfo is b)
1780
1781 def test_utc_offset_out_of_bounds(self):
1782 class Edgy(tzinfo):
1783 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001784 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001785 def utcoffset(self, dt):
1786 return self.offset
1787
1788 cls = self.theclass
1789 for offset, legit in ((-1440, False),
1790 (-1439, True),
1791 (1439, True),
1792 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001793 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001794 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001795 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001796 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001797 else:
1798 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001799 if legit:
1800 aofs = abs(offset)
1801 h, m = divmod(aofs, 60)
1802 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001803 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001804 t = t.timetz()
1805 self.assertEqual(str(t), "01:02:03" + tag)
1806 else:
1807 self.assertRaises(ValueError, str, t)
1808
1809 def test_tzinfo_classes(self):
1810 cls = self.theclass
1811 class C1(tzinfo):
1812 def utcoffset(self, dt): return None
1813 def dst(self, dt): return None
1814 def tzname(self, dt): return None
1815 for t in (cls(1, 1, 1),
1816 cls(1, 1, 1, tzinfo=None),
1817 cls(1, 1, 1, tzinfo=C1())):
1818 self.failUnless(t.utcoffset() is None)
1819 self.failUnless(t.dst() is None)
1820 self.failUnless(t.tzname() is None)
1821
Tim Peters855fe882002-12-22 03:43:39 +00001822 class C3(tzinfo):
1823 def utcoffset(self, dt): return timedelta(minutes=-1439)
1824 def dst(self, dt): return timedelta(minutes=1439)
1825 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001826 t = cls(1, 1, 1, tzinfo=C3())
1827 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1828 self.assertEqual(t.dst(), timedelta(minutes=1439))
1829 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001830
1831 # Wrong types.
1832 class C4(tzinfo):
1833 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001834 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001835 def tzname(self, dt): return 0
1836 t = cls(1, 1, 1, tzinfo=C4())
1837 self.assertRaises(TypeError, t.utcoffset)
1838 self.assertRaises(TypeError, t.dst)
1839 self.assertRaises(TypeError, t.tzname)
1840
1841 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001842 class C6(tzinfo):
1843 def utcoffset(self, dt): return timedelta(hours=-24)
1844 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001845 t = cls(1, 1, 1, tzinfo=C6())
1846 self.assertRaises(ValueError, t.utcoffset)
1847 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001848
1849 # Not a whole number of minutes.
1850 class C7(tzinfo):
1851 def utcoffset(self, dt): return timedelta(seconds=61)
1852 def dst(self, dt): return timedelta(microseconds=-81)
1853 t = cls(1, 1, 1, tzinfo=C7())
1854 self.assertRaises(ValueError, t.utcoffset)
1855 self.assertRaises(ValueError, t.dst)
1856
Tim Peters4c0db782002-12-26 05:01:19 +00001857 def test_aware_compare(self):
1858 cls = self.theclass
1859
Tim Peters60c76e42002-12-27 00:41:11 +00001860 # Ensure that utcoffset() gets ignored if the comparands have
1861 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001862 class OperandDependentOffset(tzinfo):
1863 def utcoffset(self, t):
1864 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001865 # d0 and d1 equal after adjustment
1866 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001867 else:
Tim Peters397301e2003-01-02 21:28:08 +00001868 # d2 off in the weeds
1869 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001870
1871 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1872 d0 = base.replace(minute=3)
1873 d1 = base.replace(minute=9)
1874 d2 = base.replace(minute=11)
1875 for x in d0, d1, d2:
1876 for y in d0, d1, d2:
1877 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001878 expected = cmp(x.minute, y.minute)
1879 self.assertEqual(got, expected)
1880
1881 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001882 # Note that a time can't actually have an operand-depedent offset,
1883 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1884 # so skip this test for time.
1885 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001886 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1887 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1888 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1889 for x in d0, d1, d2:
1890 for y in d0, d1, d2:
1891 got = cmp(x, y)
1892 if (x is d0 or x is d1) and (y is d0 or y is d1):
1893 expected = 0
1894 elif x is y is d2:
1895 expected = 0
1896 elif x is d2:
1897 expected = -1
1898 else:
1899 assert y is d2
1900 expected = 1
1901 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00001902
Tim Peters855fe882002-12-22 03:43:39 +00001903
Tim Peters0bf60bd2003-01-08 20:40:01 +00001904# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00001905class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001906 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00001907
1908 def test_empty(self):
1909 t = self.theclass()
1910 self.assertEqual(t.hour, 0)
1911 self.assertEqual(t.minute, 0)
1912 self.assertEqual(t.second, 0)
1913 self.assertEqual(t.microsecond, 0)
1914 self.failUnless(t.tzinfo is None)
1915
Tim Peters2a799bf2002-12-16 20:18:38 +00001916 def test_zones(self):
1917 est = FixedOffset(-300, "EST", 1)
1918 utc = FixedOffset(0, "UTC", -2)
1919 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001920 t1 = time( 7, 47, tzinfo=est)
1921 t2 = time(12, 47, tzinfo=utc)
1922 t3 = time(13, 47, tzinfo=met)
1923 t4 = time(microsecond=40)
1924 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00001925
1926 self.assertEqual(t1.tzinfo, est)
1927 self.assertEqual(t2.tzinfo, utc)
1928 self.assertEqual(t3.tzinfo, met)
1929 self.failUnless(t4.tzinfo is None)
1930 self.assertEqual(t5.tzinfo, utc)
1931
Tim Peters855fe882002-12-22 03:43:39 +00001932 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
1933 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
1934 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00001935 self.failUnless(t4.utcoffset() is None)
1936 self.assertRaises(TypeError, t1.utcoffset, "no args")
1937
1938 self.assertEqual(t1.tzname(), "EST")
1939 self.assertEqual(t2.tzname(), "UTC")
1940 self.assertEqual(t3.tzname(), "MET")
1941 self.failUnless(t4.tzname() is None)
1942 self.assertRaises(TypeError, t1.tzname, "no args")
1943
Tim Peters855fe882002-12-22 03:43:39 +00001944 self.assertEqual(t1.dst(), timedelta(minutes=1))
1945 self.assertEqual(t2.dst(), timedelta(minutes=-2))
1946 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00001947 self.failUnless(t4.dst() is None)
1948 self.assertRaises(TypeError, t1.dst, "no args")
1949
1950 self.assertEqual(hash(t1), hash(t2))
1951 self.assertEqual(hash(t1), hash(t3))
1952 self.assertEqual(hash(t2), hash(t3))
1953
1954 self.assertEqual(t1, t2)
1955 self.assertEqual(t1, t3)
1956 self.assertEqual(t2, t3)
1957 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
1958 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
1959 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
1960
1961 self.assertEqual(str(t1), "07:47:00-05:00")
1962 self.assertEqual(str(t2), "12:47:00+00:00")
1963 self.assertEqual(str(t3), "13:47:00+01:00")
1964 self.assertEqual(str(t4), "00:00:00.000040")
1965 self.assertEqual(str(t5), "00:00:00.000040+00:00")
1966
1967 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
1968 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
1969 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
1970 self.assertEqual(t4.isoformat(), "00:00:00.000040")
1971 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
1972
Tim Peters0bf60bd2003-01-08 20:40:01 +00001973 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00001974 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
1975 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
1976 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
1977 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
1978 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
1979
1980 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
1981 "07:47:00 %Z=EST %z=-0500")
1982 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
1983 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
1984
1985 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00001986 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00001987 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
1988 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
1989
Tim Petersb92bb712002-12-21 17:44:07 +00001990 # Check that an invalid tzname result raises an exception.
1991 class Badtzname(tzinfo):
1992 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00001993 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00001994 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
1995 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00001996
1997 def test_hash_edge_cases(self):
1998 # Offsets that overflow a basic time.
1999 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2000 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2001 self.assertEqual(hash(t1), hash(t2))
2002
2003 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2004 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2005 self.assertEqual(hash(t1), hash(t2))
2006
Tim Peters2a799bf2002-12-16 20:18:38 +00002007 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002008 # Try one without a tzinfo.
2009 args = 20, 59, 16, 64**2
2010 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002011 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002012 green = pickler.dumps(orig, proto)
2013 derived = unpickler.loads(green)
2014 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002015
2016 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002017 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002018 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002019 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002020 green = pickler.dumps(orig, proto)
2021 derived = unpickler.loads(green)
2022 self.assertEqual(orig, derived)
2023 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2024 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2025 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002026
2027 def test_more_bool(self):
2028 # Test cases with non-None tzinfo.
2029 cls = self.theclass
2030
2031 t = cls(0, tzinfo=FixedOffset(-300, ""))
2032 self.failUnless(t)
2033
2034 t = cls(5, tzinfo=FixedOffset(-300, ""))
2035 self.failUnless(t)
2036
2037 t = cls(5, tzinfo=FixedOffset(300, ""))
2038 self.failUnless(not t)
2039
2040 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2041 self.failUnless(not t)
2042
2043 # Mostly ensuring this doesn't overflow internally.
2044 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2045 self.failUnless(t)
2046
2047 # But this should yield a value error -- the utcoffset is bogus.
2048 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2049 self.assertRaises(ValueError, lambda: bool(t))
2050
2051 # Likewise.
2052 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2053 self.assertRaises(ValueError, lambda: bool(t))
2054
Tim Peters12bf3392002-12-24 05:41:27 +00002055 def test_replace(self):
2056 cls = self.theclass
2057 z100 = FixedOffset(100, "+100")
2058 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2059 args = [1, 2, 3, 4, z100]
2060 base = cls(*args)
2061 self.assertEqual(base, base.replace())
2062
2063 i = 0
2064 for name, newval in (("hour", 5),
2065 ("minute", 6),
2066 ("second", 7),
2067 ("microsecond", 8),
2068 ("tzinfo", zm200)):
2069 newargs = args[:]
2070 newargs[i] = newval
2071 expected = cls(*newargs)
2072 got = base.replace(**{name: newval})
2073 self.assertEqual(expected, got)
2074 i += 1
2075
2076 # Ensure we can get rid of a tzinfo.
2077 self.assertEqual(base.tzname(), "+100")
2078 base2 = base.replace(tzinfo=None)
2079 self.failUnless(base2.tzinfo is None)
2080 self.failUnless(base2.tzname() is None)
2081
2082 # Ensure we can add one.
2083 base3 = base2.replace(tzinfo=z100)
2084 self.assertEqual(base, base3)
2085 self.failUnless(base.tzinfo is base3.tzinfo)
2086
2087 # Out of bounds.
2088 base = cls(1)
2089 self.assertRaises(ValueError, base.replace, hour=24)
2090 self.assertRaises(ValueError, base.replace, minute=-1)
2091 self.assertRaises(ValueError, base.replace, second=100)
2092 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2093
Tim Peters60c76e42002-12-27 00:41:11 +00002094 def test_mixed_compare(self):
2095 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002096 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002097 self.assertEqual(t1, t2)
2098 t2 = t2.replace(tzinfo=None)
2099 self.assertEqual(t1, t2)
2100 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2101 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002102 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2103 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002104
Tim Peters0bf60bd2003-01-08 20:40:01 +00002105 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002106 class Varies(tzinfo):
2107 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002108 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002109 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002110 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002111 return self.offset
2112
2113 v = Varies()
2114 t1 = t2.replace(tzinfo=v)
2115 t2 = t2.replace(tzinfo=v)
2116 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2117 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2118 self.assertEqual(t1, t2)
2119
2120 # But if they're not identical, it isn't ignored.
2121 t2 = t2.replace(tzinfo=Varies())
2122 self.failUnless(t1 < t2) # t1's offset counter still going up
2123
Tim Petersa98924a2003-05-17 05:55:19 +00002124 def test_subclass_timetz(self):
2125
2126 class C(self.theclass):
2127 theAnswer = 42
2128
2129 def __new__(cls, *args, **kws):
2130 temp = kws.copy()
2131 extra = temp.pop('extra')
2132 result = self.theclass.__new__(cls, *args, **temp)
2133 result.extra = extra
2134 return result
2135
2136 def newmeth(self, start):
2137 return start + self.hour + self.second
2138
2139 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2140
2141 dt1 = self.theclass(*args)
2142 dt2 = C(*args, **{'extra': 7})
2143
2144 self.assertEqual(dt2.__class__, C)
2145 self.assertEqual(dt2.theAnswer, 42)
2146 self.assertEqual(dt2.extra, 7)
2147 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2148 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2149
Tim Peters4c0db782002-12-26 05:01:19 +00002150
Tim Peters0bf60bd2003-01-08 20:40:01 +00002151# Testing datetime objects with a non-None tzinfo.
2152
Tim Peters855fe882002-12-22 03:43:39 +00002153class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002154 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002155
2156 def test_trivial(self):
2157 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2158 self.assertEqual(dt.year, 1)
2159 self.assertEqual(dt.month, 2)
2160 self.assertEqual(dt.day, 3)
2161 self.assertEqual(dt.hour, 4)
2162 self.assertEqual(dt.minute, 5)
2163 self.assertEqual(dt.second, 6)
2164 self.assertEqual(dt.microsecond, 7)
2165 self.assertEqual(dt.tzinfo, None)
2166
2167 def test_even_more_compare(self):
2168 # The test_compare() and test_more_compare() inherited from TestDate
2169 # and TestDateTime covered non-tzinfo cases.
2170
2171 # Smallest possible after UTC adjustment.
2172 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2173 # Largest possible after UTC adjustment.
2174 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2175 tzinfo=FixedOffset(-1439, ""))
2176
2177 # Make sure those compare correctly, and w/o overflow.
2178 self.failUnless(t1 < t2)
2179 self.failUnless(t1 != t2)
2180 self.failUnless(t2 > t1)
2181
2182 self.failUnless(t1 == t1)
2183 self.failUnless(t2 == t2)
2184
2185 # Equal afer adjustment.
2186 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2187 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2188 self.assertEqual(t1, t2)
2189
2190 # Change t1 not to subtract a minute, and t1 should be larger.
2191 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2192 self.failUnless(t1 > t2)
2193
2194 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2195 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2196 self.failUnless(t1 < t2)
2197
2198 # Back to the original t1, but make seconds resolve it.
2199 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2200 second=1)
2201 self.failUnless(t1 > t2)
2202
2203 # Likewise, but make microseconds resolve it.
2204 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2205 microsecond=1)
2206 self.failUnless(t1 > t2)
2207
2208 # Make t2 naive and it should fail.
2209 t2 = self.theclass.min
2210 self.assertRaises(TypeError, lambda: t1 == t2)
2211 self.assertEqual(t2, t2)
2212
2213 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2214 class Naive(tzinfo):
2215 def utcoffset(self, dt): return None
2216 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2217 self.assertRaises(TypeError, lambda: t1 == t2)
2218 self.assertEqual(t2, t2)
2219
2220 # OTOH, it's OK to compare two of these mixing the two ways of being
2221 # naive.
2222 t1 = self.theclass(5, 6, 7)
2223 self.assertEqual(t1, t2)
2224
2225 # Try a bogus uctoffset.
2226 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002227 def utcoffset(self, dt):
2228 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002229 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2230 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002231 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002232
Tim Peters2a799bf2002-12-16 20:18:38 +00002233 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002234 # Try one without a tzinfo.
2235 args = 6, 7, 23, 20, 59, 1, 64**2
2236 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002237 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002238 green = pickler.dumps(orig, proto)
2239 derived = unpickler.loads(green)
2240 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002241
2242 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002243 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002244 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002245 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002246 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002247 green = pickler.dumps(orig, proto)
2248 derived = unpickler.loads(green)
2249 self.assertEqual(orig, derived)
2250 self.failUnless(isinstance(derived.tzinfo,
2251 PicklableFixedOffset))
2252 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2253 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002254
2255 def test_extreme_hashes(self):
2256 # If an attempt is made to hash these via subtracting the offset
2257 # then hashing a datetime object, OverflowError results. The
2258 # Python implementation used to blow up here.
2259 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2260 hash(t)
2261 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2262 tzinfo=FixedOffset(-1439, ""))
2263 hash(t)
2264
2265 # OTOH, an OOB offset should blow up.
2266 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2267 self.assertRaises(ValueError, hash, t)
2268
2269 def test_zones(self):
2270 est = FixedOffset(-300, "EST")
2271 utc = FixedOffset(0, "UTC")
2272 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002273 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2274 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2275 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002276 self.assertEqual(t1.tzinfo, est)
2277 self.assertEqual(t2.tzinfo, utc)
2278 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002279 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2280 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2281 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002282 self.assertEqual(t1.tzname(), "EST")
2283 self.assertEqual(t2.tzname(), "UTC")
2284 self.assertEqual(t3.tzname(), "MET")
2285 self.assertEqual(hash(t1), hash(t2))
2286 self.assertEqual(hash(t1), hash(t3))
2287 self.assertEqual(hash(t2), hash(t3))
2288 self.assertEqual(t1, t2)
2289 self.assertEqual(t1, t3)
2290 self.assertEqual(t2, t3)
2291 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2292 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2293 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002294 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002295 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2296 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2297 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2298
2299 def test_combine(self):
2300 met = FixedOffset(60, "MET")
2301 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002302 tz = time(18, 45, 3, 1234, tzinfo=met)
2303 dt = datetime.combine(d, tz)
2304 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002305 tzinfo=met))
2306
2307 def test_extract(self):
2308 met = FixedOffset(60, "MET")
2309 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2310 self.assertEqual(dt.date(), date(2002, 3, 4))
2311 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002312 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002313
2314 def test_tz_aware_arithmetic(self):
2315 import random
2316
2317 now = self.theclass.now()
2318 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002319 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002320 nowaware = self.theclass.combine(now.date(), timeaware)
2321 self.failUnless(nowaware.tzinfo is tz55)
2322 self.assertEqual(nowaware.timetz(), timeaware)
2323
2324 # Can't mix aware and non-aware.
2325 self.assertRaises(TypeError, lambda: now - nowaware)
2326 self.assertRaises(TypeError, lambda: nowaware - now)
2327
Tim Peters0bf60bd2003-01-08 20:40:01 +00002328 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002329 self.assertRaises(TypeError, lambda: now + nowaware)
2330 self.assertRaises(TypeError, lambda: nowaware + now)
2331 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2332
2333 # Subtracting should yield 0.
2334 self.assertEqual(now - now, timedelta(0))
2335 self.assertEqual(nowaware - nowaware, timedelta(0))
2336
2337 # Adding a delta should preserve tzinfo.
2338 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2339 nowawareplus = nowaware + delta
2340 self.failUnless(nowaware.tzinfo is tz55)
2341 nowawareplus2 = delta + nowaware
2342 self.failUnless(nowawareplus2.tzinfo is tz55)
2343 self.assertEqual(nowawareplus, nowawareplus2)
2344
2345 # that - delta should be what we started with, and that - what we
2346 # started with should be delta.
2347 diff = nowawareplus - delta
2348 self.failUnless(diff.tzinfo is tz55)
2349 self.assertEqual(nowaware, diff)
2350 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2351 self.assertEqual(nowawareplus - nowaware, delta)
2352
2353 # Make up a random timezone.
2354 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002355 # Attach it to nowawareplus.
2356 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002357 self.failUnless(nowawareplus.tzinfo is tzr)
2358 # Make sure the difference takes the timezone adjustments into account.
2359 got = nowaware - nowawareplus
2360 # Expected: (nowaware base - nowaware offset) -
2361 # (nowawareplus base - nowawareplus offset) =
2362 # (nowaware base - nowawareplus base) +
2363 # (nowawareplus offset - nowaware offset) =
2364 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002365 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002366 self.assertEqual(got, expected)
2367
2368 # Try max possible difference.
2369 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2370 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2371 tzinfo=FixedOffset(-1439, "max"))
2372 maxdiff = max - min
2373 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2374 timedelta(minutes=2*1439))
2375
2376 def test_tzinfo_now(self):
2377 meth = self.theclass.now
2378 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2379 base = meth()
2380 # Try with and without naming the keyword.
2381 off42 = FixedOffset(42, "42")
2382 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002383 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002384 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002385 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002386 # Bad argument with and w/o naming the keyword.
2387 self.assertRaises(TypeError, meth, 16)
2388 self.assertRaises(TypeError, meth, tzinfo=16)
2389 # Bad keyword name.
2390 self.assertRaises(TypeError, meth, tinfo=off42)
2391 # Too many args.
2392 self.assertRaises(TypeError, meth, off42, off42)
2393
Tim Peters10cadce2003-01-23 19:58:02 +00002394 # We don't know which time zone we're in, and don't have a tzinfo
2395 # class to represent it, so seeing whether a tz argument actually
2396 # does a conversion is tricky.
2397 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2398 utc = FixedOffset(0, "utc", 0)
2399 for dummy in range(3):
2400 now = datetime.now(weirdtz)
2401 self.failUnless(now.tzinfo is weirdtz)
2402 utcnow = datetime.utcnow().replace(tzinfo=utc)
2403 now2 = utcnow.astimezone(weirdtz)
2404 if abs(now - now2) < timedelta(seconds=30):
2405 break
2406 # Else the code is broken, or more than 30 seconds passed between
2407 # calls; assuming the latter, just try again.
2408 else:
2409 # Three strikes and we're out.
2410 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2411
Tim Peters2a799bf2002-12-16 20:18:38 +00002412 def test_tzinfo_fromtimestamp(self):
2413 import time
2414 meth = self.theclass.fromtimestamp
2415 ts = time.time()
2416 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2417 base = meth(ts)
2418 # Try with and without naming the keyword.
2419 off42 = FixedOffset(42, "42")
2420 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002421 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002422 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002423 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002424 # Bad argument with and w/o naming the keyword.
2425 self.assertRaises(TypeError, meth, ts, 16)
2426 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2427 # Bad keyword name.
2428 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2429 # Too many args.
2430 self.assertRaises(TypeError, meth, ts, off42, off42)
2431 # Too few args.
2432 self.assertRaises(TypeError, meth)
2433
Tim Peters2a44a8d2003-01-23 20:53:10 +00002434 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002435 timestamp = 1000000000
2436 utcdatetime = datetime.utcfromtimestamp(timestamp)
2437 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2438 # But on some flavor of Mac, it's nowhere near that. So we can't have
2439 # any idea here what time that actually is, we can only test that
2440 # relative changes match.
2441 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2442 tz = FixedOffset(utcoffset, "tz", 0)
2443 expected = utcdatetime + utcoffset
2444 got = datetime.fromtimestamp(timestamp, tz)
2445 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002446
Tim Peters2a799bf2002-12-16 20:18:38 +00002447 def test_tzinfo_utcnow(self):
2448 meth = self.theclass.utcnow
2449 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2450 base = meth()
2451 # Try with and without naming the keyword; for whatever reason,
2452 # utcnow() doesn't accept a tzinfo argument.
2453 off42 = FixedOffset(42, "42")
2454 self.assertRaises(TypeError, meth, off42)
2455 self.assertRaises(TypeError, meth, tzinfo=off42)
2456
2457 def test_tzinfo_utcfromtimestamp(self):
2458 import time
2459 meth = self.theclass.utcfromtimestamp
2460 ts = time.time()
2461 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2462 base = meth(ts)
2463 # Try with and without naming the keyword; for whatever reason,
2464 # utcfromtimestamp() doesn't accept a tzinfo argument.
2465 off42 = FixedOffset(42, "42")
2466 self.assertRaises(TypeError, meth, ts, off42)
2467 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2468
2469 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002470 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002471 # DST flag.
2472 class DST(tzinfo):
2473 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002474 if isinstance(dstvalue, int):
2475 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002476 self.dstvalue = dstvalue
2477 def dst(self, dt):
2478 return self.dstvalue
2479
2480 cls = self.theclass
2481 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2482 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2483 t = d.timetuple()
2484 self.assertEqual(1, t.tm_year)
2485 self.assertEqual(1, t.tm_mon)
2486 self.assertEqual(1, t.tm_mday)
2487 self.assertEqual(10, t.tm_hour)
2488 self.assertEqual(20, t.tm_min)
2489 self.assertEqual(30, t.tm_sec)
2490 self.assertEqual(0, t.tm_wday)
2491 self.assertEqual(1, t.tm_yday)
2492 self.assertEqual(flag, t.tm_isdst)
2493
2494 # dst() returns wrong type.
2495 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2496
2497 # dst() at the edge.
2498 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2499 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2500
2501 # dst() out of range.
2502 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2503 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2504
2505 def test_utctimetuple(self):
2506 class DST(tzinfo):
2507 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002508 if isinstance(dstvalue, int):
2509 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002510 self.dstvalue = dstvalue
2511 def dst(self, dt):
2512 return self.dstvalue
2513
2514 cls = self.theclass
2515 # This can't work: DST didn't implement utcoffset.
2516 self.assertRaises(NotImplementedError,
2517 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2518
2519 class UOFS(DST):
2520 def __init__(self, uofs, dofs=None):
2521 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002522 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002523 def utcoffset(self, dt):
2524 return self.uofs
2525
2526 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2527 # in effect for a UTC time.
2528 for dstvalue in -33, 33, 0, None:
2529 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2530 t = d.utctimetuple()
2531 self.assertEqual(d.year, t.tm_year)
2532 self.assertEqual(d.month, t.tm_mon)
2533 self.assertEqual(d.day, t.tm_mday)
2534 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2535 self.assertEqual(13, t.tm_min)
2536 self.assertEqual(d.second, t.tm_sec)
2537 self.assertEqual(d.weekday(), t.tm_wday)
2538 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2539 t.tm_yday)
2540 self.assertEqual(0, t.tm_isdst)
2541
2542 # At the edges, UTC adjustment can normalize into years out-of-range
2543 # for a datetime object. Ensure that a correct timetuple is
2544 # created anyway.
2545 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2546 # That goes back 1 minute less than a full day.
2547 t = tiny.utctimetuple()
2548 self.assertEqual(t.tm_year, MINYEAR-1)
2549 self.assertEqual(t.tm_mon, 12)
2550 self.assertEqual(t.tm_mday, 31)
2551 self.assertEqual(t.tm_hour, 0)
2552 self.assertEqual(t.tm_min, 1)
2553 self.assertEqual(t.tm_sec, 37)
2554 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2555 self.assertEqual(t.tm_isdst, 0)
2556
2557 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2558 # That goes forward 1 minute less than a full day.
2559 t = huge.utctimetuple()
2560 self.assertEqual(t.tm_year, MAXYEAR+1)
2561 self.assertEqual(t.tm_mon, 1)
2562 self.assertEqual(t.tm_mday, 1)
2563 self.assertEqual(t.tm_hour, 23)
2564 self.assertEqual(t.tm_min, 58)
2565 self.assertEqual(t.tm_sec, 37)
2566 self.assertEqual(t.tm_yday, 1)
2567 self.assertEqual(t.tm_isdst, 0)
2568
2569 def test_tzinfo_isoformat(self):
2570 zero = FixedOffset(0, "+00:00")
2571 plus = FixedOffset(220, "+03:40")
2572 minus = FixedOffset(-231, "-03:51")
2573 unknown = FixedOffset(None, "")
2574
2575 cls = self.theclass
2576 datestr = '0001-02-03'
2577 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002578 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002579 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2580 timestr = '04:05:59' + (us and '.987001' or '')
2581 ofsstr = ofs is not None and d.tzname() or ''
2582 tailstr = timestr + ofsstr
2583 iso = d.isoformat()
2584 self.assertEqual(iso, datestr + 'T' + tailstr)
2585 self.assertEqual(iso, d.isoformat('T'))
2586 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2587 self.assertEqual(str(d), datestr + ' ' + tailstr)
2588
Tim Peters12bf3392002-12-24 05:41:27 +00002589 def test_replace(self):
2590 cls = self.theclass
2591 z100 = FixedOffset(100, "+100")
2592 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2593 args = [1, 2, 3, 4, 5, 6, 7, z100]
2594 base = cls(*args)
2595 self.assertEqual(base, base.replace())
2596
2597 i = 0
2598 for name, newval in (("year", 2),
2599 ("month", 3),
2600 ("day", 4),
2601 ("hour", 5),
2602 ("minute", 6),
2603 ("second", 7),
2604 ("microsecond", 8),
2605 ("tzinfo", zm200)):
2606 newargs = args[:]
2607 newargs[i] = newval
2608 expected = cls(*newargs)
2609 got = base.replace(**{name: newval})
2610 self.assertEqual(expected, got)
2611 i += 1
2612
2613 # Ensure we can get rid of a tzinfo.
2614 self.assertEqual(base.tzname(), "+100")
2615 base2 = base.replace(tzinfo=None)
2616 self.failUnless(base2.tzinfo is None)
2617 self.failUnless(base2.tzname() is None)
2618
2619 # Ensure we can add one.
2620 base3 = base2.replace(tzinfo=z100)
2621 self.assertEqual(base, base3)
2622 self.failUnless(base.tzinfo is base3.tzinfo)
2623
2624 # Out of bounds.
2625 base = cls(2000, 2, 29)
2626 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002627
Tim Peters80475bb2002-12-25 07:40:55 +00002628 def test_more_astimezone(self):
2629 # The inherited test_astimezone covered some trivial and error cases.
2630 fnone = FixedOffset(None, "None")
2631 f44m = FixedOffset(44, "44")
2632 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2633
Tim Peters10cadce2003-01-23 19:58:02 +00002634 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002635 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002636 # Replacing with degenerate tzinfo raises an exception.
2637 self.assertRaises(ValueError, dt.astimezone, fnone)
2638 # Ditto with None tz.
2639 self.assertRaises(TypeError, dt.astimezone, None)
2640 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002641 x = dt.astimezone(dt.tzinfo)
2642 self.failUnless(x.tzinfo is f44m)
2643 self.assertEqual(x.date(), dt.date())
2644 self.assertEqual(x.time(), dt.time())
2645
2646 # Replacing with different tzinfo does adjust.
2647 got = dt.astimezone(fm5h)
2648 self.failUnless(got.tzinfo is fm5h)
2649 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2650 expected = dt - dt.utcoffset() # in effect, convert to UTC
2651 expected += fm5h.utcoffset(dt) # and from there to local time
2652 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2653 self.assertEqual(got.date(), expected.date())
2654 self.assertEqual(got.time(), expected.time())
2655 self.assertEqual(got.timetz(), expected.timetz())
2656 self.failUnless(got.tzinfo is expected.tzinfo)
2657 self.assertEqual(got, expected)
2658
Tim Peters4c0db782002-12-26 05:01:19 +00002659 def test_aware_subtract(self):
2660 cls = self.theclass
2661
Tim Peters60c76e42002-12-27 00:41:11 +00002662 # Ensure that utcoffset() is ignored when the operands have the
2663 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002664 class OperandDependentOffset(tzinfo):
2665 def utcoffset(self, t):
2666 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002667 # d0 and d1 equal after adjustment
2668 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002669 else:
Tim Peters397301e2003-01-02 21:28:08 +00002670 # d2 off in the weeds
2671 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002672
2673 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2674 d0 = base.replace(minute=3)
2675 d1 = base.replace(minute=9)
2676 d2 = base.replace(minute=11)
2677 for x in d0, d1, d2:
2678 for y in d0, d1, d2:
2679 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002680 expected = timedelta(minutes=x.minute - y.minute)
2681 self.assertEqual(got, expected)
2682
2683 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2684 # ignored.
2685 base = cls(8, 9, 10, 11, 12, 13, 14)
2686 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2687 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2688 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2689 for x in d0, d1, d2:
2690 for y in d0, d1, d2:
2691 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002692 if (x is d0 or x is d1) and (y is d0 or y is d1):
2693 expected = timedelta(0)
2694 elif x is y is d2:
2695 expected = timedelta(0)
2696 elif x is d2:
2697 expected = timedelta(minutes=(11-59)-0)
2698 else:
2699 assert y is d2
2700 expected = timedelta(minutes=0-(11-59))
2701 self.assertEqual(got, expected)
2702
Tim Peters60c76e42002-12-27 00:41:11 +00002703 def test_mixed_compare(self):
2704 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002705 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002706 self.assertEqual(t1, t2)
2707 t2 = t2.replace(tzinfo=None)
2708 self.assertEqual(t1, t2)
2709 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2710 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002711 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2712 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002713
Tim Peters0bf60bd2003-01-08 20:40:01 +00002714 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002715 class Varies(tzinfo):
2716 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002717 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002718 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002719 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002720 return self.offset
2721
2722 v = Varies()
2723 t1 = t2.replace(tzinfo=v)
2724 t2 = t2.replace(tzinfo=v)
2725 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2726 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2727 self.assertEqual(t1, t2)
2728
2729 # But if they're not identical, it isn't ignored.
2730 t2 = t2.replace(tzinfo=Varies())
2731 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002732
Tim Petersa98924a2003-05-17 05:55:19 +00002733 def test_subclass_datetimetz(self):
2734
2735 class C(self.theclass):
2736 theAnswer = 42
2737
2738 def __new__(cls, *args, **kws):
2739 temp = kws.copy()
2740 extra = temp.pop('extra')
2741 result = self.theclass.__new__(cls, *args, **temp)
2742 result.extra = extra
2743 return result
2744
2745 def newmeth(self, start):
2746 return start + self.hour + self.year
2747
2748 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2749
2750 dt1 = self.theclass(*args)
2751 dt2 = C(*args, **{'extra': 7})
2752
2753 self.assertEqual(dt2.__class__, C)
2754 self.assertEqual(dt2.theAnswer, 42)
2755 self.assertEqual(dt2.extra, 7)
2756 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2757 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2758
Tim Peters621818b2002-12-29 23:44:49 +00002759# Pain to set up DST-aware tzinfo classes.
2760
2761def first_sunday_on_or_after(dt):
2762 days_to_go = 6 - dt.weekday()
2763 if days_to_go:
2764 dt += timedelta(days_to_go)
2765 return dt
2766
2767ZERO = timedelta(0)
2768HOUR = timedelta(hours=1)
2769DAY = timedelta(days=1)
2770# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2771DSTSTART = datetime(1, 4, 1, 2)
2772# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002773# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2774# being standard time on that day, there is no spelling in local time of
2775# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2776DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002777
2778class USTimeZone(tzinfo):
2779
2780 def __init__(self, hours, reprname, stdname, dstname):
2781 self.stdoffset = timedelta(hours=hours)
2782 self.reprname = reprname
2783 self.stdname = stdname
2784 self.dstname = dstname
2785
2786 def __repr__(self):
2787 return self.reprname
2788
2789 def tzname(self, dt):
2790 if self.dst(dt):
2791 return self.dstname
2792 else:
2793 return self.stdname
2794
2795 def utcoffset(self, dt):
2796 return self.stdoffset + self.dst(dt)
2797
2798 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002799 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002800 # An exception instead may be sensible here, in one or more of
2801 # the cases.
2802 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002803 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002804
2805 # Find first Sunday in April.
2806 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2807 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2808
2809 # Find last Sunday in October.
2810 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2811 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2812
Tim Peters621818b2002-12-29 23:44:49 +00002813 # Can't compare naive to aware objects, so strip the timezone from
2814 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002815 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002816 return HOUR
2817 else:
2818 return ZERO
2819
Tim Peters521fc152002-12-31 17:36:56 +00002820Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2821Central = USTimeZone(-6, "Central", "CST", "CDT")
2822Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2823Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002824utc_real = FixedOffset(0, "UTC", 0)
2825# For better test coverage, we want another flavor of UTC that's west of
2826# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002827utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002828
2829class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002830 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002831 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002832 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002833
Tim Peters0bf60bd2003-01-08 20:40:01 +00002834 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002835
Tim Peters521fc152002-12-31 17:36:56 +00002836 # Check a time that's inside DST.
2837 def checkinside(self, dt, tz, utc, dston, dstoff):
2838 self.assertEqual(dt.dst(), HOUR)
2839
2840 # Conversion to our own timezone is always an identity.
2841 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002842
2843 asutc = dt.astimezone(utc)
2844 there_and_back = asutc.astimezone(tz)
2845
2846 # Conversion to UTC and back isn't always an identity here,
2847 # because there are redundant spellings (in local time) of
2848 # UTC time when DST begins: the clock jumps from 1:59:59
2849 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2850 # make sense then. The classes above treat 2:MM:SS as
2851 # daylight time then (it's "after 2am"), really an alias
2852 # for 1:MM:SS standard time. The latter form is what
2853 # conversion back from UTC produces.
2854 if dt.date() == dston.date() and dt.hour == 2:
2855 # We're in the redundant hour, and coming back from
2856 # UTC gives the 1:MM:SS standard-time spelling.
2857 self.assertEqual(there_and_back + HOUR, dt)
2858 # Although during was considered to be in daylight
2859 # time, there_and_back is not.
2860 self.assertEqual(there_and_back.dst(), ZERO)
2861 # They're the same times in UTC.
2862 self.assertEqual(there_and_back.astimezone(utc),
2863 dt.astimezone(utc))
2864 else:
2865 # We're not in the redundant hour.
2866 self.assertEqual(dt, there_and_back)
2867
Tim Peters327098a2003-01-20 22:54:38 +00002868 # Because we have a redundant spelling when DST begins, there is
2869 # (unforunately) an hour when DST ends that can't be spelled at all in
2870 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2871 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2872 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2873 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2874 # expressed in local time. Nevertheless, we want conversion back
2875 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002876 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002877 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002878 if dt.date() == dstoff.date() and dt.hour == 0:
2879 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002880 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002881 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2882 nexthour_utc += HOUR
2883 nexthour_tz = nexthour_utc.astimezone(tz)
2884 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002885 else:
Tim Peters327098a2003-01-20 22:54:38 +00002886 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002887
2888 # Check a time that's outside DST.
2889 def checkoutside(self, dt, tz, utc):
2890 self.assertEqual(dt.dst(), ZERO)
2891
2892 # Conversion to our own timezone is always an identity.
2893 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00002894
2895 # Converting to UTC and back is an identity too.
2896 asutc = dt.astimezone(utc)
2897 there_and_back = asutc.astimezone(tz)
2898 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00002899
Tim Peters1024bf82002-12-30 17:09:40 +00002900 def convert_between_tz_and_utc(self, tz, utc):
2901 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00002902 # Because 1:MM on the day DST ends is taken as being standard time,
2903 # there is no spelling in tz for the last hour of daylight time.
2904 # For purposes of the test, the last hour of DST is 0:MM, which is
2905 # taken as being daylight time (and 1:MM is taken as being standard
2906 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00002907 dstoff = self.dstoff.replace(tzinfo=tz)
2908 for delta in (timedelta(weeks=13),
2909 DAY,
2910 HOUR,
2911 timedelta(minutes=1),
2912 timedelta(microseconds=1)):
2913
Tim Peters521fc152002-12-31 17:36:56 +00002914 self.checkinside(dston, tz, utc, dston, dstoff)
2915 for during in dston + delta, dstoff - delta:
2916 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00002917
Tim Peters521fc152002-12-31 17:36:56 +00002918 self.checkoutside(dstoff, tz, utc)
2919 for outside in dston - delta, dstoff + delta:
2920 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00002921
Tim Peters621818b2002-12-29 23:44:49 +00002922 def test_easy(self):
2923 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00002924 self.convert_between_tz_and_utc(Eastern, utc_real)
2925 self.convert_between_tz_and_utc(Pacific, utc_real)
2926 self.convert_between_tz_and_utc(Eastern, utc_fake)
2927 self.convert_between_tz_and_utc(Pacific, utc_fake)
2928 # The next is really dancing near the edge. It works because
2929 # Pacific and Eastern are far enough apart that their "problem
2930 # hours" don't overlap.
2931 self.convert_between_tz_and_utc(Eastern, Pacific)
2932 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00002933 # OTOH, these fail! Don't enable them. The difficulty is that
2934 # the edge case tests assume that every hour is representable in
2935 # the "utc" class. This is always true for a fixed-offset tzinfo
2936 # class (lke utc_real and utc_fake), but not for Eastern or Central.
2937 # For these adjacent DST-aware time zones, the range of time offsets
2938 # tested ends up creating hours in the one that aren't representable
2939 # in the other. For the same reason, we would see failures in the
2940 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
2941 # offset deltas in convert_between_tz_and_utc().
2942 #
2943 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
2944 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00002945
Tim Petersf3615152003-01-01 21:51:37 +00002946 def test_tricky(self):
2947 # 22:00 on day before daylight starts.
2948 fourback = self.dston - timedelta(hours=4)
2949 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00002950 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00002951 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
2952 # 2", we should get the 3 spelling.
2953 # If we plug 22:00 the day before into Eastern, it "looks like std
2954 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
2955 # to 22:00 lands on 2:00, which makes no sense in local time (the
2956 # local clock jumps from 1 to 3). The point here is to make sure we
2957 # get the 3 spelling.
2958 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00002959 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002960 self.assertEqual(expected, got)
2961
2962 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
2963 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00002964 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00002965 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
2966 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
2967 # spelling.
2968 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00002969 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00002970 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00002971
Tim Petersadf64202003-01-04 06:03:15 +00002972 # Now on the day DST ends, we want "repeat an hour" behavior.
2973 # UTC 4:MM 5:MM 6:MM 7:MM checking these
2974 # EST 23:MM 0:MM 1:MM 2:MM
2975 # EDT 0:MM 1:MM 2:MM 3:MM
2976 # wall 0:MM 1:MM 1:MM 2:MM against these
2977 for utc in utc_real, utc_fake:
2978 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00002979 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00002980 # Convert that to UTC.
2981 first_std_hour -= tz.utcoffset(None)
2982 # Adjust for possibly fake UTC.
2983 asutc = first_std_hour + utc.utcoffset(None)
2984 # First UTC hour to convert; this is 4:00 when utc=utc_real &
2985 # tz=Eastern.
2986 asutcbase = asutc.replace(tzinfo=utc)
2987 for tzhour in (0, 1, 1, 2):
2988 expectedbase = self.dstoff.replace(hour=tzhour)
2989 for minute in 0, 30, 59:
2990 expected = expectedbase.replace(minute=minute)
2991 asutc = asutcbase.replace(minute=minute)
2992 astz = asutc.astimezone(tz)
2993 self.assertEqual(astz.replace(tzinfo=None), expected)
2994 asutcbase += HOUR
2995
2996
Tim Peters710fb152003-01-02 19:35:54 +00002997 def test_bogus_dst(self):
2998 class ok(tzinfo):
2999 def utcoffset(self, dt): return HOUR
3000 def dst(self, dt): return HOUR
3001
3002 now = self.theclass.now().replace(tzinfo=utc_real)
3003 # Doesn't blow up.
3004 now.astimezone(ok())
3005
3006 # Does blow up.
3007 class notok(ok):
3008 def dst(self, dt): return None
3009 self.assertRaises(ValueError, now.astimezone, notok())
3010
Tim Peters52dcce22003-01-23 16:36:11 +00003011 def test_fromutc(self):
3012 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3013 now = datetime.utcnow().replace(tzinfo=utc_real)
3014 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3015 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3016 enow = Eastern.fromutc(now) # doesn't blow up
3017 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3018 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3019 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3020
3021 # Always converts UTC to standard time.
3022 class FauxUSTimeZone(USTimeZone):
3023 def fromutc(self, dt):
3024 return dt + self.stdoffset
3025 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3026
3027 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3028 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3029 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3030
3031 # Check around DST start.
3032 start = self.dston.replace(hour=4, tzinfo=Eastern)
3033 fstart = start.replace(tzinfo=FEastern)
3034 for wall in 23, 0, 1, 3, 4, 5:
3035 expected = start.replace(hour=wall)
3036 if wall == 23:
3037 expected -= timedelta(days=1)
3038 got = Eastern.fromutc(start)
3039 self.assertEqual(expected, got)
3040
3041 expected = fstart + FEastern.stdoffset
3042 got = FEastern.fromutc(fstart)
3043 self.assertEqual(expected, got)
3044
3045 # Ensure astimezone() calls fromutc() too.
3046 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3047 self.assertEqual(expected, got)
3048
3049 start += HOUR
3050 fstart += HOUR
3051
3052 # Check around DST end.
3053 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3054 fstart = start.replace(tzinfo=FEastern)
3055 for wall in 0, 1, 1, 2, 3, 4:
3056 expected = start.replace(hour=wall)
3057 got = Eastern.fromutc(start)
3058 self.assertEqual(expected, got)
3059
3060 expected = fstart + FEastern.stdoffset
3061 got = FEastern.fromutc(fstart)
3062 self.assertEqual(expected, got)
3063
3064 # Ensure astimezone() calls fromutc() too.
3065 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3066 self.assertEqual(expected, got)
3067
3068 start += HOUR
3069 fstart += HOUR
3070
Tim Peters710fb152003-01-02 19:35:54 +00003071
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003072def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003073 allsuites = [unittest.makeSuite(klass, 'test')
3074 for klass in (TestModule,
3075 TestTZInfo,
3076 TestTimeDelta,
3077 TestDateOnly,
3078 TestDate,
3079 TestDateTime,
3080 TestTime,
3081 TestTimeTZ,
3082 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003083 TestTimezoneConversions,
Tim Peters2a799bf2002-12-16 20:18:38 +00003084 )
3085 ]
3086 return unittest.TestSuite(allsuites)
3087
3088def test_main():
3089 import gc
3090 import sys
3091
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003092 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003093 lastrc = None
3094 while True:
3095 test_support.run_suite(thesuite)
3096 if 1: # change to 0, under a debug build, for some leak detection
3097 break
3098 gc.collect()
3099 if gc.garbage:
3100 raise SystemError("gc.garbage not empty after test run: %r" %
3101 gc.garbage)
3102 if hasattr(sys, 'gettotalrefcount'):
3103 thisrc = sys.gettotalrefcount()
3104 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3105 if lastrc:
3106 print >> sys.stderr, 'delta:', thisrc - lastrc
3107 else:
3108 print >> sys.stderr
3109 lastrc = thisrc
3110
3111if __name__ == "__main__":
3112 test_main()