blob: 203bea150044e37ed11045f5312bd9accde16c44 [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):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000449 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000450 def from_td(td):
451 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000452
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 Peters604c0132004-06-07 23:04:33 +0000513class SubclassDate(date):
514 sub_var = 1
515
Tim Peters07534a62003-02-07 22:50:28 +0000516class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000517 # Tests here should pass for both dates and datetimes, except for a
518 # few tests that TestDateTime overrides.
519
520 theclass = date
521
522 def test_basic_attributes(self):
523 dt = self.theclass(2002, 3, 1)
524 self.assertEqual(dt.year, 2002)
525 self.assertEqual(dt.month, 3)
526 self.assertEqual(dt.day, 1)
527
528 def test_roundtrip(self):
529 for dt in (self.theclass(1, 2, 3),
530 self.theclass.today()):
531 # Verify dt -> string -> date identity.
532 s = repr(dt)
533 self.failUnless(s.startswith('datetime.'))
534 s = s[9:]
535 dt2 = eval(s)
536 self.assertEqual(dt, dt2)
537
538 # Verify identity via reconstructing from pieces.
539 dt2 = self.theclass(dt.year, dt.month, dt.day)
540 self.assertEqual(dt, dt2)
541
542 def test_ordinal_conversions(self):
543 # Check some fixed values.
544 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
545 (1, 12, 31, 365),
546 (2, 1, 1, 366),
547 # first example from "Calendrical Calculations"
548 (1945, 11, 12, 710347)]:
549 d = self.theclass(y, m, d)
550 self.assertEqual(n, d.toordinal())
551 fromord = self.theclass.fromordinal(n)
552 self.assertEqual(d, fromord)
553 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000554 # if we're checking something fancier than a date, verify
555 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000556 self.assertEqual(fromord.hour, 0)
557 self.assertEqual(fromord.minute, 0)
558 self.assertEqual(fromord.second, 0)
559 self.assertEqual(fromord.microsecond, 0)
560
Tim Peters0bf60bd2003-01-08 20:40:01 +0000561 # Check first and last days of year spottily across the whole
562 # range of years supported.
563 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000564 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
565 d = self.theclass(year, 1, 1)
566 n = d.toordinal()
567 d2 = self.theclass.fromordinal(n)
568 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000569 # Verify that moving back a day gets to the end of year-1.
570 if year > 1:
571 d = self.theclass.fromordinal(n-1)
572 d2 = self.theclass(year-1, 12, 31)
573 self.assertEqual(d, d2)
574 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000575
576 # Test every day in a leap-year and a non-leap year.
577 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
578 for year, isleap in (2000, True), (2002, False):
579 n = self.theclass(year, 1, 1).toordinal()
580 for month, maxday in zip(range(1, 13), dim):
581 if month == 2 and isleap:
582 maxday += 1
583 for day in range(1, maxday+1):
584 d = self.theclass(year, month, day)
585 self.assertEqual(d.toordinal(), n)
586 self.assertEqual(d, self.theclass.fromordinal(n))
587 n += 1
588
589 def test_extreme_ordinals(self):
590 a = self.theclass.min
591 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
592 aord = a.toordinal()
593 b = a.fromordinal(aord)
594 self.assertEqual(a, b)
595
596 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
597
598 b = a + timedelta(days=1)
599 self.assertEqual(b.toordinal(), aord + 1)
600 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
601
602 a = self.theclass.max
603 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
604 aord = a.toordinal()
605 b = a.fromordinal(aord)
606 self.assertEqual(a, b)
607
608 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
609
610 b = a - timedelta(days=1)
611 self.assertEqual(b.toordinal(), aord - 1)
612 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
613
614 def test_bad_constructor_arguments(self):
615 # bad years
616 self.theclass(MINYEAR, 1, 1) # no exception
617 self.theclass(MAXYEAR, 1, 1) # no exception
618 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
619 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
620 # bad months
621 self.theclass(2000, 1, 1) # no exception
622 self.theclass(2000, 12, 1) # no exception
623 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
624 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
625 # bad days
626 self.theclass(2000, 2, 29) # no exception
627 self.theclass(2004, 2, 29) # no exception
628 self.theclass(2400, 2, 29) # no exception
629 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
630 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
631 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
635
636 def test_hash_equality(self):
637 d = self.theclass(2000, 12, 31)
638 # same thing
639 e = self.theclass(2000, 12, 31)
640 self.assertEqual(d, e)
641 self.assertEqual(hash(d), hash(e))
642
643 dic = {d: 1}
644 dic[e] = 2
645 self.assertEqual(len(dic), 1)
646 self.assertEqual(dic[d], 2)
647 self.assertEqual(dic[e], 2)
648
649 d = self.theclass(2001, 1, 1)
650 # same thing
651 e = self.theclass(2001, 1, 1)
652 self.assertEqual(d, e)
653 self.assertEqual(hash(d), hash(e))
654
655 dic = {d: 1}
656 dic[e] = 2
657 self.assertEqual(len(dic), 1)
658 self.assertEqual(dic[d], 2)
659 self.assertEqual(dic[e], 2)
660
661 def test_computations(self):
662 a = self.theclass(2002, 1, 31)
663 b = self.theclass(1956, 1, 31)
664
665 diff = a-b
666 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
667 self.assertEqual(diff.seconds, 0)
668 self.assertEqual(diff.microseconds, 0)
669
670 day = timedelta(1)
671 week = timedelta(7)
672 a = self.theclass(2002, 3, 2)
673 self.assertEqual(a + day, self.theclass(2002, 3, 3))
674 self.assertEqual(day + a, self.theclass(2002, 3, 3))
675 self.assertEqual(a - day, self.theclass(2002, 3, 1))
676 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
677 self.assertEqual(a + week, self.theclass(2002, 3, 9))
678 self.assertEqual(a - week, self.theclass(2002, 2, 23))
679 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
680 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
681 self.assertEqual((a + week) - a, week)
682 self.assertEqual((a + day) - a, day)
683 self.assertEqual((a - week) - a, -week)
684 self.assertEqual((a - day) - a, -day)
685 self.assertEqual(a - (a + week), -week)
686 self.assertEqual(a - (a + day), -day)
687 self.assertEqual(a - (a - week), week)
688 self.assertEqual(a - (a - day), day)
689
690 # Add/sub ints, longs, floats should be illegal
691 for i in 1, 1L, 1.0:
692 self.assertRaises(TypeError, lambda: a+i)
693 self.assertRaises(TypeError, lambda: a-i)
694 self.assertRaises(TypeError, lambda: i+a)
695 self.assertRaises(TypeError, lambda: i-a)
696
697 # delta - date is senseless.
698 self.assertRaises(TypeError, lambda: day - a)
699 # mixing date and (delta or date) via * or // is senseless
700 self.assertRaises(TypeError, lambda: day * a)
701 self.assertRaises(TypeError, lambda: a * day)
702 self.assertRaises(TypeError, lambda: day // a)
703 self.assertRaises(TypeError, lambda: a // day)
704 self.assertRaises(TypeError, lambda: a * a)
705 self.assertRaises(TypeError, lambda: a // a)
706 # date + date is senseless
707 self.assertRaises(TypeError, lambda: a + a)
708
709 def test_overflow(self):
710 tiny = self.theclass.resolution
711
712 dt = self.theclass.min + tiny
713 dt -= tiny # no problem
714 self.assertRaises(OverflowError, dt.__sub__, tiny)
715 self.assertRaises(OverflowError, dt.__add__, -tiny)
716
717 dt = self.theclass.max - tiny
718 dt += tiny # no problem
719 self.assertRaises(OverflowError, dt.__add__, tiny)
720 self.assertRaises(OverflowError, dt.__sub__, -tiny)
721
722 def test_fromtimestamp(self):
723 import time
724
725 # Try an arbitrary fixed value.
726 year, month, day = 1999, 9, 19
727 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
728 d = self.theclass.fromtimestamp(ts)
729 self.assertEqual(d.year, year)
730 self.assertEqual(d.month, month)
731 self.assertEqual(d.day, day)
732
Tim Peters1b6f7a92004-06-20 02:50:16 +0000733 def test_insane_fromtimestamp(self):
734 # It's possible that some platform maps time_t to double,
735 # and that this test will fail there. This test should
736 # exempt such platforms (provided they return reasonable
737 # results!).
738 for insane in -1e200, 1e200:
739 self.assertRaises(ValueError, self.theclass.fromtimestamp,
740 insane)
741
Tim Peters2a799bf2002-12-16 20:18:38 +0000742 def test_today(self):
743 import time
744
745 # We claim that today() is like fromtimestamp(time.time()), so
746 # prove it.
747 for dummy in range(3):
748 today = self.theclass.today()
749 ts = time.time()
750 todayagain = self.theclass.fromtimestamp(ts)
751 if today == todayagain:
752 break
753 # There are several legit reasons that could fail:
754 # 1. It recently became midnight, between the today() and the
755 # time() calls.
756 # 2. The platform time() has such fine resolution that we'll
757 # never get the same value twice.
758 # 3. The platform time() has poor resolution, and we just
759 # happened to call today() right before a resolution quantum
760 # boundary.
761 # 4. The system clock got fiddled between calls.
762 # In any case, wait a little while and try again.
763 time.sleep(0.1)
764
765 # It worked or it didn't. If it didn't, assume it's reason #2, and
766 # let the test pass if they're within half a second of each other.
767 self.failUnless(today == todayagain or
768 abs(todayagain - today) < timedelta(seconds=0.5))
769
770 def test_weekday(self):
771 for i in range(7):
772 # March 4, 2002 is a Monday
773 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
774 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
775 # January 2, 1956 is a Monday
776 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
777 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
778
779 def test_isocalendar(self):
780 # Check examples from
781 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
782 for i in range(7):
783 d = self.theclass(2003, 12, 22+i)
784 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
785 d = self.theclass(2003, 12, 29) + timedelta(i)
786 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
787 d = self.theclass(2004, 1, 5+i)
788 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
789 d = self.theclass(2009, 12, 21+i)
790 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
791 d = self.theclass(2009, 12, 28) + timedelta(i)
792 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
793 d = self.theclass(2010, 1, 4+i)
794 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
795
796 def test_iso_long_years(self):
797 # Calculate long ISO years and compare to table from
798 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
799 ISO_LONG_YEARS_TABLE = """
800 4 32 60 88
801 9 37 65 93
802 15 43 71 99
803 20 48 76
804 26 54 82
805
806 105 133 161 189
807 111 139 167 195
808 116 144 172
809 122 150 178
810 128 156 184
811
812 201 229 257 285
813 207 235 263 291
814 212 240 268 296
815 218 246 274
816 224 252 280
817
818 303 331 359 387
819 308 336 364 392
820 314 342 370 398
821 320 348 376
822 325 353 381
823 """
824 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
825 iso_long_years.sort()
826 L = []
827 for i in range(400):
828 d = self.theclass(2000+i, 12, 31)
829 d1 = self.theclass(1600+i, 12, 31)
830 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
831 if d.isocalendar()[1] == 53:
832 L.append(i)
833 self.assertEqual(L, iso_long_years)
834
835 def test_isoformat(self):
836 t = self.theclass(2, 3, 2)
837 self.assertEqual(t.isoformat(), "0002-03-02")
838
839 def test_ctime(self):
840 t = self.theclass(2002, 3, 2)
841 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
842
843 def test_strftime(self):
844 t = self.theclass(2005, 3, 2)
845 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000846 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000847
848 self.assertRaises(TypeError, t.strftime) # needs an arg
849 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
850 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
851
852 # A naive object replaces %z and %Z w/ empty strings.
853 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
854
855 def test_resolution_info(self):
856 self.assert_(isinstance(self.theclass.min, self.theclass))
857 self.assert_(isinstance(self.theclass.max, self.theclass))
858 self.assert_(isinstance(self.theclass.resolution, timedelta))
859 self.assert_(self.theclass.max > self.theclass.min)
860
861 def test_extreme_timedelta(self):
862 big = self.theclass.max - self.theclass.min
863 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
864 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
865 # n == 315537897599999999 ~= 2**58.13
866 justasbig = timedelta(0, 0, n)
867 self.assertEqual(big, justasbig)
868 self.assertEqual(self.theclass.min + big, self.theclass.max)
869 self.assertEqual(self.theclass.max - big, self.theclass.min)
870
871 def test_timetuple(self):
872 for i in range(7):
873 # January 2, 1956 is a Monday (0)
874 d = self.theclass(1956, 1, 2+i)
875 t = d.timetuple()
876 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
877 # February 1, 1956 is a Wednesday (2)
878 d = self.theclass(1956, 2, 1+i)
879 t = d.timetuple()
880 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
881 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
882 # of the year.
883 d = self.theclass(1956, 3, 1+i)
884 t = d.timetuple()
885 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
886 self.assertEqual(t.tm_year, 1956)
887 self.assertEqual(t.tm_mon, 3)
888 self.assertEqual(t.tm_mday, 1+i)
889 self.assertEqual(t.tm_hour, 0)
890 self.assertEqual(t.tm_min, 0)
891 self.assertEqual(t.tm_sec, 0)
892 self.assertEqual(t.tm_wday, (3+i)%7)
893 self.assertEqual(t.tm_yday, 61+i)
894 self.assertEqual(t.tm_isdst, -1)
895
896 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000897 args = 6, 7, 23
898 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000899 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000900 green = pickler.dumps(orig, proto)
901 derived = unpickler.loads(green)
902 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000903
904 def test_compare(self):
905 t1 = self.theclass(2, 3, 4)
906 t2 = self.theclass(2, 3, 4)
907 self.failUnless(t1 == t2)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t1 >= t2)
910 self.failUnless(not t1 != t2)
911 self.failUnless(not t1 < t2)
912 self.failUnless(not t1 > t2)
913 self.assertEqual(cmp(t1, t2), 0)
914 self.assertEqual(cmp(t2, t1), 0)
915
916 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
917 t2 = self.theclass(*args) # this is larger than t1
918 self.failUnless(t1 < t2)
919 self.failUnless(t2 > t1)
920 self.failUnless(t1 <= t2)
921 self.failUnless(t2 >= t1)
922 self.failUnless(t1 != t2)
923 self.failUnless(t2 != t1)
924 self.failUnless(not t1 == t2)
925 self.failUnless(not t2 == t1)
926 self.failUnless(not t1 > t2)
927 self.failUnless(not t2 < t1)
928 self.failUnless(not t1 >= t2)
929 self.failUnless(not t2 <= t1)
930 self.assertEqual(cmp(t1, t2), -1)
931 self.assertEqual(cmp(t2, t1), 1)
932
Tim Peters68124bb2003-02-08 03:46:31 +0000933 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000934 self.assertEqual(t1 == badarg, False)
935 self.assertEqual(t1 != badarg, True)
936 self.assertEqual(badarg == t1, False)
937 self.assertEqual(badarg != t1, True)
938
Tim Peters2a799bf2002-12-16 20:18:38 +0000939 self.assertRaises(TypeError, lambda: t1 < badarg)
940 self.assertRaises(TypeError, lambda: t1 > badarg)
941 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000942 self.assertRaises(TypeError, lambda: badarg <= t1)
943 self.assertRaises(TypeError, lambda: badarg < t1)
944 self.assertRaises(TypeError, lambda: badarg > t1)
945 self.assertRaises(TypeError, lambda: badarg >= t1)
946
Tim Peters8d81a012003-01-24 22:36:34 +0000947 def test_mixed_compare(self):
948 our = self.theclass(2000, 4, 5)
949 self.assertRaises(TypeError, cmp, our, 1)
950 self.assertRaises(TypeError, cmp, 1, our)
951
952 class AnotherDateTimeClass(object):
953 def __cmp__(self, other):
954 # Return "equal" so calling this can't be confused with
955 # compare-by-address (which never says "equal" for distinct
956 # objects).
957 return 0
958
959 # This still errors, because date and datetime comparison raise
960 # TypeError instead of NotImplemented when they don't know what to
961 # do, in order to stop comparison from falling back to the default
962 # compare-by-address.
963 their = AnotherDateTimeClass()
964 self.assertRaises(TypeError, cmp, our, their)
965 # Oops: The next stab raises TypeError in the C implementation,
966 # but not in the Python implementation of datetime. The difference
967 # is due to that the Python implementation defines __cmp__ but
968 # the C implementation defines tp_richcompare. This is more pain
969 # to fix than it's worth, so commenting out the test.
970 # self.assertEqual(cmp(their, our), 0)
971
972 # But date and datetime comparison return NotImplemented instead if the
973 # other object has a timetuple attr. This gives the other object a
974 # chance to do the comparison.
975 class Comparable(AnotherDateTimeClass):
976 def timetuple(self):
977 return ()
978
979 their = Comparable()
980 self.assertEqual(cmp(our, their), 0)
981 self.assertEqual(cmp(their, our), 0)
982 self.failUnless(our == their)
983 self.failUnless(their == our)
984
Tim Peters2a799bf2002-12-16 20:18:38 +0000985 def test_bool(self):
986 # All dates are considered true.
987 self.failUnless(self.theclass.min)
988 self.failUnless(self.theclass.max)
989
Tim Petersd6844152002-12-22 20:58:42 +0000990 def test_srftime_out_of_range(self):
991 # For nasty technical reasons, we can't handle years before 1900.
992 cls = self.theclass
993 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
994 for y in 1, 49, 51, 99, 100, 1000, 1899:
995 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000996
997 def test_replace(self):
998 cls = self.theclass
999 args = [1, 2, 3]
1000 base = cls(*args)
1001 self.assertEqual(base, base.replace())
1002
1003 i = 0
1004 for name, newval in (("year", 2),
1005 ("month", 3),
1006 ("day", 4)):
1007 newargs = args[:]
1008 newargs[i] = newval
1009 expected = cls(*newargs)
1010 got = base.replace(**{name: newval})
1011 self.assertEqual(expected, got)
1012 i += 1
1013
1014 # Out of bounds.
1015 base = cls(2000, 2, 29)
1016 self.assertRaises(ValueError, base.replace, year=2001)
1017
Tim Petersa98924a2003-05-17 05:55:19 +00001018 def test_subclass_date(self):
1019
1020 class C(self.theclass):
1021 theAnswer = 42
1022
1023 def __new__(cls, *args, **kws):
1024 temp = kws.copy()
1025 extra = temp.pop('extra')
1026 result = self.theclass.__new__(cls, *args, **temp)
1027 result.extra = extra
1028 return result
1029
1030 def newmeth(self, start):
1031 return start + self.year + self.month
1032
1033 args = 2003, 4, 14
1034
1035 dt1 = self.theclass(*args)
1036 dt2 = C(*args, **{'extra': 7})
1037
1038 self.assertEqual(dt2.__class__, C)
1039 self.assertEqual(dt2.theAnswer, 42)
1040 self.assertEqual(dt2.extra, 7)
1041 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1042 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1043
Tim Peters604c0132004-06-07 23:04:33 +00001044 def test_pickling_subclass_date(self):
1045
1046 args = 6, 7, 23
1047 orig = SubclassDate(*args)
1048 for pickler, unpickler, proto in pickle_choices:
1049 green = pickler.dumps(orig, proto)
1050 derived = unpickler.loads(green)
1051 self.assertEqual(orig, derived)
1052
Tim Peters3f606292004-03-21 23:38:41 +00001053 def test_backdoor_resistance(self):
1054 # For fast unpickling, the constructor accepts a pickle string.
1055 # This is a low-overhead backdoor. A user can (by intent or
1056 # mistake) pass a string directly, which (if it's the right length)
1057 # will get treated like a pickle, and bypass the normal sanity
1058 # checks in the constructor. This can create insane objects.
1059 # The constructor doesn't want to burn the time to validate all
1060 # fields, but does check the month field. This stops, e.g.,
1061 # datetime.datetime('1995-03-25') from yielding an insane object.
1062 base = '1995-03-25'
1063 if not issubclass(self.theclass, datetime):
1064 base = base[:4]
1065 for month_byte in '9', chr(0), chr(13), '\xff':
1066 self.assertRaises(TypeError, self.theclass,
1067 base[:2] + month_byte + base[3:])
1068 for ord_byte in range(1, 13):
1069 # This shouldn't blow up because of the month byte alone. If
1070 # the implementation changes to do more-careful checking, it may
1071 # blow up because other fields are insane.
1072 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001073
Tim Peters2a799bf2002-12-16 20:18:38 +00001074#############################################################################
1075# datetime tests
1076
Tim Peters604c0132004-06-07 23:04:33 +00001077class SubclassDatetime(datetime):
1078 sub_var = 1
1079
Tim Peters2a799bf2002-12-16 20:18:38 +00001080class TestDateTime(TestDate):
1081
1082 theclass = datetime
1083
1084 def test_basic_attributes(self):
1085 dt = self.theclass(2002, 3, 1, 12, 0)
1086 self.assertEqual(dt.year, 2002)
1087 self.assertEqual(dt.month, 3)
1088 self.assertEqual(dt.day, 1)
1089 self.assertEqual(dt.hour, 12)
1090 self.assertEqual(dt.minute, 0)
1091 self.assertEqual(dt.second, 0)
1092 self.assertEqual(dt.microsecond, 0)
1093
1094 def test_basic_attributes_nonzero(self):
1095 # Make sure all attributes are non-zero so bugs in
1096 # bit-shifting access show up.
1097 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1098 self.assertEqual(dt.year, 2002)
1099 self.assertEqual(dt.month, 3)
1100 self.assertEqual(dt.day, 1)
1101 self.assertEqual(dt.hour, 12)
1102 self.assertEqual(dt.minute, 59)
1103 self.assertEqual(dt.second, 59)
1104 self.assertEqual(dt.microsecond, 8000)
1105
1106 def test_roundtrip(self):
1107 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1108 self.theclass.now()):
1109 # Verify dt -> string -> datetime identity.
1110 s = repr(dt)
1111 self.failUnless(s.startswith('datetime.'))
1112 s = s[9:]
1113 dt2 = eval(s)
1114 self.assertEqual(dt, dt2)
1115
1116 # Verify identity via reconstructing from pieces.
1117 dt2 = self.theclass(dt.year, dt.month, dt.day,
1118 dt.hour, dt.minute, dt.second,
1119 dt.microsecond)
1120 self.assertEqual(dt, dt2)
1121
1122 def test_isoformat(self):
1123 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1124 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1125 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1126 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1127 # str is ISO format with the separator forced to a blank.
1128 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1129
1130 t = self.theclass(2, 3, 2)
1131 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1132 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1133 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1134 # str is ISO format with the separator forced to a blank.
1135 self.assertEqual(str(t), "0002-03-02 00:00:00")
1136
1137 def test_more_ctime(self):
1138 # Test fields that TestDate doesn't touch.
1139 import time
1140
1141 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1142 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1143 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1144 # out. The difference is that t.ctime() produces " 2" for the day,
1145 # but platform ctime() produces "02" for the day. According to
1146 # C99, t.ctime() is correct here.
1147 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1148
1149 # So test a case where that difference doesn't matter.
1150 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1151 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1152
1153 def test_tz_independent_comparing(self):
1154 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1155 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1156 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1157 self.assertEqual(dt1, dt3)
1158 self.assert_(dt2 > dt3)
1159
1160 # Make sure comparison doesn't forget microseconds, and isn't done
1161 # via comparing a float timestamp (an IEEE double doesn't have enough
1162 # precision to span microsecond resolution across years 1 thru 9999,
1163 # so comparing via timestamp necessarily calls some distinct values
1164 # equal).
1165 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1166 us = timedelta(microseconds=1)
1167 dt2 = dt1 + us
1168 self.assertEqual(dt2 - dt1, us)
1169 self.assert_(dt1 < dt2)
1170
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001171 def test_strftime_with_bad_tzname_replace(self):
1172 # verify ok if tzinfo.tzname().replace() returns a non-string
1173 class MyTzInfo(FixedOffset):
1174 def tzname(self, dt):
1175 class MyStr(str):
1176 def replace(self, *args):
1177 return None
1178 return MyStr('name')
1179 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1180 self.assertRaises(TypeError, t.strftime, '%Z')
1181
Tim Peters2a799bf2002-12-16 20:18:38 +00001182 def test_bad_constructor_arguments(self):
1183 # bad years
1184 self.theclass(MINYEAR, 1, 1) # no exception
1185 self.theclass(MAXYEAR, 1, 1) # no exception
1186 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1187 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1188 # bad months
1189 self.theclass(2000, 1, 1) # no exception
1190 self.theclass(2000, 12, 1) # no exception
1191 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1192 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1193 # bad days
1194 self.theclass(2000, 2, 29) # no exception
1195 self.theclass(2004, 2, 29) # no exception
1196 self.theclass(2400, 2, 29) # no exception
1197 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1198 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1199 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1200 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1201 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1202 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1203 # bad hours
1204 self.theclass(2000, 1, 31, 0) # no exception
1205 self.theclass(2000, 1, 31, 23) # no exception
1206 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1207 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1208 # bad minutes
1209 self.theclass(2000, 1, 31, 23, 0) # no exception
1210 self.theclass(2000, 1, 31, 23, 59) # no exception
1211 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1212 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1213 # bad seconds
1214 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1215 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1216 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1217 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1218 # bad microseconds
1219 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1220 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1221 self.assertRaises(ValueError, self.theclass,
1222 2000, 1, 31, 23, 59, 59, -1)
1223 self.assertRaises(ValueError, self.theclass,
1224 2000, 1, 31, 23, 59, 59,
1225 1000000)
1226
1227 def test_hash_equality(self):
1228 d = self.theclass(2000, 12, 31, 23, 30, 17)
1229 e = self.theclass(2000, 12, 31, 23, 30, 17)
1230 self.assertEqual(d, e)
1231 self.assertEqual(hash(d), hash(e))
1232
1233 dic = {d: 1}
1234 dic[e] = 2
1235 self.assertEqual(len(dic), 1)
1236 self.assertEqual(dic[d], 2)
1237 self.assertEqual(dic[e], 2)
1238
1239 d = self.theclass(2001, 1, 1, 0, 5, 17)
1240 e = self.theclass(2001, 1, 1, 0, 5, 17)
1241 self.assertEqual(d, e)
1242 self.assertEqual(hash(d), hash(e))
1243
1244 dic = {d: 1}
1245 dic[e] = 2
1246 self.assertEqual(len(dic), 1)
1247 self.assertEqual(dic[d], 2)
1248 self.assertEqual(dic[e], 2)
1249
1250 def test_computations(self):
1251 a = self.theclass(2002, 1, 31)
1252 b = self.theclass(1956, 1, 31)
1253 diff = a-b
1254 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1255 self.assertEqual(diff.seconds, 0)
1256 self.assertEqual(diff.microseconds, 0)
1257 a = self.theclass(2002, 3, 2, 17, 6)
1258 millisec = timedelta(0, 0, 1000)
1259 hour = timedelta(0, 3600)
1260 day = timedelta(1)
1261 week = timedelta(7)
1262 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1263 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1264 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1265 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1266 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1267 self.assertEqual(a - hour, a + -hour)
1268 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1269 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1270 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1271 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1272 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1273 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1274 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1275 self.assertEqual((a + week) - a, week)
1276 self.assertEqual((a + day) - a, day)
1277 self.assertEqual((a + hour) - a, hour)
1278 self.assertEqual((a + millisec) - a, millisec)
1279 self.assertEqual((a - week) - a, -week)
1280 self.assertEqual((a - day) - a, -day)
1281 self.assertEqual((a - hour) - a, -hour)
1282 self.assertEqual((a - millisec) - a, -millisec)
1283 self.assertEqual(a - (a + week), -week)
1284 self.assertEqual(a - (a + day), -day)
1285 self.assertEqual(a - (a + hour), -hour)
1286 self.assertEqual(a - (a + millisec), -millisec)
1287 self.assertEqual(a - (a - week), week)
1288 self.assertEqual(a - (a - day), day)
1289 self.assertEqual(a - (a - hour), hour)
1290 self.assertEqual(a - (a - millisec), millisec)
1291 self.assertEqual(a + (week + day + hour + millisec),
1292 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1293 self.assertEqual(a + (week + day + hour + millisec),
1294 (((a + week) + day) + hour) + millisec)
1295 self.assertEqual(a - (week + day + hour + millisec),
1296 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1297 self.assertEqual(a - (week + day + hour + millisec),
1298 (((a - week) - day) - hour) - millisec)
1299 # Add/sub ints, longs, floats should be illegal
1300 for i in 1, 1L, 1.0:
1301 self.assertRaises(TypeError, lambda: a+i)
1302 self.assertRaises(TypeError, lambda: a-i)
1303 self.assertRaises(TypeError, lambda: i+a)
1304 self.assertRaises(TypeError, lambda: i-a)
1305
1306 # delta - datetime is senseless.
1307 self.assertRaises(TypeError, lambda: day - a)
1308 # mixing datetime and (delta or datetime) via * or // is senseless
1309 self.assertRaises(TypeError, lambda: day * a)
1310 self.assertRaises(TypeError, lambda: a * day)
1311 self.assertRaises(TypeError, lambda: day // a)
1312 self.assertRaises(TypeError, lambda: a // day)
1313 self.assertRaises(TypeError, lambda: a * a)
1314 self.assertRaises(TypeError, lambda: a // a)
1315 # datetime + datetime is senseless
1316 self.assertRaises(TypeError, lambda: a + a)
1317
1318 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001319 args = 6, 7, 23, 20, 59, 1, 64**2
1320 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001321 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001322 green = pickler.dumps(orig, proto)
1323 derived = unpickler.loads(green)
1324 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001325
Guido van Rossum275666f2003-02-07 21:49:01 +00001326 def test_more_pickling(self):
1327 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1328 s = pickle.dumps(a)
1329 b = pickle.loads(s)
1330 self.assertEqual(b.year, 2003)
1331 self.assertEqual(b.month, 2)
1332 self.assertEqual(b.day, 7)
1333
Tim Peters604c0132004-06-07 23:04:33 +00001334 def test_pickling_subclass_datetime(self):
1335 args = 6, 7, 23, 20, 59, 1, 64**2
1336 orig = SubclassDatetime(*args)
1337 for pickler, unpickler, proto in pickle_choices:
1338 green = pickler.dumps(orig, proto)
1339 derived = unpickler.loads(green)
1340 self.assertEqual(orig, derived)
1341
Tim Peters2a799bf2002-12-16 20:18:38 +00001342 def test_more_compare(self):
1343 # The test_compare() inherited from TestDate covers the error cases.
1344 # We just want to test lexicographic ordering on the members datetime
1345 # has that date lacks.
1346 args = [2000, 11, 29, 20, 58, 16, 999998]
1347 t1 = self.theclass(*args)
1348 t2 = self.theclass(*args)
1349 self.failUnless(t1 == t2)
1350 self.failUnless(t1 <= t2)
1351 self.failUnless(t1 >= t2)
1352 self.failUnless(not t1 != t2)
1353 self.failUnless(not t1 < t2)
1354 self.failUnless(not t1 > t2)
1355 self.assertEqual(cmp(t1, t2), 0)
1356 self.assertEqual(cmp(t2, t1), 0)
1357
1358 for i in range(len(args)):
1359 newargs = args[:]
1360 newargs[i] = args[i] + 1
1361 t2 = self.theclass(*newargs) # this is larger than t1
1362 self.failUnless(t1 < t2)
1363 self.failUnless(t2 > t1)
1364 self.failUnless(t1 <= t2)
1365 self.failUnless(t2 >= t1)
1366 self.failUnless(t1 != t2)
1367 self.failUnless(t2 != t1)
1368 self.failUnless(not t1 == t2)
1369 self.failUnless(not t2 == t1)
1370 self.failUnless(not t1 > t2)
1371 self.failUnless(not t2 < t1)
1372 self.failUnless(not t1 >= t2)
1373 self.failUnless(not t2 <= t1)
1374 self.assertEqual(cmp(t1, t2), -1)
1375 self.assertEqual(cmp(t2, t1), 1)
1376
1377
1378 # A helper for timestamp constructor tests.
1379 def verify_field_equality(self, expected, got):
1380 self.assertEqual(expected.tm_year, got.year)
1381 self.assertEqual(expected.tm_mon, got.month)
1382 self.assertEqual(expected.tm_mday, got.day)
1383 self.assertEqual(expected.tm_hour, got.hour)
1384 self.assertEqual(expected.tm_min, got.minute)
1385 self.assertEqual(expected.tm_sec, got.second)
1386
1387 def test_fromtimestamp(self):
1388 import time
1389
1390 ts = time.time()
1391 expected = time.localtime(ts)
1392 got = self.theclass.fromtimestamp(ts)
1393 self.verify_field_equality(expected, got)
1394
1395 def test_utcfromtimestamp(self):
1396 import time
1397
1398 ts = time.time()
1399 expected = time.gmtime(ts)
1400 got = self.theclass.utcfromtimestamp(ts)
1401 self.verify_field_equality(expected, got)
1402
Thomas Wouters477c8d52006-05-27 19:21:47 +00001403 def test_microsecond_rounding(self):
1404 # Test whether fromtimestamp "rounds up" floats that are less
1405 # than one microsecond smaller than an integer.
1406 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1407 self.theclass.fromtimestamp(1))
1408
Tim Peters1b6f7a92004-06-20 02:50:16 +00001409 def test_insane_fromtimestamp(self):
1410 # It's possible that some platform maps time_t to double,
1411 # and that this test will fail there. This test should
1412 # exempt such platforms (provided they return reasonable
1413 # results!).
1414 for insane in -1e200, 1e200:
1415 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1416 insane)
1417
1418 def test_insane_utcfromtimestamp(self):
1419 # It's possible that some platform maps time_t to double,
1420 # and that this test will fail there. This test should
1421 # exempt such platforms (provided they return reasonable
1422 # results!).
1423 for insane in -1e200, 1e200:
1424 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1425 insane)
1426
Tim Peters2a799bf2002-12-16 20:18:38 +00001427 def test_utcnow(self):
1428 import time
1429
1430 # Call it a success if utcnow() and utcfromtimestamp() are within
1431 # a second of each other.
1432 tolerance = timedelta(seconds=1)
1433 for dummy in range(3):
1434 from_now = self.theclass.utcnow()
1435 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1436 if abs(from_timestamp - from_now) <= tolerance:
1437 break
1438 # Else try again a few times.
1439 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1440
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001441 def test_strptime(self):
1442 import time
1443
1444 string = '2004-12-01 13:02:47'
1445 format = '%Y-%m-%d %H:%M:%S'
1446 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1447 got = self.theclass.strptime(string, format)
1448 self.assertEqual(expected, got)
1449
Tim Peters2a799bf2002-12-16 20:18:38 +00001450 def test_more_timetuple(self):
1451 # This tests fields beyond those tested by the TestDate.test_timetuple.
1452 t = self.theclass(2004, 12, 31, 6, 22, 33)
1453 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1454 self.assertEqual(t.timetuple(),
1455 (t.year, t.month, t.day,
1456 t.hour, t.minute, t.second,
1457 t.weekday(),
1458 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1459 -1))
1460 tt = t.timetuple()
1461 self.assertEqual(tt.tm_year, t.year)
1462 self.assertEqual(tt.tm_mon, t.month)
1463 self.assertEqual(tt.tm_mday, t.day)
1464 self.assertEqual(tt.tm_hour, t.hour)
1465 self.assertEqual(tt.tm_min, t.minute)
1466 self.assertEqual(tt.tm_sec, t.second)
1467 self.assertEqual(tt.tm_wday, t.weekday())
1468 self.assertEqual(tt.tm_yday, t.toordinal() -
1469 date(t.year, 1, 1).toordinal() + 1)
1470 self.assertEqual(tt.tm_isdst, -1)
1471
1472 def test_more_strftime(self):
1473 # This tests fields beyond those tested by the TestDate.test_strftime.
1474 t = self.theclass(2004, 12, 31, 6, 22, 33)
1475 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1476 "12 31 04 33 22 06 366")
1477
1478 def test_extract(self):
1479 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1480 self.assertEqual(dt.date(), date(2002, 3, 4))
1481 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1482
1483 def test_combine(self):
1484 d = date(2002, 3, 4)
1485 t = time(18, 45, 3, 1234)
1486 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1487 combine = self.theclass.combine
1488 dt = combine(d, t)
1489 self.assertEqual(dt, expected)
1490
1491 dt = combine(time=t, date=d)
1492 self.assertEqual(dt, expected)
1493
1494 self.assertEqual(d, dt.date())
1495 self.assertEqual(t, dt.time())
1496 self.assertEqual(dt, combine(dt.date(), dt.time()))
1497
1498 self.assertRaises(TypeError, combine) # need an arg
1499 self.assertRaises(TypeError, combine, d) # need two args
1500 self.assertRaises(TypeError, combine, t, d) # args reversed
1501 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1502 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1503
Tim Peters12bf3392002-12-24 05:41:27 +00001504 def test_replace(self):
1505 cls = self.theclass
1506 args = [1, 2, 3, 4, 5, 6, 7]
1507 base = cls(*args)
1508 self.assertEqual(base, base.replace())
1509
1510 i = 0
1511 for name, newval in (("year", 2),
1512 ("month", 3),
1513 ("day", 4),
1514 ("hour", 5),
1515 ("minute", 6),
1516 ("second", 7),
1517 ("microsecond", 8)):
1518 newargs = args[:]
1519 newargs[i] = newval
1520 expected = cls(*newargs)
1521 got = base.replace(**{name: newval})
1522 self.assertEqual(expected, got)
1523 i += 1
1524
1525 # Out of bounds.
1526 base = cls(2000, 2, 29)
1527 self.assertRaises(ValueError, base.replace, year=2001)
1528
Tim Peters80475bb2002-12-25 07:40:55 +00001529 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001530 # Pretty boring! The TZ test is more interesting here. astimezone()
1531 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001532 dt = self.theclass.now()
1533 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001534 self.assertRaises(TypeError, dt.astimezone) # not enough args
1535 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1536 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001537 self.assertRaises(ValueError, dt.astimezone, f) # naive
1538 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001539
Tim Peters52dcce22003-01-23 16:36:11 +00001540 class Bogus(tzinfo):
1541 def utcoffset(self, dt): return None
1542 def dst(self, dt): return timedelta(0)
1543 bog = Bogus()
1544 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1545
1546 class AlsoBogus(tzinfo):
1547 def utcoffset(self, dt): return timedelta(0)
1548 def dst(self, dt): return None
1549 alsobog = AlsoBogus()
1550 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001551
Tim Petersa98924a2003-05-17 05:55:19 +00001552 def test_subclass_datetime(self):
1553
1554 class C(self.theclass):
1555 theAnswer = 42
1556
1557 def __new__(cls, *args, **kws):
1558 temp = kws.copy()
1559 extra = temp.pop('extra')
1560 result = self.theclass.__new__(cls, *args, **temp)
1561 result.extra = extra
1562 return result
1563
1564 def newmeth(self, start):
1565 return start + self.year + self.month + self.second
1566
1567 args = 2003, 4, 14, 12, 13, 41
1568
1569 dt1 = self.theclass(*args)
1570 dt2 = C(*args, **{'extra': 7})
1571
1572 self.assertEqual(dt2.__class__, C)
1573 self.assertEqual(dt2.theAnswer, 42)
1574 self.assertEqual(dt2.extra, 7)
1575 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1576 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1577 dt1.second - 7)
1578
Tim Peters604c0132004-06-07 23:04:33 +00001579class SubclassTime(time):
1580 sub_var = 1
1581
Tim Peters07534a62003-02-07 22:50:28 +00001582class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001583
1584 theclass = time
1585
1586 def test_basic_attributes(self):
1587 t = self.theclass(12, 0)
1588 self.assertEqual(t.hour, 12)
1589 self.assertEqual(t.minute, 0)
1590 self.assertEqual(t.second, 0)
1591 self.assertEqual(t.microsecond, 0)
1592
1593 def test_basic_attributes_nonzero(self):
1594 # Make sure all attributes are non-zero so bugs in
1595 # bit-shifting access show up.
1596 t = self.theclass(12, 59, 59, 8000)
1597 self.assertEqual(t.hour, 12)
1598 self.assertEqual(t.minute, 59)
1599 self.assertEqual(t.second, 59)
1600 self.assertEqual(t.microsecond, 8000)
1601
1602 def test_roundtrip(self):
1603 t = self.theclass(1, 2, 3, 4)
1604
1605 # Verify t -> string -> time identity.
1606 s = repr(t)
1607 self.failUnless(s.startswith('datetime.'))
1608 s = s[9:]
1609 t2 = eval(s)
1610 self.assertEqual(t, t2)
1611
1612 # Verify identity via reconstructing from pieces.
1613 t2 = self.theclass(t.hour, t.minute, t.second,
1614 t.microsecond)
1615 self.assertEqual(t, t2)
1616
1617 def test_comparing(self):
1618 args = [1, 2, 3, 4]
1619 t1 = self.theclass(*args)
1620 t2 = self.theclass(*args)
1621 self.failUnless(t1 == t2)
1622 self.failUnless(t1 <= t2)
1623 self.failUnless(t1 >= t2)
1624 self.failUnless(not t1 != t2)
1625 self.failUnless(not t1 < t2)
1626 self.failUnless(not t1 > t2)
1627 self.assertEqual(cmp(t1, t2), 0)
1628 self.assertEqual(cmp(t2, t1), 0)
1629
1630 for i in range(len(args)):
1631 newargs = args[:]
1632 newargs[i] = args[i] + 1
1633 t2 = self.theclass(*newargs) # this is larger than t1
1634 self.failUnless(t1 < t2)
1635 self.failUnless(t2 > t1)
1636 self.failUnless(t1 <= t2)
1637 self.failUnless(t2 >= t1)
1638 self.failUnless(t1 != t2)
1639 self.failUnless(t2 != t1)
1640 self.failUnless(not t1 == t2)
1641 self.failUnless(not t2 == t1)
1642 self.failUnless(not t1 > t2)
1643 self.failUnless(not t2 < t1)
1644 self.failUnless(not t1 >= t2)
1645 self.failUnless(not t2 <= t1)
1646 self.assertEqual(cmp(t1, t2), -1)
1647 self.assertEqual(cmp(t2, t1), 1)
1648
Tim Peters68124bb2003-02-08 03:46:31 +00001649 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001650 self.assertEqual(t1 == badarg, False)
1651 self.assertEqual(t1 != badarg, True)
1652 self.assertEqual(badarg == t1, False)
1653 self.assertEqual(badarg != t1, True)
1654
Tim Peters2a799bf2002-12-16 20:18:38 +00001655 self.assertRaises(TypeError, lambda: t1 <= badarg)
1656 self.assertRaises(TypeError, lambda: t1 < badarg)
1657 self.assertRaises(TypeError, lambda: t1 > badarg)
1658 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001659 self.assertRaises(TypeError, lambda: badarg <= t1)
1660 self.assertRaises(TypeError, lambda: badarg < t1)
1661 self.assertRaises(TypeError, lambda: badarg > t1)
1662 self.assertRaises(TypeError, lambda: badarg >= t1)
1663
1664 def test_bad_constructor_arguments(self):
1665 # bad hours
1666 self.theclass(0, 0) # no exception
1667 self.theclass(23, 0) # no exception
1668 self.assertRaises(ValueError, self.theclass, -1, 0)
1669 self.assertRaises(ValueError, self.theclass, 24, 0)
1670 # bad minutes
1671 self.theclass(23, 0) # no exception
1672 self.theclass(23, 59) # no exception
1673 self.assertRaises(ValueError, self.theclass, 23, -1)
1674 self.assertRaises(ValueError, self.theclass, 23, 60)
1675 # bad seconds
1676 self.theclass(23, 59, 0) # no exception
1677 self.theclass(23, 59, 59) # no exception
1678 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1679 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1680 # bad microseconds
1681 self.theclass(23, 59, 59, 0) # no exception
1682 self.theclass(23, 59, 59, 999999) # no exception
1683 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1684 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1685
1686 def test_hash_equality(self):
1687 d = self.theclass(23, 30, 17)
1688 e = self.theclass(23, 30, 17)
1689 self.assertEqual(d, e)
1690 self.assertEqual(hash(d), hash(e))
1691
1692 dic = {d: 1}
1693 dic[e] = 2
1694 self.assertEqual(len(dic), 1)
1695 self.assertEqual(dic[d], 2)
1696 self.assertEqual(dic[e], 2)
1697
1698 d = self.theclass(0, 5, 17)
1699 e = self.theclass(0, 5, 17)
1700 self.assertEqual(d, e)
1701 self.assertEqual(hash(d), hash(e))
1702
1703 dic = {d: 1}
1704 dic[e] = 2
1705 self.assertEqual(len(dic), 1)
1706 self.assertEqual(dic[d], 2)
1707 self.assertEqual(dic[e], 2)
1708
1709 def test_isoformat(self):
1710 t = self.theclass(4, 5, 1, 123)
1711 self.assertEqual(t.isoformat(), "04:05:01.000123")
1712 self.assertEqual(t.isoformat(), str(t))
1713
1714 t = self.theclass()
1715 self.assertEqual(t.isoformat(), "00:00:00")
1716 self.assertEqual(t.isoformat(), str(t))
1717
1718 t = self.theclass(microsecond=1)
1719 self.assertEqual(t.isoformat(), "00:00:00.000001")
1720 self.assertEqual(t.isoformat(), str(t))
1721
1722 t = self.theclass(microsecond=10)
1723 self.assertEqual(t.isoformat(), "00:00:00.000010")
1724 self.assertEqual(t.isoformat(), str(t))
1725
1726 t = self.theclass(microsecond=100)
1727 self.assertEqual(t.isoformat(), "00:00:00.000100")
1728 self.assertEqual(t.isoformat(), str(t))
1729
1730 t = self.theclass(microsecond=1000)
1731 self.assertEqual(t.isoformat(), "00:00:00.001000")
1732 self.assertEqual(t.isoformat(), str(t))
1733
1734 t = self.theclass(microsecond=10000)
1735 self.assertEqual(t.isoformat(), "00:00:00.010000")
1736 self.assertEqual(t.isoformat(), str(t))
1737
1738 t = self.theclass(microsecond=100000)
1739 self.assertEqual(t.isoformat(), "00:00:00.100000")
1740 self.assertEqual(t.isoformat(), str(t))
1741
1742 def test_strftime(self):
1743 t = self.theclass(1, 2, 3, 4)
1744 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1745 # A naive object replaces %z and %Z with empty strings.
1746 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1747
1748 def test_str(self):
1749 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1750 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1751 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1752 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1753 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1754
1755 def test_repr(self):
1756 name = 'datetime.' + self.theclass.__name__
1757 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1758 "%s(1, 2, 3, 4)" % name)
1759 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1760 "%s(10, 2, 3, 4000)" % name)
1761 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1762 "%s(0, 2, 3, 400000)" % name)
1763 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1764 "%s(12, 2, 3)" % name)
1765 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1766 "%s(23, 15)" % name)
1767
1768 def test_resolution_info(self):
1769 self.assert_(isinstance(self.theclass.min, self.theclass))
1770 self.assert_(isinstance(self.theclass.max, self.theclass))
1771 self.assert_(isinstance(self.theclass.resolution, timedelta))
1772 self.assert_(self.theclass.max > self.theclass.min)
1773
1774 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001775 args = 20, 59, 16, 64**2
1776 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001777 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001778 green = pickler.dumps(orig, proto)
1779 derived = unpickler.loads(green)
1780 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001781
Tim Peters604c0132004-06-07 23:04:33 +00001782 def test_pickling_subclass_time(self):
1783 args = 20, 59, 16, 64**2
1784 orig = SubclassTime(*args)
1785 for pickler, unpickler, proto in pickle_choices:
1786 green = pickler.dumps(orig, proto)
1787 derived = unpickler.loads(green)
1788 self.assertEqual(orig, derived)
1789
Tim Peters2a799bf2002-12-16 20:18:38 +00001790 def test_bool(self):
1791 cls = self.theclass
1792 self.failUnless(cls(1))
1793 self.failUnless(cls(0, 1))
1794 self.failUnless(cls(0, 0, 1))
1795 self.failUnless(cls(0, 0, 0, 1))
1796 self.failUnless(not cls(0))
1797 self.failUnless(not cls())
1798
Tim Peters12bf3392002-12-24 05:41:27 +00001799 def test_replace(self):
1800 cls = self.theclass
1801 args = [1, 2, 3, 4]
1802 base = cls(*args)
1803 self.assertEqual(base, base.replace())
1804
1805 i = 0
1806 for name, newval in (("hour", 5),
1807 ("minute", 6),
1808 ("second", 7),
1809 ("microsecond", 8)):
1810 newargs = args[:]
1811 newargs[i] = newval
1812 expected = cls(*newargs)
1813 got = base.replace(**{name: newval})
1814 self.assertEqual(expected, got)
1815 i += 1
1816
1817 # Out of bounds.
1818 base = cls(1)
1819 self.assertRaises(ValueError, base.replace, hour=24)
1820 self.assertRaises(ValueError, base.replace, minute=-1)
1821 self.assertRaises(ValueError, base.replace, second=100)
1822 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1823
Tim Petersa98924a2003-05-17 05:55:19 +00001824 def test_subclass_time(self):
1825
1826 class C(self.theclass):
1827 theAnswer = 42
1828
1829 def __new__(cls, *args, **kws):
1830 temp = kws.copy()
1831 extra = temp.pop('extra')
1832 result = self.theclass.__new__(cls, *args, **temp)
1833 result.extra = extra
1834 return result
1835
1836 def newmeth(self, start):
1837 return start + self.hour + self.second
1838
1839 args = 4, 5, 6
1840
1841 dt1 = self.theclass(*args)
1842 dt2 = C(*args, **{'extra': 7})
1843
1844 self.assertEqual(dt2.__class__, C)
1845 self.assertEqual(dt2.theAnswer, 42)
1846 self.assertEqual(dt2.extra, 7)
1847 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1848 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1849
Armin Rigof4afb212005-11-07 07:15:48 +00001850 def test_backdoor_resistance(self):
1851 # see TestDate.test_backdoor_resistance().
1852 base = '2:59.0'
1853 for hour_byte in ' ', '9', chr(24), '\xff':
1854 self.assertRaises(TypeError, self.theclass,
1855 hour_byte + base[1:])
1856
Tim Peters855fe882002-12-22 03:43:39 +00001857# A mixin for classes with a tzinfo= argument. Subclasses must define
1858# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001859# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001860class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001861
Tim Petersbad8ff02002-12-30 20:52:32 +00001862 def test_argument_passing(self):
1863 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001864 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001865 class introspective(tzinfo):
1866 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001867 def utcoffset(self, dt):
1868 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001869 dst = utcoffset
1870
1871 obj = cls(1, 2, 3, tzinfo=introspective())
1872
Tim Peters0bf60bd2003-01-08 20:40:01 +00001873 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001874 self.assertEqual(obj.tzname(), expected)
1875
Tim Peters0bf60bd2003-01-08 20:40:01 +00001876 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001877 self.assertEqual(obj.utcoffset(), expected)
1878 self.assertEqual(obj.dst(), expected)
1879
Tim Peters855fe882002-12-22 03:43:39 +00001880 def test_bad_tzinfo_classes(self):
1881 cls = self.theclass
1882 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001883
Tim Peters855fe882002-12-22 03:43:39 +00001884 class NiceTry(object):
1885 def __init__(self): pass
1886 def utcoffset(self, dt): pass
1887 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1888
1889 class BetterTry(tzinfo):
1890 def __init__(self): pass
1891 def utcoffset(self, dt): pass
1892 b = BetterTry()
1893 t = cls(1, 1, 1, tzinfo=b)
1894 self.failUnless(t.tzinfo is b)
1895
1896 def test_utc_offset_out_of_bounds(self):
1897 class Edgy(tzinfo):
1898 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001899 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001900 def utcoffset(self, dt):
1901 return self.offset
1902
1903 cls = self.theclass
1904 for offset, legit in ((-1440, False),
1905 (-1439, True),
1906 (1439, True),
1907 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001908 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001909 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001910 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001911 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001912 else:
1913 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001914 if legit:
1915 aofs = abs(offset)
1916 h, m = divmod(aofs, 60)
1917 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001918 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001919 t = t.timetz()
1920 self.assertEqual(str(t), "01:02:03" + tag)
1921 else:
1922 self.assertRaises(ValueError, str, t)
1923
1924 def test_tzinfo_classes(self):
1925 cls = self.theclass
1926 class C1(tzinfo):
1927 def utcoffset(self, dt): return None
1928 def dst(self, dt): return None
1929 def tzname(self, dt): return None
1930 for t in (cls(1, 1, 1),
1931 cls(1, 1, 1, tzinfo=None),
1932 cls(1, 1, 1, tzinfo=C1())):
1933 self.failUnless(t.utcoffset() is None)
1934 self.failUnless(t.dst() is None)
1935 self.failUnless(t.tzname() is None)
1936
Tim Peters855fe882002-12-22 03:43:39 +00001937 class C3(tzinfo):
1938 def utcoffset(self, dt): return timedelta(minutes=-1439)
1939 def dst(self, dt): return timedelta(minutes=1439)
1940 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001941 t = cls(1, 1, 1, tzinfo=C3())
1942 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1943 self.assertEqual(t.dst(), timedelta(minutes=1439))
1944 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001945
1946 # Wrong types.
1947 class C4(tzinfo):
1948 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001949 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001950 def tzname(self, dt): return 0
1951 t = cls(1, 1, 1, tzinfo=C4())
1952 self.assertRaises(TypeError, t.utcoffset)
1953 self.assertRaises(TypeError, t.dst)
1954 self.assertRaises(TypeError, t.tzname)
1955
1956 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001957 class C6(tzinfo):
1958 def utcoffset(self, dt): return timedelta(hours=-24)
1959 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001960 t = cls(1, 1, 1, tzinfo=C6())
1961 self.assertRaises(ValueError, t.utcoffset)
1962 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001963
1964 # Not a whole number of minutes.
1965 class C7(tzinfo):
1966 def utcoffset(self, dt): return timedelta(seconds=61)
1967 def dst(self, dt): return timedelta(microseconds=-81)
1968 t = cls(1, 1, 1, tzinfo=C7())
1969 self.assertRaises(ValueError, t.utcoffset)
1970 self.assertRaises(ValueError, t.dst)
1971
Tim Peters4c0db782002-12-26 05:01:19 +00001972 def test_aware_compare(self):
1973 cls = self.theclass
1974
Tim Peters60c76e42002-12-27 00:41:11 +00001975 # Ensure that utcoffset() gets ignored if the comparands have
1976 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001977 class OperandDependentOffset(tzinfo):
1978 def utcoffset(self, t):
1979 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001980 # d0 and d1 equal after adjustment
1981 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001982 else:
Tim Peters397301e2003-01-02 21:28:08 +00001983 # d2 off in the weeds
1984 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001985
1986 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1987 d0 = base.replace(minute=3)
1988 d1 = base.replace(minute=9)
1989 d2 = base.replace(minute=11)
1990 for x in d0, d1, d2:
1991 for y in d0, d1, d2:
1992 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001993 expected = cmp(x.minute, y.minute)
1994 self.assertEqual(got, expected)
1995
1996 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001997 # Note that a time can't actually have an operand-depedent offset,
1998 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1999 # so skip this test for time.
2000 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002001 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2002 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2003 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2004 for x in d0, d1, d2:
2005 for y in d0, d1, d2:
2006 got = cmp(x, y)
2007 if (x is d0 or x is d1) and (y is d0 or y is d1):
2008 expected = 0
2009 elif x is y is d2:
2010 expected = 0
2011 elif x is d2:
2012 expected = -1
2013 else:
2014 assert y is d2
2015 expected = 1
2016 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002017
Tim Peters855fe882002-12-22 03:43:39 +00002018
Tim Peters0bf60bd2003-01-08 20:40:01 +00002019# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002020class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002021 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002022
2023 def test_empty(self):
2024 t = self.theclass()
2025 self.assertEqual(t.hour, 0)
2026 self.assertEqual(t.minute, 0)
2027 self.assertEqual(t.second, 0)
2028 self.assertEqual(t.microsecond, 0)
2029 self.failUnless(t.tzinfo is None)
2030
Tim Peters2a799bf2002-12-16 20:18:38 +00002031 def test_zones(self):
2032 est = FixedOffset(-300, "EST", 1)
2033 utc = FixedOffset(0, "UTC", -2)
2034 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002035 t1 = time( 7, 47, tzinfo=est)
2036 t2 = time(12, 47, tzinfo=utc)
2037 t3 = time(13, 47, tzinfo=met)
2038 t4 = time(microsecond=40)
2039 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002040
2041 self.assertEqual(t1.tzinfo, est)
2042 self.assertEqual(t2.tzinfo, utc)
2043 self.assertEqual(t3.tzinfo, met)
2044 self.failUnless(t4.tzinfo is None)
2045 self.assertEqual(t5.tzinfo, utc)
2046
Tim Peters855fe882002-12-22 03:43:39 +00002047 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2048 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2049 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002050 self.failUnless(t4.utcoffset() is None)
2051 self.assertRaises(TypeError, t1.utcoffset, "no args")
2052
2053 self.assertEqual(t1.tzname(), "EST")
2054 self.assertEqual(t2.tzname(), "UTC")
2055 self.assertEqual(t3.tzname(), "MET")
2056 self.failUnless(t4.tzname() is None)
2057 self.assertRaises(TypeError, t1.tzname, "no args")
2058
Tim Peters855fe882002-12-22 03:43:39 +00002059 self.assertEqual(t1.dst(), timedelta(minutes=1))
2060 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2061 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002062 self.failUnless(t4.dst() is None)
2063 self.assertRaises(TypeError, t1.dst, "no args")
2064
2065 self.assertEqual(hash(t1), hash(t2))
2066 self.assertEqual(hash(t1), hash(t3))
2067 self.assertEqual(hash(t2), hash(t3))
2068
2069 self.assertEqual(t1, t2)
2070 self.assertEqual(t1, t3)
2071 self.assertEqual(t2, t3)
2072 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2073 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2074 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2075
2076 self.assertEqual(str(t1), "07:47:00-05:00")
2077 self.assertEqual(str(t2), "12:47:00+00:00")
2078 self.assertEqual(str(t3), "13:47:00+01:00")
2079 self.assertEqual(str(t4), "00:00:00.000040")
2080 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2081
2082 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2083 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2084 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2085 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2086 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2087
Tim Peters0bf60bd2003-01-08 20:40:01 +00002088 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002089 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2090 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2091 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2092 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2093 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2094
2095 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2096 "07:47:00 %Z=EST %z=-0500")
2097 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2098 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2099
2100 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002101 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002102 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2103 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2104
Tim Petersb92bb712002-12-21 17:44:07 +00002105 # Check that an invalid tzname result raises an exception.
2106 class Badtzname(tzinfo):
2107 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002108 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002109 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2110 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002111
2112 def test_hash_edge_cases(self):
2113 # Offsets that overflow a basic time.
2114 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2115 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2116 self.assertEqual(hash(t1), hash(t2))
2117
2118 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2119 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2120 self.assertEqual(hash(t1), hash(t2))
2121
Tim Peters2a799bf2002-12-16 20:18:38 +00002122 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002123 # Try one without a tzinfo.
2124 args = 20, 59, 16, 64**2
2125 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002126 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002127 green = pickler.dumps(orig, proto)
2128 derived = unpickler.loads(green)
2129 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002130
2131 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002132 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002133 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002134 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002135 green = pickler.dumps(orig, proto)
2136 derived = unpickler.loads(green)
2137 self.assertEqual(orig, derived)
2138 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2139 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2140 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002141
2142 def test_more_bool(self):
2143 # Test cases with non-None tzinfo.
2144 cls = self.theclass
2145
2146 t = cls(0, tzinfo=FixedOffset(-300, ""))
2147 self.failUnless(t)
2148
2149 t = cls(5, tzinfo=FixedOffset(-300, ""))
2150 self.failUnless(t)
2151
2152 t = cls(5, tzinfo=FixedOffset(300, ""))
2153 self.failUnless(not t)
2154
2155 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2156 self.failUnless(not t)
2157
2158 # Mostly ensuring this doesn't overflow internally.
2159 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2160 self.failUnless(t)
2161
2162 # But this should yield a value error -- the utcoffset is bogus.
2163 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2164 self.assertRaises(ValueError, lambda: bool(t))
2165
2166 # Likewise.
2167 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2168 self.assertRaises(ValueError, lambda: bool(t))
2169
Tim Peters12bf3392002-12-24 05:41:27 +00002170 def test_replace(self):
2171 cls = self.theclass
2172 z100 = FixedOffset(100, "+100")
2173 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2174 args = [1, 2, 3, 4, z100]
2175 base = cls(*args)
2176 self.assertEqual(base, base.replace())
2177
2178 i = 0
2179 for name, newval in (("hour", 5),
2180 ("minute", 6),
2181 ("second", 7),
2182 ("microsecond", 8),
2183 ("tzinfo", zm200)):
2184 newargs = args[:]
2185 newargs[i] = newval
2186 expected = cls(*newargs)
2187 got = base.replace(**{name: newval})
2188 self.assertEqual(expected, got)
2189 i += 1
2190
2191 # Ensure we can get rid of a tzinfo.
2192 self.assertEqual(base.tzname(), "+100")
2193 base2 = base.replace(tzinfo=None)
2194 self.failUnless(base2.tzinfo is None)
2195 self.failUnless(base2.tzname() is None)
2196
2197 # Ensure we can add one.
2198 base3 = base2.replace(tzinfo=z100)
2199 self.assertEqual(base, base3)
2200 self.failUnless(base.tzinfo is base3.tzinfo)
2201
2202 # Out of bounds.
2203 base = cls(1)
2204 self.assertRaises(ValueError, base.replace, hour=24)
2205 self.assertRaises(ValueError, base.replace, minute=-1)
2206 self.assertRaises(ValueError, base.replace, second=100)
2207 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2208
Tim Peters60c76e42002-12-27 00:41:11 +00002209 def test_mixed_compare(self):
2210 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002211 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002212 self.assertEqual(t1, t2)
2213 t2 = t2.replace(tzinfo=None)
2214 self.assertEqual(t1, t2)
2215 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2216 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002217 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2218 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002219
Tim Peters0bf60bd2003-01-08 20:40:01 +00002220 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002221 class Varies(tzinfo):
2222 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002223 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002224 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002225 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002226 return self.offset
2227
2228 v = Varies()
2229 t1 = t2.replace(tzinfo=v)
2230 t2 = t2.replace(tzinfo=v)
2231 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2232 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2233 self.assertEqual(t1, t2)
2234
2235 # But if they're not identical, it isn't ignored.
2236 t2 = t2.replace(tzinfo=Varies())
2237 self.failUnless(t1 < t2) # t1's offset counter still going up
2238
Tim Petersa98924a2003-05-17 05:55:19 +00002239 def test_subclass_timetz(self):
2240
2241 class C(self.theclass):
2242 theAnswer = 42
2243
2244 def __new__(cls, *args, **kws):
2245 temp = kws.copy()
2246 extra = temp.pop('extra')
2247 result = self.theclass.__new__(cls, *args, **temp)
2248 result.extra = extra
2249 return result
2250
2251 def newmeth(self, start):
2252 return start + self.hour + self.second
2253
2254 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2255
2256 dt1 = self.theclass(*args)
2257 dt2 = C(*args, **{'extra': 7})
2258
2259 self.assertEqual(dt2.__class__, C)
2260 self.assertEqual(dt2.theAnswer, 42)
2261 self.assertEqual(dt2.extra, 7)
2262 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2263 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2264
Tim Peters4c0db782002-12-26 05:01:19 +00002265
Tim Peters0bf60bd2003-01-08 20:40:01 +00002266# Testing datetime objects with a non-None tzinfo.
2267
Tim Peters855fe882002-12-22 03:43:39 +00002268class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002269 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002270
2271 def test_trivial(self):
2272 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2273 self.assertEqual(dt.year, 1)
2274 self.assertEqual(dt.month, 2)
2275 self.assertEqual(dt.day, 3)
2276 self.assertEqual(dt.hour, 4)
2277 self.assertEqual(dt.minute, 5)
2278 self.assertEqual(dt.second, 6)
2279 self.assertEqual(dt.microsecond, 7)
2280 self.assertEqual(dt.tzinfo, None)
2281
2282 def test_even_more_compare(self):
2283 # The test_compare() and test_more_compare() inherited from TestDate
2284 # and TestDateTime covered non-tzinfo cases.
2285
2286 # Smallest possible after UTC adjustment.
2287 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2288 # Largest possible after UTC adjustment.
2289 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2290 tzinfo=FixedOffset(-1439, ""))
2291
2292 # Make sure those compare correctly, and w/o overflow.
2293 self.failUnless(t1 < t2)
2294 self.failUnless(t1 != t2)
2295 self.failUnless(t2 > t1)
2296
2297 self.failUnless(t1 == t1)
2298 self.failUnless(t2 == t2)
2299
2300 # Equal afer adjustment.
2301 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2302 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2303 self.assertEqual(t1, t2)
2304
2305 # Change t1 not to subtract a minute, and t1 should be larger.
2306 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2307 self.failUnless(t1 > t2)
2308
2309 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2310 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2311 self.failUnless(t1 < t2)
2312
2313 # Back to the original t1, but make seconds resolve it.
2314 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2315 second=1)
2316 self.failUnless(t1 > t2)
2317
2318 # Likewise, but make microseconds resolve it.
2319 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2320 microsecond=1)
2321 self.failUnless(t1 > t2)
2322
2323 # Make t2 naive and it should fail.
2324 t2 = self.theclass.min
2325 self.assertRaises(TypeError, lambda: t1 == t2)
2326 self.assertEqual(t2, t2)
2327
2328 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2329 class Naive(tzinfo):
2330 def utcoffset(self, dt): return None
2331 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2332 self.assertRaises(TypeError, lambda: t1 == t2)
2333 self.assertEqual(t2, t2)
2334
2335 # OTOH, it's OK to compare two of these mixing the two ways of being
2336 # naive.
2337 t1 = self.theclass(5, 6, 7)
2338 self.assertEqual(t1, t2)
2339
2340 # Try a bogus uctoffset.
2341 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002342 def utcoffset(self, dt):
2343 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002344 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2345 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002346 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002347
Tim Peters2a799bf2002-12-16 20:18:38 +00002348 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002349 # Try one without a tzinfo.
2350 args = 6, 7, 23, 20, 59, 1, 64**2
2351 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002352 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002353 green = pickler.dumps(orig, proto)
2354 derived = unpickler.loads(green)
2355 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002356
2357 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002358 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002359 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002360 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002361 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002362 green = pickler.dumps(orig, proto)
2363 derived = unpickler.loads(green)
2364 self.assertEqual(orig, derived)
2365 self.failUnless(isinstance(derived.tzinfo,
2366 PicklableFixedOffset))
2367 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2368 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002369
2370 def test_extreme_hashes(self):
2371 # If an attempt is made to hash these via subtracting the offset
2372 # then hashing a datetime object, OverflowError results. The
2373 # Python implementation used to blow up here.
2374 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2375 hash(t)
2376 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2377 tzinfo=FixedOffset(-1439, ""))
2378 hash(t)
2379
2380 # OTOH, an OOB offset should blow up.
2381 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2382 self.assertRaises(ValueError, hash, t)
2383
2384 def test_zones(self):
2385 est = FixedOffset(-300, "EST")
2386 utc = FixedOffset(0, "UTC")
2387 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002388 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2389 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2390 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002391 self.assertEqual(t1.tzinfo, est)
2392 self.assertEqual(t2.tzinfo, utc)
2393 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002394 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2395 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2396 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002397 self.assertEqual(t1.tzname(), "EST")
2398 self.assertEqual(t2.tzname(), "UTC")
2399 self.assertEqual(t3.tzname(), "MET")
2400 self.assertEqual(hash(t1), hash(t2))
2401 self.assertEqual(hash(t1), hash(t3))
2402 self.assertEqual(hash(t2), hash(t3))
2403 self.assertEqual(t1, t2)
2404 self.assertEqual(t1, t3)
2405 self.assertEqual(t2, t3)
2406 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2407 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2408 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002409 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002410 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2411 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2412 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2413
2414 def test_combine(self):
2415 met = FixedOffset(60, "MET")
2416 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002417 tz = time(18, 45, 3, 1234, tzinfo=met)
2418 dt = datetime.combine(d, tz)
2419 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002420 tzinfo=met))
2421
2422 def test_extract(self):
2423 met = FixedOffset(60, "MET")
2424 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2425 self.assertEqual(dt.date(), date(2002, 3, 4))
2426 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002427 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002428
2429 def test_tz_aware_arithmetic(self):
2430 import random
2431
2432 now = self.theclass.now()
2433 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002434 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002435 nowaware = self.theclass.combine(now.date(), timeaware)
2436 self.failUnless(nowaware.tzinfo is tz55)
2437 self.assertEqual(nowaware.timetz(), timeaware)
2438
2439 # Can't mix aware and non-aware.
2440 self.assertRaises(TypeError, lambda: now - nowaware)
2441 self.assertRaises(TypeError, lambda: nowaware - now)
2442
Tim Peters0bf60bd2003-01-08 20:40:01 +00002443 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002444 self.assertRaises(TypeError, lambda: now + nowaware)
2445 self.assertRaises(TypeError, lambda: nowaware + now)
2446 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2447
2448 # Subtracting should yield 0.
2449 self.assertEqual(now - now, timedelta(0))
2450 self.assertEqual(nowaware - nowaware, timedelta(0))
2451
2452 # Adding a delta should preserve tzinfo.
2453 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2454 nowawareplus = nowaware + delta
2455 self.failUnless(nowaware.tzinfo is tz55)
2456 nowawareplus2 = delta + nowaware
2457 self.failUnless(nowawareplus2.tzinfo is tz55)
2458 self.assertEqual(nowawareplus, nowawareplus2)
2459
2460 # that - delta should be what we started with, and that - what we
2461 # started with should be delta.
2462 diff = nowawareplus - delta
2463 self.failUnless(diff.tzinfo is tz55)
2464 self.assertEqual(nowaware, diff)
2465 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2466 self.assertEqual(nowawareplus - nowaware, delta)
2467
2468 # Make up a random timezone.
2469 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002470 # Attach it to nowawareplus.
2471 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002472 self.failUnless(nowawareplus.tzinfo is tzr)
2473 # Make sure the difference takes the timezone adjustments into account.
2474 got = nowaware - nowawareplus
2475 # Expected: (nowaware base - nowaware offset) -
2476 # (nowawareplus base - nowawareplus offset) =
2477 # (nowaware base - nowawareplus base) +
2478 # (nowawareplus offset - nowaware offset) =
2479 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002480 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002481 self.assertEqual(got, expected)
2482
2483 # Try max possible difference.
2484 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2485 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2486 tzinfo=FixedOffset(-1439, "max"))
2487 maxdiff = max - min
2488 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2489 timedelta(minutes=2*1439))
2490
2491 def test_tzinfo_now(self):
2492 meth = self.theclass.now
2493 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2494 base = meth()
2495 # Try with and without naming the keyword.
2496 off42 = FixedOffset(42, "42")
2497 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002498 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002499 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002500 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002501 # Bad argument with and w/o naming the keyword.
2502 self.assertRaises(TypeError, meth, 16)
2503 self.assertRaises(TypeError, meth, tzinfo=16)
2504 # Bad keyword name.
2505 self.assertRaises(TypeError, meth, tinfo=off42)
2506 # Too many args.
2507 self.assertRaises(TypeError, meth, off42, off42)
2508
Tim Peters10cadce2003-01-23 19:58:02 +00002509 # We don't know which time zone we're in, and don't have a tzinfo
2510 # class to represent it, so seeing whether a tz argument actually
2511 # does a conversion is tricky.
2512 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2513 utc = FixedOffset(0, "utc", 0)
2514 for dummy in range(3):
2515 now = datetime.now(weirdtz)
2516 self.failUnless(now.tzinfo is weirdtz)
2517 utcnow = datetime.utcnow().replace(tzinfo=utc)
2518 now2 = utcnow.astimezone(weirdtz)
2519 if abs(now - now2) < timedelta(seconds=30):
2520 break
2521 # Else the code is broken, or more than 30 seconds passed between
2522 # calls; assuming the latter, just try again.
2523 else:
2524 # Three strikes and we're out.
2525 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2526
Tim Peters2a799bf2002-12-16 20:18:38 +00002527 def test_tzinfo_fromtimestamp(self):
2528 import time
2529 meth = self.theclass.fromtimestamp
2530 ts = time.time()
2531 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2532 base = meth(ts)
2533 # Try with and without naming the keyword.
2534 off42 = FixedOffset(42, "42")
2535 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002536 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002537 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002538 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002539 # Bad argument with and w/o naming the keyword.
2540 self.assertRaises(TypeError, meth, ts, 16)
2541 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2542 # Bad keyword name.
2543 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2544 # Too many args.
2545 self.assertRaises(TypeError, meth, ts, off42, off42)
2546 # Too few args.
2547 self.assertRaises(TypeError, meth)
2548
Tim Peters2a44a8d2003-01-23 20:53:10 +00002549 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002550 timestamp = 1000000000
2551 utcdatetime = datetime.utcfromtimestamp(timestamp)
2552 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2553 # But on some flavor of Mac, it's nowhere near that. So we can't have
2554 # any idea here what time that actually is, we can only test that
2555 # relative changes match.
2556 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2557 tz = FixedOffset(utcoffset, "tz", 0)
2558 expected = utcdatetime + utcoffset
2559 got = datetime.fromtimestamp(timestamp, tz)
2560 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002561
Tim Peters2a799bf2002-12-16 20:18:38 +00002562 def test_tzinfo_utcnow(self):
2563 meth = self.theclass.utcnow
2564 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2565 base = meth()
2566 # Try with and without naming the keyword; for whatever reason,
2567 # utcnow() doesn't accept a tzinfo argument.
2568 off42 = FixedOffset(42, "42")
2569 self.assertRaises(TypeError, meth, off42)
2570 self.assertRaises(TypeError, meth, tzinfo=off42)
2571
2572 def test_tzinfo_utcfromtimestamp(self):
2573 import time
2574 meth = self.theclass.utcfromtimestamp
2575 ts = time.time()
2576 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2577 base = meth(ts)
2578 # Try with and without naming the keyword; for whatever reason,
2579 # utcfromtimestamp() doesn't accept a tzinfo argument.
2580 off42 = FixedOffset(42, "42")
2581 self.assertRaises(TypeError, meth, ts, off42)
2582 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2583
2584 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002585 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002586 # DST flag.
2587 class DST(tzinfo):
2588 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002589 if isinstance(dstvalue, int):
2590 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002591 self.dstvalue = dstvalue
2592 def dst(self, dt):
2593 return self.dstvalue
2594
2595 cls = self.theclass
2596 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2597 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2598 t = d.timetuple()
2599 self.assertEqual(1, t.tm_year)
2600 self.assertEqual(1, t.tm_mon)
2601 self.assertEqual(1, t.tm_mday)
2602 self.assertEqual(10, t.tm_hour)
2603 self.assertEqual(20, t.tm_min)
2604 self.assertEqual(30, t.tm_sec)
2605 self.assertEqual(0, t.tm_wday)
2606 self.assertEqual(1, t.tm_yday)
2607 self.assertEqual(flag, t.tm_isdst)
2608
2609 # dst() returns wrong type.
2610 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2611
2612 # dst() at the edge.
2613 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2614 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2615
2616 # dst() out of range.
2617 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2618 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2619
2620 def test_utctimetuple(self):
2621 class DST(tzinfo):
2622 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002623 if isinstance(dstvalue, int):
2624 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002625 self.dstvalue = dstvalue
2626 def dst(self, dt):
2627 return self.dstvalue
2628
2629 cls = self.theclass
2630 # This can't work: DST didn't implement utcoffset.
2631 self.assertRaises(NotImplementedError,
2632 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2633
2634 class UOFS(DST):
2635 def __init__(self, uofs, dofs=None):
2636 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002637 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002638 def utcoffset(self, dt):
2639 return self.uofs
2640
2641 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2642 # in effect for a UTC time.
2643 for dstvalue in -33, 33, 0, None:
2644 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2645 t = d.utctimetuple()
2646 self.assertEqual(d.year, t.tm_year)
2647 self.assertEqual(d.month, t.tm_mon)
2648 self.assertEqual(d.day, t.tm_mday)
2649 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2650 self.assertEqual(13, t.tm_min)
2651 self.assertEqual(d.second, t.tm_sec)
2652 self.assertEqual(d.weekday(), t.tm_wday)
2653 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2654 t.tm_yday)
2655 self.assertEqual(0, t.tm_isdst)
2656
2657 # At the edges, UTC adjustment can normalize into years out-of-range
2658 # for a datetime object. Ensure that a correct timetuple is
2659 # created anyway.
2660 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2661 # That goes back 1 minute less than a full day.
2662 t = tiny.utctimetuple()
2663 self.assertEqual(t.tm_year, MINYEAR-1)
2664 self.assertEqual(t.tm_mon, 12)
2665 self.assertEqual(t.tm_mday, 31)
2666 self.assertEqual(t.tm_hour, 0)
2667 self.assertEqual(t.tm_min, 1)
2668 self.assertEqual(t.tm_sec, 37)
2669 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2670 self.assertEqual(t.tm_isdst, 0)
2671
2672 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2673 # That goes forward 1 minute less than a full day.
2674 t = huge.utctimetuple()
2675 self.assertEqual(t.tm_year, MAXYEAR+1)
2676 self.assertEqual(t.tm_mon, 1)
2677 self.assertEqual(t.tm_mday, 1)
2678 self.assertEqual(t.tm_hour, 23)
2679 self.assertEqual(t.tm_min, 58)
2680 self.assertEqual(t.tm_sec, 37)
2681 self.assertEqual(t.tm_yday, 1)
2682 self.assertEqual(t.tm_isdst, 0)
2683
2684 def test_tzinfo_isoformat(self):
2685 zero = FixedOffset(0, "+00:00")
2686 plus = FixedOffset(220, "+03:40")
2687 minus = FixedOffset(-231, "-03:51")
2688 unknown = FixedOffset(None, "")
2689
2690 cls = self.theclass
2691 datestr = '0001-02-03'
2692 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002693 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002694 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2695 timestr = '04:05:59' + (us and '.987001' or '')
2696 ofsstr = ofs is not None and d.tzname() or ''
2697 tailstr = timestr + ofsstr
2698 iso = d.isoformat()
2699 self.assertEqual(iso, datestr + 'T' + tailstr)
2700 self.assertEqual(iso, d.isoformat('T'))
2701 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2702 self.assertEqual(str(d), datestr + ' ' + tailstr)
2703
Tim Peters12bf3392002-12-24 05:41:27 +00002704 def test_replace(self):
2705 cls = self.theclass
2706 z100 = FixedOffset(100, "+100")
2707 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2708 args = [1, 2, 3, 4, 5, 6, 7, z100]
2709 base = cls(*args)
2710 self.assertEqual(base, base.replace())
2711
2712 i = 0
2713 for name, newval in (("year", 2),
2714 ("month", 3),
2715 ("day", 4),
2716 ("hour", 5),
2717 ("minute", 6),
2718 ("second", 7),
2719 ("microsecond", 8),
2720 ("tzinfo", zm200)):
2721 newargs = args[:]
2722 newargs[i] = newval
2723 expected = cls(*newargs)
2724 got = base.replace(**{name: newval})
2725 self.assertEqual(expected, got)
2726 i += 1
2727
2728 # Ensure we can get rid of a tzinfo.
2729 self.assertEqual(base.tzname(), "+100")
2730 base2 = base.replace(tzinfo=None)
2731 self.failUnless(base2.tzinfo is None)
2732 self.failUnless(base2.tzname() is None)
2733
2734 # Ensure we can add one.
2735 base3 = base2.replace(tzinfo=z100)
2736 self.assertEqual(base, base3)
2737 self.failUnless(base.tzinfo is base3.tzinfo)
2738
2739 # Out of bounds.
2740 base = cls(2000, 2, 29)
2741 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002742
Tim Peters80475bb2002-12-25 07:40:55 +00002743 def test_more_astimezone(self):
2744 # The inherited test_astimezone covered some trivial and error cases.
2745 fnone = FixedOffset(None, "None")
2746 f44m = FixedOffset(44, "44")
2747 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2748
Tim Peters10cadce2003-01-23 19:58:02 +00002749 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002750 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002751 # Replacing with degenerate tzinfo raises an exception.
2752 self.assertRaises(ValueError, dt.astimezone, fnone)
2753 # Ditto with None tz.
2754 self.assertRaises(TypeError, dt.astimezone, None)
2755 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002756 x = dt.astimezone(dt.tzinfo)
2757 self.failUnless(x.tzinfo is f44m)
2758 self.assertEqual(x.date(), dt.date())
2759 self.assertEqual(x.time(), dt.time())
2760
2761 # Replacing with different tzinfo does adjust.
2762 got = dt.astimezone(fm5h)
2763 self.failUnless(got.tzinfo is fm5h)
2764 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2765 expected = dt - dt.utcoffset() # in effect, convert to UTC
2766 expected += fm5h.utcoffset(dt) # and from there to local time
2767 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2768 self.assertEqual(got.date(), expected.date())
2769 self.assertEqual(got.time(), expected.time())
2770 self.assertEqual(got.timetz(), expected.timetz())
2771 self.failUnless(got.tzinfo is expected.tzinfo)
2772 self.assertEqual(got, expected)
2773
Tim Peters4c0db782002-12-26 05:01:19 +00002774 def test_aware_subtract(self):
2775 cls = self.theclass
2776
Tim Peters60c76e42002-12-27 00:41:11 +00002777 # Ensure that utcoffset() is ignored when the operands have the
2778 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002779 class OperandDependentOffset(tzinfo):
2780 def utcoffset(self, t):
2781 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002782 # d0 and d1 equal after adjustment
2783 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002784 else:
Tim Peters397301e2003-01-02 21:28:08 +00002785 # d2 off in the weeds
2786 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002787
2788 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2789 d0 = base.replace(minute=3)
2790 d1 = base.replace(minute=9)
2791 d2 = base.replace(minute=11)
2792 for x in d0, d1, d2:
2793 for y in d0, d1, d2:
2794 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002795 expected = timedelta(minutes=x.minute - y.minute)
2796 self.assertEqual(got, expected)
2797
2798 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2799 # ignored.
2800 base = cls(8, 9, 10, 11, 12, 13, 14)
2801 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2802 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2803 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2804 for x in d0, d1, d2:
2805 for y in d0, d1, d2:
2806 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002807 if (x is d0 or x is d1) and (y is d0 or y is d1):
2808 expected = timedelta(0)
2809 elif x is y is d2:
2810 expected = timedelta(0)
2811 elif x is d2:
2812 expected = timedelta(minutes=(11-59)-0)
2813 else:
2814 assert y is d2
2815 expected = timedelta(minutes=0-(11-59))
2816 self.assertEqual(got, expected)
2817
Tim Peters60c76e42002-12-27 00:41:11 +00002818 def test_mixed_compare(self):
2819 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002820 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002821 self.assertEqual(t1, t2)
2822 t2 = t2.replace(tzinfo=None)
2823 self.assertEqual(t1, t2)
2824 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2825 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002826 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2827 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002828
Tim Peters0bf60bd2003-01-08 20:40:01 +00002829 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002830 class Varies(tzinfo):
2831 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002832 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002833 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002834 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002835 return self.offset
2836
2837 v = Varies()
2838 t1 = t2.replace(tzinfo=v)
2839 t2 = t2.replace(tzinfo=v)
2840 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2841 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2842 self.assertEqual(t1, t2)
2843
2844 # But if they're not identical, it isn't ignored.
2845 t2 = t2.replace(tzinfo=Varies())
2846 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002847
Tim Petersa98924a2003-05-17 05:55:19 +00002848 def test_subclass_datetimetz(self):
2849
2850 class C(self.theclass):
2851 theAnswer = 42
2852
2853 def __new__(cls, *args, **kws):
2854 temp = kws.copy()
2855 extra = temp.pop('extra')
2856 result = self.theclass.__new__(cls, *args, **temp)
2857 result.extra = extra
2858 return result
2859
2860 def newmeth(self, start):
2861 return start + self.hour + self.year
2862
2863 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2864
2865 dt1 = self.theclass(*args)
2866 dt2 = C(*args, **{'extra': 7})
2867
2868 self.assertEqual(dt2.__class__, C)
2869 self.assertEqual(dt2.theAnswer, 42)
2870 self.assertEqual(dt2.extra, 7)
2871 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2872 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2873
Tim Peters621818b2002-12-29 23:44:49 +00002874# Pain to set up DST-aware tzinfo classes.
2875
2876def first_sunday_on_or_after(dt):
2877 days_to_go = 6 - dt.weekday()
2878 if days_to_go:
2879 dt += timedelta(days_to_go)
2880 return dt
2881
2882ZERO = timedelta(0)
2883HOUR = timedelta(hours=1)
2884DAY = timedelta(days=1)
2885# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2886DSTSTART = datetime(1, 4, 1, 2)
2887# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002888# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2889# being standard time on that day, there is no spelling in local time of
2890# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2891DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002892
2893class USTimeZone(tzinfo):
2894
2895 def __init__(self, hours, reprname, stdname, dstname):
2896 self.stdoffset = timedelta(hours=hours)
2897 self.reprname = reprname
2898 self.stdname = stdname
2899 self.dstname = dstname
2900
2901 def __repr__(self):
2902 return self.reprname
2903
2904 def tzname(self, dt):
2905 if self.dst(dt):
2906 return self.dstname
2907 else:
2908 return self.stdname
2909
2910 def utcoffset(self, dt):
2911 return self.stdoffset + self.dst(dt)
2912
2913 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002914 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002915 # An exception instead may be sensible here, in one or more of
2916 # the cases.
2917 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002918 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002919
2920 # Find first Sunday in April.
2921 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2922 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2923
2924 # Find last Sunday in October.
2925 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2926 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2927
Tim Peters621818b2002-12-29 23:44:49 +00002928 # Can't compare naive to aware objects, so strip the timezone from
2929 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002930 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002931 return HOUR
2932 else:
2933 return ZERO
2934
Tim Peters521fc152002-12-31 17:36:56 +00002935Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2936Central = USTimeZone(-6, "Central", "CST", "CDT")
2937Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2938Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002939utc_real = FixedOffset(0, "UTC", 0)
2940# For better test coverage, we want another flavor of UTC that's west of
2941# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002942utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002943
2944class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002945 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002946 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002947 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002948
Tim Peters0bf60bd2003-01-08 20:40:01 +00002949 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002950
Tim Peters521fc152002-12-31 17:36:56 +00002951 # Check a time that's inside DST.
2952 def checkinside(self, dt, tz, utc, dston, dstoff):
2953 self.assertEqual(dt.dst(), HOUR)
2954
2955 # Conversion to our own timezone is always an identity.
2956 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002957
2958 asutc = dt.astimezone(utc)
2959 there_and_back = asutc.astimezone(tz)
2960
2961 # Conversion to UTC and back isn't always an identity here,
2962 # because there are redundant spellings (in local time) of
2963 # UTC time when DST begins: the clock jumps from 1:59:59
2964 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2965 # make sense then. The classes above treat 2:MM:SS as
2966 # daylight time then (it's "after 2am"), really an alias
2967 # for 1:MM:SS standard time. The latter form is what
2968 # conversion back from UTC produces.
2969 if dt.date() == dston.date() and dt.hour == 2:
2970 # We're in the redundant hour, and coming back from
2971 # UTC gives the 1:MM:SS standard-time spelling.
2972 self.assertEqual(there_and_back + HOUR, dt)
2973 # Although during was considered to be in daylight
2974 # time, there_and_back is not.
2975 self.assertEqual(there_and_back.dst(), ZERO)
2976 # They're the same times in UTC.
2977 self.assertEqual(there_and_back.astimezone(utc),
2978 dt.astimezone(utc))
2979 else:
2980 # We're not in the redundant hour.
2981 self.assertEqual(dt, there_and_back)
2982
Tim Peters327098a2003-01-20 22:54:38 +00002983 # Because we have a redundant spelling when DST begins, there is
2984 # (unforunately) an hour when DST ends that can't be spelled at all in
2985 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2986 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2987 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2988 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2989 # expressed in local time. Nevertheless, we want conversion back
2990 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002991 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002992 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002993 if dt.date() == dstoff.date() and dt.hour == 0:
2994 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002995 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002996 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2997 nexthour_utc += HOUR
2998 nexthour_tz = nexthour_utc.astimezone(tz)
2999 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003000 else:
Tim Peters327098a2003-01-20 22:54:38 +00003001 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003002
3003 # Check a time that's outside DST.
3004 def checkoutside(self, dt, tz, utc):
3005 self.assertEqual(dt.dst(), ZERO)
3006
3007 # Conversion to our own timezone is always an identity.
3008 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003009
3010 # Converting to UTC and back is an identity too.
3011 asutc = dt.astimezone(utc)
3012 there_and_back = asutc.astimezone(tz)
3013 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003014
Tim Peters1024bf82002-12-30 17:09:40 +00003015 def convert_between_tz_and_utc(self, tz, utc):
3016 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003017 # Because 1:MM on the day DST ends is taken as being standard time,
3018 # there is no spelling in tz for the last hour of daylight time.
3019 # For purposes of the test, the last hour of DST is 0:MM, which is
3020 # taken as being daylight time (and 1:MM is taken as being standard
3021 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003022 dstoff = self.dstoff.replace(tzinfo=tz)
3023 for delta in (timedelta(weeks=13),
3024 DAY,
3025 HOUR,
3026 timedelta(minutes=1),
3027 timedelta(microseconds=1)):
3028
Tim Peters521fc152002-12-31 17:36:56 +00003029 self.checkinside(dston, tz, utc, dston, dstoff)
3030 for during in dston + delta, dstoff - delta:
3031 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003032
Tim Peters521fc152002-12-31 17:36:56 +00003033 self.checkoutside(dstoff, tz, utc)
3034 for outside in dston - delta, dstoff + delta:
3035 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003036
Tim Peters621818b2002-12-29 23:44:49 +00003037 def test_easy(self):
3038 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003039 self.convert_between_tz_and_utc(Eastern, utc_real)
3040 self.convert_between_tz_and_utc(Pacific, utc_real)
3041 self.convert_between_tz_and_utc(Eastern, utc_fake)
3042 self.convert_between_tz_and_utc(Pacific, utc_fake)
3043 # The next is really dancing near the edge. It works because
3044 # Pacific and Eastern are far enough apart that their "problem
3045 # hours" don't overlap.
3046 self.convert_between_tz_and_utc(Eastern, Pacific)
3047 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003048 # OTOH, these fail! Don't enable them. The difficulty is that
3049 # the edge case tests assume that every hour is representable in
3050 # the "utc" class. This is always true for a fixed-offset tzinfo
3051 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3052 # For these adjacent DST-aware time zones, the range of time offsets
3053 # tested ends up creating hours in the one that aren't representable
3054 # in the other. For the same reason, we would see failures in the
3055 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3056 # offset deltas in convert_between_tz_and_utc().
3057 #
3058 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3059 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003060
Tim Petersf3615152003-01-01 21:51:37 +00003061 def test_tricky(self):
3062 # 22:00 on day before daylight starts.
3063 fourback = self.dston - timedelta(hours=4)
3064 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003065 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003066 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3067 # 2", we should get the 3 spelling.
3068 # If we plug 22:00 the day before into Eastern, it "looks like std
3069 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3070 # to 22:00 lands on 2:00, which makes no sense in local time (the
3071 # local clock jumps from 1 to 3). The point here is to make sure we
3072 # get the 3 spelling.
3073 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003074 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003075 self.assertEqual(expected, got)
3076
3077 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3078 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003079 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003080 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3081 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3082 # spelling.
3083 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003084 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003085 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003086
Tim Petersadf64202003-01-04 06:03:15 +00003087 # Now on the day DST ends, we want "repeat an hour" behavior.
3088 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3089 # EST 23:MM 0:MM 1:MM 2:MM
3090 # EDT 0:MM 1:MM 2:MM 3:MM
3091 # wall 0:MM 1:MM 1:MM 2:MM against these
3092 for utc in utc_real, utc_fake:
3093 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003094 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003095 # Convert that to UTC.
3096 first_std_hour -= tz.utcoffset(None)
3097 # Adjust for possibly fake UTC.
3098 asutc = first_std_hour + utc.utcoffset(None)
3099 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3100 # tz=Eastern.
3101 asutcbase = asutc.replace(tzinfo=utc)
3102 for tzhour in (0, 1, 1, 2):
3103 expectedbase = self.dstoff.replace(hour=tzhour)
3104 for minute in 0, 30, 59:
3105 expected = expectedbase.replace(minute=minute)
3106 asutc = asutcbase.replace(minute=minute)
3107 astz = asutc.astimezone(tz)
3108 self.assertEqual(astz.replace(tzinfo=None), expected)
3109 asutcbase += HOUR
3110
3111
Tim Peters710fb152003-01-02 19:35:54 +00003112 def test_bogus_dst(self):
3113 class ok(tzinfo):
3114 def utcoffset(self, dt): return HOUR
3115 def dst(self, dt): return HOUR
3116
3117 now = self.theclass.now().replace(tzinfo=utc_real)
3118 # Doesn't blow up.
3119 now.astimezone(ok())
3120
3121 # Does blow up.
3122 class notok(ok):
3123 def dst(self, dt): return None
3124 self.assertRaises(ValueError, now.astimezone, notok())
3125
Tim Peters52dcce22003-01-23 16:36:11 +00003126 def test_fromutc(self):
3127 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3128 now = datetime.utcnow().replace(tzinfo=utc_real)
3129 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3130 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3131 enow = Eastern.fromutc(now) # doesn't blow up
3132 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3133 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3134 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3135
3136 # Always converts UTC to standard time.
3137 class FauxUSTimeZone(USTimeZone):
3138 def fromutc(self, dt):
3139 return dt + self.stdoffset
3140 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3141
3142 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3143 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3144 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3145
3146 # Check around DST start.
3147 start = self.dston.replace(hour=4, tzinfo=Eastern)
3148 fstart = start.replace(tzinfo=FEastern)
3149 for wall in 23, 0, 1, 3, 4, 5:
3150 expected = start.replace(hour=wall)
3151 if wall == 23:
3152 expected -= timedelta(days=1)
3153 got = Eastern.fromutc(start)
3154 self.assertEqual(expected, got)
3155
3156 expected = fstart + FEastern.stdoffset
3157 got = FEastern.fromutc(fstart)
3158 self.assertEqual(expected, got)
3159
3160 # Ensure astimezone() calls fromutc() too.
3161 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3162 self.assertEqual(expected, got)
3163
3164 start += HOUR
3165 fstart += HOUR
3166
3167 # Check around DST end.
3168 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3169 fstart = start.replace(tzinfo=FEastern)
3170 for wall in 0, 1, 1, 2, 3, 4:
3171 expected = start.replace(hour=wall)
3172 got = Eastern.fromutc(start)
3173 self.assertEqual(expected, got)
3174
3175 expected = fstart + FEastern.stdoffset
3176 got = FEastern.fromutc(fstart)
3177 self.assertEqual(expected, got)
3178
3179 # Ensure astimezone() calls fromutc() too.
3180 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3181 self.assertEqual(expected, got)
3182
3183 start += HOUR
3184 fstart += HOUR
3185
Tim Peters710fb152003-01-02 19:35:54 +00003186
Tim Peters528ca532004-09-16 01:30:50 +00003187#############################################################################
3188# oddballs
3189
3190class Oddballs(unittest.TestCase):
3191
3192 def test_bug_1028306(self):
3193 # Trying to compare a date to a datetime should act like a mixed-
3194 # type comparison, despite that datetime is a subclass of date.
3195 as_date = date.today()
3196 as_datetime = datetime.combine(as_date, time())
3197 self.assert_(as_date != as_datetime)
3198 self.assert_(as_datetime != as_date)
3199 self.assert_(not as_date == as_datetime)
3200 self.assert_(not as_datetime == as_date)
3201 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3202 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3203 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3204 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3205 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3206 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3207 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3208 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3209
3210 # Neverthelss, comparison should work with the base-class (date)
3211 # projection if use of a date method is forced.
3212 self.assert_(as_date.__eq__(as_datetime))
3213 different_day = (as_date.day + 1) % 20 + 1
3214 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3215 different_day)))
3216
3217 # And date should compare with other subclasses of date. If a
3218 # subclass wants to stop this, it's up to the subclass to do so.
3219 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3220 self.assertEqual(as_date, date_sc)
3221 self.assertEqual(date_sc, as_date)
3222
3223 # Ditto for datetimes.
3224 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3225 as_date.day, 0, 0, 0)
3226 self.assertEqual(as_datetime, datetime_sc)
3227 self.assertEqual(datetime_sc, as_datetime)
3228
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003229def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003230 allsuites = [unittest.makeSuite(klass, 'test')
3231 for klass in (TestModule,
3232 TestTZInfo,
3233 TestTimeDelta,
3234 TestDateOnly,
3235 TestDate,
3236 TestDateTime,
3237 TestTime,
3238 TestTimeTZ,
3239 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003240 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003241 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003242 )
3243 ]
3244 return unittest.TestSuite(allsuites)
3245
3246def test_main():
3247 import gc
3248 import sys
3249
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003250 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003251 lastrc = None
3252 while True:
3253 test_support.run_suite(thesuite)
3254 if 1: # change to 0, under a debug build, for some leak detection
3255 break
3256 gc.collect()
3257 if gc.garbage:
3258 raise SystemError("gc.garbage not empty after test run: %r" %
3259 gc.garbage)
3260 if hasattr(sys, 'gettotalrefcount'):
3261 thisrc = sys.gettotalrefcount()
3262 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3263 if lastrc:
3264 print >> sys.stderr, 'delta:', thisrc - lastrc
3265 else:
3266 print >> sys.stderr
3267 lastrc = thisrc
3268
3269if __name__ == "__main__":
3270 test_main()