blob: 056b74ffc6ee2bd32f1fc059b919d4c5e39946a5 [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
Guido van Rossumf1200f82007-03-07 15:16:29 +00006import os
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
Collin Winterc2898c52007-04-25 17:29:52 +0000130class HarmlessMixedComparison:
Tim Peters07534a62003-02-07 22:50:28 +0000131 # 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
Collin Winterc2898c52007-04-25 17:29:52 +0000169class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
Tim Peters07534a62003-02-07 22:50:28 +0000170
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
Benjamin Petersonbe2c0a92008-10-04 21:33:08 +0000256 # Division of int by timedelta doesn't make sense.
Tim Peters2a799bf2002-12-16 20:18:38 +0000257 # 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
Collin Winterc2898c52007-04-25 17:29:52 +0000516class TestDate(HarmlessMixedComparison, unittest.TestCase):
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
Alexander Belopolskycfd6bc02010-05-27 21:53:16 +0000712 for delta in [tiny, timedelta(1), timedelta(2)]:
713 dt = self.theclass.min + delta
714 dt -= delta # no problem
715 self.assertRaises(OverflowError, dt.__sub__, delta)
716 self.assertRaises(OverflowError, dt.__add__, -delta)
Tim Peters2a799bf2002-12-16 20:18:38 +0000717
Alexander Belopolskycfd6bc02010-05-27 21:53:16 +0000718 dt = self.theclass.max - delta
719 dt += delta # no problem
720 self.assertRaises(OverflowError, dt.__add__, delta)
721 self.assertRaises(OverflowError, dt.__sub__, -delta)
Tim Peters2a799bf2002-12-16 20:18:38 +0000722
723 def test_fromtimestamp(self):
724 import time
725
726 # Try an arbitrary fixed value.
727 year, month, day = 1999, 9, 19
728 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
729 d = self.theclass.fromtimestamp(ts)
730 self.assertEqual(d.year, year)
731 self.assertEqual(d.month, month)
732 self.assertEqual(d.day, day)
733
Tim Peters1b6f7a92004-06-20 02:50:16 +0000734 def test_insane_fromtimestamp(self):
735 # It's possible that some platform maps time_t to double,
736 # and that this test will fail there. This test should
737 # exempt such platforms (provided they return reasonable
738 # results!).
739 for insane in -1e200, 1e200:
740 self.assertRaises(ValueError, self.theclass.fromtimestamp,
741 insane)
742
Tim Peters2a799bf2002-12-16 20:18:38 +0000743 def test_today(self):
744 import time
745
746 # We claim that today() is like fromtimestamp(time.time()), so
747 # prove it.
748 for dummy in range(3):
749 today = self.theclass.today()
750 ts = time.time()
751 todayagain = self.theclass.fromtimestamp(ts)
752 if today == todayagain:
753 break
754 # There are several legit reasons that could fail:
755 # 1. It recently became midnight, between the today() and the
756 # time() calls.
757 # 2. The platform time() has such fine resolution that we'll
758 # never get the same value twice.
759 # 3. The platform time() has poor resolution, and we just
760 # happened to call today() right before a resolution quantum
761 # boundary.
762 # 4. The system clock got fiddled between calls.
763 # In any case, wait a little while and try again.
764 time.sleep(0.1)
765
766 # It worked or it didn't. If it didn't, assume it's reason #2, and
767 # let the test pass if they're within half a second of each other.
768 self.failUnless(today == todayagain or
769 abs(todayagain - today) < timedelta(seconds=0.5))
770
771 def test_weekday(self):
772 for i in range(7):
773 # March 4, 2002 is a Monday
774 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
775 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
776 # January 2, 1956 is a Monday
777 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
778 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
779
780 def test_isocalendar(self):
781 # Check examples from
782 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
783 for i in range(7):
784 d = self.theclass(2003, 12, 22+i)
785 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
786 d = self.theclass(2003, 12, 29) + timedelta(i)
787 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
788 d = self.theclass(2004, 1, 5+i)
789 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
790 d = self.theclass(2009, 12, 21+i)
791 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
792 d = self.theclass(2009, 12, 28) + timedelta(i)
793 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
794 d = self.theclass(2010, 1, 4+i)
795 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
796
797 def test_iso_long_years(self):
798 # Calculate long ISO years and compare to table from
799 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
800 ISO_LONG_YEARS_TABLE = """
801 4 32 60 88
802 9 37 65 93
803 15 43 71 99
804 20 48 76
805 26 54 82
806
807 105 133 161 189
808 111 139 167 195
809 116 144 172
810 122 150 178
811 128 156 184
812
813 201 229 257 285
814 207 235 263 291
815 212 240 268 296
816 218 246 274
817 224 252 280
818
819 303 331 359 387
820 308 336 364 392
821 314 342 370 398
822 320 348 376
823 325 353 381
824 """
825 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
826 iso_long_years.sort()
827 L = []
828 for i in range(400):
829 d = self.theclass(2000+i, 12, 31)
830 d1 = self.theclass(1600+i, 12, 31)
831 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
832 if d.isocalendar()[1] == 53:
833 L.append(i)
834 self.assertEqual(L, iso_long_years)
835
836 def test_isoformat(self):
837 t = self.theclass(2, 3, 2)
838 self.assertEqual(t.isoformat(), "0002-03-02")
839
840 def test_ctime(self):
841 t = self.theclass(2002, 3, 2)
842 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
843
844 def test_strftime(self):
845 t = self.theclass(2005, 3, 2)
846 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000847 self.assertEqual(t.strftime(""), "") # SF bug #761337
Georg Brandl4ddfcd32006-09-30 11:17:34 +0000848 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
Tim Peters2a799bf2002-12-16 20:18:38 +0000849
850 self.assertRaises(TypeError, t.strftime) # needs an arg
851 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
852 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
853
Gregory P. Smith137d8242008-06-02 04:05:52 +0000854 # test that unicode input is allowed (issue 2782)
855 self.assertEqual(t.strftime(u"%m"), "03")
856
Tim Peters2a799bf2002-12-16 20:18:38 +0000857 # A naive object replaces %z and %Z w/ empty strings.
858 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
859
Georg Brandl686d53e2009-01-14 00:08:09 +0000860 #make sure that invalid format specifiers are handled correctly
Benjamin Petersonf5def212009-01-19 15:06:00 +0000861 #self.assertRaises(ValueError, t.strftime, "%e")
862 #self.assertRaises(ValueError, t.strftime, "%")
863 #self.assertRaises(ValueError, t.strftime, "%#")
864
865 #oh well, some systems just ignore those invalid ones.
866 #at least, excercise them to make sure that no crashes
867 #are generated
868 for f in ["%e", "%", "%#"]:
869 try:
870 t.strftime(f)
871 except ValueError:
872 pass
Georg Brandl686d53e2009-01-14 00:08:09 +0000873
874 #check that this standard extension works
875 t.strftime("%f")
876
Gregory P. Smith137d8242008-06-02 04:05:52 +0000877
Eric Smitha9f7d622008-02-17 19:46:49 +0000878 def test_format(self):
879 dt = self.theclass(2007, 9, 10)
880 self.assertEqual(dt.__format__(''), str(dt))
881
882 # check that a derived class's __str__() gets called
883 class A(self.theclass):
884 def __str__(self):
885 return 'A'
886 a = A(2007, 9, 10)
887 self.assertEqual(a.__format__(''), 'A')
888
889 # check that a derived class's strftime gets called
890 class B(self.theclass):
891 def strftime(self, format_spec):
892 return 'B'
893 b = B(2007, 9, 10)
894 self.assertEqual(b.__format__(''), str(dt))
895
896 for fmt in ["m:%m d:%d y:%y",
897 "m:%m d:%d y:%y H:%H M:%M S:%S",
898 "%z %Z",
899 ]:
900 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
901 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
902 self.assertEqual(b.__format__(fmt), 'B')
903
Tim Peters2a799bf2002-12-16 20:18:38 +0000904 def test_resolution_info(self):
905 self.assert_(isinstance(self.theclass.min, self.theclass))
906 self.assert_(isinstance(self.theclass.max, self.theclass))
907 self.assert_(isinstance(self.theclass.resolution, timedelta))
908 self.assert_(self.theclass.max > self.theclass.min)
909
910 def test_extreme_timedelta(self):
911 big = self.theclass.max - self.theclass.min
912 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
913 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
914 # n == 315537897599999999 ~= 2**58.13
915 justasbig = timedelta(0, 0, n)
916 self.assertEqual(big, justasbig)
917 self.assertEqual(self.theclass.min + big, self.theclass.max)
918 self.assertEqual(self.theclass.max - big, self.theclass.min)
919
920 def test_timetuple(self):
921 for i in range(7):
922 # January 2, 1956 is a Monday (0)
923 d = self.theclass(1956, 1, 2+i)
924 t = d.timetuple()
925 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
926 # February 1, 1956 is a Wednesday (2)
927 d = self.theclass(1956, 2, 1+i)
928 t = d.timetuple()
929 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
930 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
931 # of the year.
932 d = self.theclass(1956, 3, 1+i)
933 t = d.timetuple()
934 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
935 self.assertEqual(t.tm_year, 1956)
936 self.assertEqual(t.tm_mon, 3)
937 self.assertEqual(t.tm_mday, 1+i)
938 self.assertEqual(t.tm_hour, 0)
939 self.assertEqual(t.tm_min, 0)
940 self.assertEqual(t.tm_sec, 0)
941 self.assertEqual(t.tm_wday, (3+i)%7)
942 self.assertEqual(t.tm_yday, 61+i)
943 self.assertEqual(t.tm_isdst, -1)
944
945 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000946 args = 6, 7, 23
947 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000948 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000949 green = pickler.dumps(orig, proto)
950 derived = unpickler.loads(green)
951 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000952
953 def test_compare(self):
954 t1 = self.theclass(2, 3, 4)
955 t2 = self.theclass(2, 3, 4)
956 self.failUnless(t1 == t2)
957 self.failUnless(t1 <= t2)
958 self.failUnless(t1 >= t2)
959 self.failUnless(not t1 != t2)
960 self.failUnless(not t1 < t2)
961 self.failUnless(not t1 > t2)
962 self.assertEqual(cmp(t1, t2), 0)
963 self.assertEqual(cmp(t2, t1), 0)
964
965 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
966 t2 = self.theclass(*args) # this is larger than t1
967 self.failUnless(t1 < t2)
968 self.failUnless(t2 > t1)
969 self.failUnless(t1 <= t2)
970 self.failUnless(t2 >= t1)
971 self.failUnless(t1 != t2)
972 self.failUnless(t2 != t1)
973 self.failUnless(not t1 == t2)
974 self.failUnless(not t2 == t1)
975 self.failUnless(not t1 > t2)
976 self.failUnless(not t2 < t1)
977 self.failUnless(not t1 >= t2)
978 self.failUnless(not t2 <= t1)
979 self.assertEqual(cmp(t1, t2), -1)
980 self.assertEqual(cmp(t2, t1), 1)
981
Tim Peters68124bb2003-02-08 03:46:31 +0000982 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000983 self.assertEqual(t1 == badarg, False)
984 self.assertEqual(t1 != badarg, True)
985 self.assertEqual(badarg == t1, False)
986 self.assertEqual(badarg != t1, True)
987
Tim Peters2a799bf2002-12-16 20:18:38 +0000988 self.assertRaises(TypeError, lambda: t1 < badarg)
989 self.assertRaises(TypeError, lambda: t1 > badarg)
990 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000991 self.assertRaises(TypeError, lambda: badarg <= t1)
992 self.assertRaises(TypeError, lambda: badarg < t1)
993 self.assertRaises(TypeError, lambda: badarg > t1)
994 self.assertRaises(TypeError, lambda: badarg >= t1)
995
Tim Peters8d81a012003-01-24 22:36:34 +0000996 def test_mixed_compare(self):
997 our = self.theclass(2000, 4, 5)
998 self.assertRaises(TypeError, cmp, our, 1)
999 self.assertRaises(TypeError, cmp, 1, our)
1000
1001 class AnotherDateTimeClass(object):
1002 def __cmp__(self, other):
1003 # Return "equal" so calling this can't be confused with
1004 # compare-by-address (which never says "equal" for distinct
1005 # objects).
1006 return 0
Nick Coghlan48361f52008-08-11 15:45:58 +00001007 __hash__ = None # Silence Py3k warning
Tim Peters8d81a012003-01-24 22:36:34 +00001008
1009 # This still errors, because date and datetime comparison raise
1010 # TypeError instead of NotImplemented when they don't know what to
1011 # do, in order to stop comparison from falling back to the default
1012 # compare-by-address.
1013 their = AnotherDateTimeClass()
1014 self.assertRaises(TypeError, cmp, our, their)
1015 # Oops: The next stab raises TypeError in the C implementation,
1016 # but not in the Python implementation of datetime. The difference
1017 # is due to that the Python implementation defines __cmp__ but
1018 # the C implementation defines tp_richcompare. This is more pain
1019 # to fix than it's worth, so commenting out the test.
1020 # self.assertEqual(cmp(their, our), 0)
1021
1022 # But date and datetime comparison return NotImplemented instead if the
1023 # other object has a timetuple attr. This gives the other object a
1024 # chance to do the comparison.
1025 class Comparable(AnotherDateTimeClass):
1026 def timetuple(self):
1027 return ()
1028
1029 their = Comparable()
1030 self.assertEqual(cmp(our, their), 0)
1031 self.assertEqual(cmp(their, our), 0)
1032 self.failUnless(our == their)
1033 self.failUnless(their == our)
1034
Tim Peters2a799bf2002-12-16 20:18:38 +00001035 def test_bool(self):
1036 # All dates are considered true.
1037 self.failUnless(self.theclass.min)
1038 self.failUnless(self.theclass.max)
1039
Guido van Rossum966bb8c2007-08-24 14:53:14 +00001040 def test_strftime_out_of_range(self):
Tim Petersd6844152002-12-22 20:58:42 +00001041 # For nasty technical reasons, we can't handle years before 1900.
1042 cls = self.theclass
1043 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1044 for y in 1, 49, 51, 99, 100, 1000, 1899:
1045 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +00001046
1047 def test_replace(self):
1048 cls = self.theclass
1049 args = [1, 2, 3]
1050 base = cls(*args)
1051 self.assertEqual(base, base.replace())
1052
1053 i = 0
1054 for name, newval in (("year", 2),
1055 ("month", 3),
1056 ("day", 4)):
1057 newargs = args[:]
1058 newargs[i] = newval
1059 expected = cls(*newargs)
1060 got = base.replace(**{name: newval})
1061 self.assertEqual(expected, got)
1062 i += 1
1063
1064 # Out of bounds.
1065 base = cls(2000, 2, 29)
1066 self.assertRaises(ValueError, base.replace, year=2001)
1067
Tim Petersa98924a2003-05-17 05:55:19 +00001068 def test_subclass_date(self):
1069
1070 class C(self.theclass):
1071 theAnswer = 42
1072
1073 def __new__(cls, *args, **kws):
1074 temp = kws.copy()
1075 extra = temp.pop('extra')
1076 result = self.theclass.__new__(cls, *args, **temp)
1077 result.extra = extra
1078 return result
1079
1080 def newmeth(self, start):
1081 return start + self.year + self.month
1082
1083 args = 2003, 4, 14
1084
1085 dt1 = self.theclass(*args)
1086 dt2 = C(*args, **{'extra': 7})
1087
1088 self.assertEqual(dt2.__class__, C)
1089 self.assertEqual(dt2.theAnswer, 42)
1090 self.assertEqual(dt2.extra, 7)
1091 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1092 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1093
Tim Peters604c0132004-06-07 23:04:33 +00001094 def test_pickling_subclass_date(self):
1095
1096 args = 6, 7, 23
1097 orig = SubclassDate(*args)
1098 for pickler, unpickler, proto in pickle_choices:
1099 green = pickler.dumps(orig, proto)
1100 derived = unpickler.loads(green)
1101 self.assertEqual(orig, derived)
1102
Tim Peters3f606292004-03-21 23:38:41 +00001103 def test_backdoor_resistance(self):
1104 # For fast unpickling, the constructor accepts a pickle string.
1105 # This is a low-overhead backdoor. A user can (by intent or
1106 # mistake) pass a string directly, which (if it's the right length)
1107 # will get treated like a pickle, and bypass the normal sanity
1108 # checks in the constructor. This can create insane objects.
1109 # The constructor doesn't want to burn the time to validate all
1110 # fields, but does check the month field. This stops, e.g.,
1111 # datetime.datetime('1995-03-25') from yielding an insane object.
1112 base = '1995-03-25'
1113 if not issubclass(self.theclass, datetime):
1114 base = base[:4]
1115 for month_byte in '9', chr(0), chr(13), '\xff':
1116 self.assertRaises(TypeError, self.theclass,
1117 base[:2] + month_byte + base[3:])
1118 for ord_byte in range(1, 13):
1119 # This shouldn't blow up because of the month byte alone. If
1120 # the implementation changes to do more-careful checking, it may
1121 # blow up because other fields are insane.
1122 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001123
Tim Peters2a799bf2002-12-16 20:18:38 +00001124#############################################################################
1125# datetime tests
1126
Tim Peters604c0132004-06-07 23:04:33 +00001127class SubclassDatetime(datetime):
1128 sub_var = 1
1129
Tim Peters2a799bf2002-12-16 20:18:38 +00001130class TestDateTime(TestDate):
1131
1132 theclass = datetime
1133
1134 def test_basic_attributes(self):
1135 dt = self.theclass(2002, 3, 1, 12, 0)
1136 self.assertEqual(dt.year, 2002)
1137 self.assertEqual(dt.month, 3)
1138 self.assertEqual(dt.day, 1)
1139 self.assertEqual(dt.hour, 12)
1140 self.assertEqual(dt.minute, 0)
1141 self.assertEqual(dt.second, 0)
1142 self.assertEqual(dt.microsecond, 0)
1143
1144 def test_basic_attributes_nonzero(self):
1145 # Make sure all attributes are non-zero so bugs in
1146 # bit-shifting access show up.
1147 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1148 self.assertEqual(dt.year, 2002)
1149 self.assertEqual(dt.month, 3)
1150 self.assertEqual(dt.day, 1)
1151 self.assertEqual(dt.hour, 12)
1152 self.assertEqual(dt.minute, 59)
1153 self.assertEqual(dt.second, 59)
1154 self.assertEqual(dt.microsecond, 8000)
1155
1156 def test_roundtrip(self):
1157 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1158 self.theclass.now()):
1159 # Verify dt -> string -> datetime identity.
1160 s = repr(dt)
1161 self.failUnless(s.startswith('datetime.'))
1162 s = s[9:]
1163 dt2 = eval(s)
1164 self.assertEqual(dt, dt2)
1165
1166 # Verify identity via reconstructing from pieces.
1167 dt2 = self.theclass(dt.year, dt.month, dt.day,
1168 dt.hour, dt.minute, dt.second,
1169 dt.microsecond)
1170 self.assertEqual(dt, dt2)
1171
1172 def test_isoformat(self):
1173 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1174 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1175 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1176 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
Amaury Forgeot d'Arc7682d042009-12-29 22:39:49 +00001177 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
Tim Peters2a799bf2002-12-16 20:18:38 +00001178 # str is ISO format with the separator forced to a blank.
1179 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1180
1181 t = self.theclass(2, 3, 2)
1182 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1183 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1184 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1185 # str is ISO format with the separator forced to a blank.
1186 self.assertEqual(str(t), "0002-03-02 00:00:00")
1187
Eric Smitha9f7d622008-02-17 19:46:49 +00001188 def test_format(self):
1189 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1190 self.assertEqual(dt.__format__(''), str(dt))
1191
1192 # check that a derived class's __str__() gets called
1193 class A(self.theclass):
1194 def __str__(self):
1195 return 'A'
1196 a = A(2007, 9, 10, 4, 5, 1, 123)
1197 self.assertEqual(a.__format__(''), 'A')
1198
1199 # check that a derived class's strftime gets called
1200 class B(self.theclass):
1201 def strftime(self, format_spec):
1202 return 'B'
1203 b = B(2007, 9, 10, 4, 5, 1, 123)
1204 self.assertEqual(b.__format__(''), str(dt))
1205
1206 for fmt in ["m:%m d:%d y:%y",
1207 "m:%m d:%d y:%y H:%H M:%M S:%S",
1208 "%z %Z",
1209 ]:
1210 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1211 self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1212 self.assertEqual(b.__format__(fmt), 'B')
1213
Tim Peters2a799bf2002-12-16 20:18:38 +00001214 def test_more_ctime(self):
1215 # Test fields that TestDate doesn't touch.
1216 import time
1217
1218 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1219 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1220 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1221 # out. The difference is that t.ctime() produces " 2" for the day,
1222 # but platform ctime() produces "02" for the day. According to
1223 # C99, t.ctime() is correct here.
1224 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1225
1226 # So test a case where that difference doesn't matter.
1227 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1228 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1229
1230 def test_tz_independent_comparing(self):
1231 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1232 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1233 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1234 self.assertEqual(dt1, dt3)
1235 self.assert_(dt2 > dt3)
1236
1237 # Make sure comparison doesn't forget microseconds, and isn't done
1238 # via comparing a float timestamp (an IEEE double doesn't have enough
1239 # precision to span microsecond resolution across years 1 thru 9999,
1240 # so comparing via timestamp necessarily calls some distinct values
1241 # equal).
1242 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1243 us = timedelta(microseconds=1)
1244 dt2 = dt1 + us
1245 self.assertEqual(dt2 - dt1, us)
1246 self.assert_(dt1 < dt2)
1247
Neal Norwitzd5b0c9b2006-03-20 01:58:39 +00001248 def test_strftime_with_bad_tzname_replace(self):
1249 # verify ok if tzinfo.tzname().replace() returns a non-string
1250 class MyTzInfo(FixedOffset):
1251 def tzname(self, dt):
1252 class MyStr(str):
1253 def replace(self, *args):
1254 return None
1255 return MyStr('name')
1256 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1257 self.assertRaises(TypeError, t.strftime, '%Z')
1258
Tim Peters2a799bf2002-12-16 20:18:38 +00001259 def test_bad_constructor_arguments(self):
1260 # bad years
1261 self.theclass(MINYEAR, 1, 1) # no exception
1262 self.theclass(MAXYEAR, 1, 1) # no exception
1263 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1264 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1265 # bad months
1266 self.theclass(2000, 1, 1) # no exception
1267 self.theclass(2000, 12, 1) # no exception
1268 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1269 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1270 # bad days
1271 self.theclass(2000, 2, 29) # no exception
1272 self.theclass(2004, 2, 29) # no exception
1273 self.theclass(2400, 2, 29) # no exception
1274 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1275 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1276 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1277 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1278 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1279 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1280 # bad hours
1281 self.theclass(2000, 1, 31, 0) # no exception
1282 self.theclass(2000, 1, 31, 23) # no exception
1283 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1284 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1285 # bad minutes
1286 self.theclass(2000, 1, 31, 23, 0) # no exception
1287 self.theclass(2000, 1, 31, 23, 59) # no exception
1288 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1289 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1290 # bad seconds
1291 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1292 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1293 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1294 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1295 # bad microseconds
1296 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1297 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1298 self.assertRaises(ValueError, self.theclass,
1299 2000, 1, 31, 23, 59, 59, -1)
1300 self.assertRaises(ValueError, self.theclass,
1301 2000, 1, 31, 23, 59, 59,
1302 1000000)
1303
1304 def test_hash_equality(self):
1305 d = self.theclass(2000, 12, 31, 23, 30, 17)
1306 e = self.theclass(2000, 12, 31, 23, 30, 17)
1307 self.assertEqual(d, e)
1308 self.assertEqual(hash(d), hash(e))
1309
1310 dic = {d: 1}
1311 dic[e] = 2
1312 self.assertEqual(len(dic), 1)
1313 self.assertEqual(dic[d], 2)
1314 self.assertEqual(dic[e], 2)
1315
1316 d = self.theclass(2001, 1, 1, 0, 5, 17)
1317 e = self.theclass(2001, 1, 1, 0, 5, 17)
1318 self.assertEqual(d, e)
1319 self.assertEqual(hash(d), hash(e))
1320
1321 dic = {d: 1}
1322 dic[e] = 2
1323 self.assertEqual(len(dic), 1)
1324 self.assertEqual(dic[d], 2)
1325 self.assertEqual(dic[e], 2)
1326
1327 def test_computations(self):
1328 a = self.theclass(2002, 1, 31)
1329 b = self.theclass(1956, 1, 31)
1330 diff = a-b
1331 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1332 self.assertEqual(diff.seconds, 0)
1333 self.assertEqual(diff.microseconds, 0)
1334 a = self.theclass(2002, 3, 2, 17, 6)
1335 millisec = timedelta(0, 0, 1000)
1336 hour = timedelta(0, 3600)
1337 day = timedelta(1)
1338 week = timedelta(7)
1339 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1340 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1341 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1342 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1343 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1344 self.assertEqual(a - hour, a + -hour)
1345 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1346 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1347 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1348 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1349 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1350 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1351 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1352 self.assertEqual((a + week) - a, week)
1353 self.assertEqual((a + day) - a, day)
1354 self.assertEqual((a + hour) - a, hour)
1355 self.assertEqual((a + millisec) - a, millisec)
1356 self.assertEqual((a - week) - a, -week)
1357 self.assertEqual((a - day) - a, -day)
1358 self.assertEqual((a - hour) - a, -hour)
1359 self.assertEqual((a - millisec) - a, -millisec)
1360 self.assertEqual(a - (a + week), -week)
1361 self.assertEqual(a - (a + day), -day)
1362 self.assertEqual(a - (a + hour), -hour)
1363 self.assertEqual(a - (a + millisec), -millisec)
1364 self.assertEqual(a - (a - week), week)
1365 self.assertEqual(a - (a - day), day)
1366 self.assertEqual(a - (a - hour), hour)
1367 self.assertEqual(a - (a - millisec), millisec)
1368 self.assertEqual(a + (week + day + hour + millisec),
1369 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1370 self.assertEqual(a + (week + day + hour + millisec),
1371 (((a + week) + day) + hour) + millisec)
1372 self.assertEqual(a - (week + day + hour + millisec),
1373 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1374 self.assertEqual(a - (week + day + hour + millisec),
1375 (((a - week) - day) - hour) - millisec)
1376 # Add/sub ints, longs, floats should be illegal
1377 for i in 1, 1L, 1.0:
1378 self.assertRaises(TypeError, lambda: a+i)
1379 self.assertRaises(TypeError, lambda: a-i)
1380 self.assertRaises(TypeError, lambda: i+a)
1381 self.assertRaises(TypeError, lambda: i-a)
1382
1383 # delta - datetime is senseless.
1384 self.assertRaises(TypeError, lambda: day - a)
1385 # mixing datetime and (delta or datetime) via * or // is senseless
1386 self.assertRaises(TypeError, lambda: day * a)
1387 self.assertRaises(TypeError, lambda: a * day)
1388 self.assertRaises(TypeError, lambda: day // a)
1389 self.assertRaises(TypeError, lambda: a // day)
1390 self.assertRaises(TypeError, lambda: a * a)
1391 self.assertRaises(TypeError, lambda: a // a)
1392 # datetime + datetime is senseless
1393 self.assertRaises(TypeError, lambda: a + a)
1394
1395 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001396 args = 6, 7, 23, 20, 59, 1, 64**2
1397 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001398 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001399 green = pickler.dumps(orig, proto)
1400 derived = unpickler.loads(green)
1401 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001402
Guido van Rossum275666f2003-02-07 21:49:01 +00001403 def test_more_pickling(self):
1404 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1405 s = pickle.dumps(a)
1406 b = pickle.loads(s)
1407 self.assertEqual(b.year, 2003)
1408 self.assertEqual(b.month, 2)
1409 self.assertEqual(b.day, 7)
1410
Tim Peters604c0132004-06-07 23:04:33 +00001411 def test_pickling_subclass_datetime(self):
1412 args = 6, 7, 23, 20, 59, 1, 64**2
1413 orig = SubclassDatetime(*args)
1414 for pickler, unpickler, proto in pickle_choices:
1415 green = pickler.dumps(orig, proto)
1416 derived = unpickler.loads(green)
1417 self.assertEqual(orig, derived)
1418
Tim Peters2a799bf2002-12-16 20:18:38 +00001419 def test_more_compare(self):
1420 # The test_compare() inherited from TestDate covers the error cases.
1421 # We just want to test lexicographic ordering on the members datetime
1422 # has that date lacks.
1423 args = [2000, 11, 29, 20, 58, 16, 999998]
1424 t1 = self.theclass(*args)
1425 t2 = self.theclass(*args)
1426 self.failUnless(t1 == t2)
1427 self.failUnless(t1 <= t2)
1428 self.failUnless(t1 >= t2)
1429 self.failUnless(not t1 != t2)
1430 self.failUnless(not t1 < t2)
1431 self.failUnless(not t1 > t2)
1432 self.assertEqual(cmp(t1, t2), 0)
1433 self.assertEqual(cmp(t2, t1), 0)
1434
1435 for i in range(len(args)):
1436 newargs = args[:]
1437 newargs[i] = args[i] + 1
1438 t2 = self.theclass(*newargs) # this is larger than t1
1439 self.failUnless(t1 < t2)
1440 self.failUnless(t2 > t1)
1441 self.failUnless(t1 <= t2)
1442 self.failUnless(t2 >= t1)
1443 self.failUnless(t1 != t2)
1444 self.failUnless(t2 != t1)
1445 self.failUnless(not t1 == t2)
1446 self.failUnless(not t2 == t1)
1447 self.failUnless(not t1 > t2)
1448 self.failUnless(not t2 < t1)
1449 self.failUnless(not t1 >= t2)
1450 self.failUnless(not t2 <= t1)
1451 self.assertEqual(cmp(t1, t2), -1)
1452 self.assertEqual(cmp(t2, t1), 1)
1453
1454
1455 # A helper for timestamp constructor tests.
1456 def verify_field_equality(self, expected, got):
1457 self.assertEqual(expected.tm_year, got.year)
1458 self.assertEqual(expected.tm_mon, got.month)
1459 self.assertEqual(expected.tm_mday, got.day)
1460 self.assertEqual(expected.tm_hour, got.hour)
1461 self.assertEqual(expected.tm_min, got.minute)
1462 self.assertEqual(expected.tm_sec, got.second)
1463
1464 def test_fromtimestamp(self):
1465 import time
1466
1467 ts = time.time()
1468 expected = time.localtime(ts)
1469 got = self.theclass.fromtimestamp(ts)
1470 self.verify_field_equality(expected, got)
1471
1472 def test_utcfromtimestamp(self):
1473 import time
1474
1475 ts = time.time()
1476 expected = time.gmtime(ts)
1477 got = self.theclass.utcfromtimestamp(ts)
1478 self.verify_field_equality(expected, got)
1479
Georg Brandl6d78a582006-04-28 19:09:24 +00001480 def test_microsecond_rounding(self):
1481 # Test whether fromtimestamp "rounds up" floats that are less
1482 # than one microsecond smaller than an integer.
1483 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1484 self.theclass.fromtimestamp(1))
1485
Tim Peters1b6f7a92004-06-20 02:50:16 +00001486 def test_insane_fromtimestamp(self):
1487 # It's possible that some platform maps time_t to double,
1488 # and that this test will fail there. This test should
1489 # exempt such platforms (provided they return reasonable
1490 # results!).
1491 for insane in -1e200, 1e200:
1492 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1493 insane)
1494
1495 def test_insane_utcfromtimestamp(self):
1496 # It's possible that some platform maps time_t to double,
1497 # and that this test will fail there. This test should
1498 # exempt such platforms (provided they return reasonable
1499 # results!).
1500 for insane in -1e200, 1e200:
1501 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1502 insane)
1503
Guido van Rossum2054ee92007-03-06 15:50:01 +00001504 def test_negative_float_fromtimestamp(self):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001505 # Windows doesn't accept negative timestamps
1506 if os.name == "nt":
1507 return
Guido van Rossum2054ee92007-03-06 15:50:01 +00001508 # The result is tz-dependent; at least test that this doesn't
1509 # fail (like it did before bug 1646728 was fixed).
1510 self.theclass.fromtimestamp(-1.05)
1511
1512 def test_negative_float_utcfromtimestamp(self):
Guido van Rossumf1200f82007-03-07 15:16:29 +00001513 # Windows doesn't accept negative timestamps
1514 if os.name == "nt":
1515 return
Guido van Rossum2054ee92007-03-06 15:50:01 +00001516 d = self.theclass.utcfromtimestamp(-1.05)
1517 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1518
Tim Peters2a799bf2002-12-16 20:18:38 +00001519 def test_utcnow(self):
1520 import time
1521
1522 # Call it a success if utcnow() and utcfromtimestamp() are within
1523 # a second of each other.
1524 tolerance = timedelta(seconds=1)
1525 for dummy in range(3):
1526 from_now = self.theclass.utcnow()
1527 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1528 if abs(from_timestamp - from_now) <= tolerance:
1529 break
1530 # Else try again a few times.
1531 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1532
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001533 def test_strptime(self):
Skip Montanarofc070d22008-03-15 16:04:45 +00001534 import _strptime
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001535
Skip Montanarofc070d22008-03-15 16:04:45 +00001536 string = '2004-12-01 13:02:47.197'
1537 format = '%Y-%m-%d %H:%M:%S.%f'
1538 result, frac = _strptime._strptime(string, format)
1539 expected = self.theclass(*(result[0:6]+(frac,)))
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001540 got = self.theclass.strptime(string, format)
1541 self.assertEqual(expected, got)
1542
Tim Peters2a799bf2002-12-16 20:18:38 +00001543 def test_more_timetuple(self):
1544 # This tests fields beyond those tested by the TestDate.test_timetuple.
1545 t = self.theclass(2004, 12, 31, 6, 22, 33)
1546 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1547 self.assertEqual(t.timetuple(),
1548 (t.year, t.month, t.day,
1549 t.hour, t.minute, t.second,
1550 t.weekday(),
1551 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1552 -1))
1553 tt = t.timetuple()
1554 self.assertEqual(tt.tm_year, t.year)
1555 self.assertEqual(tt.tm_mon, t.month)
1556 self.assertEqual(tt.tm_mday, t.day)
1557 self.assertEqual(tt.tm_hour, t.hour)
1558 self.assertEqual(tt.tm_min, t.minute)
1559 self.assertEqual(tt.tm_sec, t.second)
1560 self.assertEqual(tt.tm_wday, t.weekday())
1561 self.assertEqual(tt.tm_yday, t.toordinal() -
1562 date(t.year, 1, 1).toordinal() + 1)
1563 self.assertEqual(tt.tm_isdst, -1)
1564
1565 def test_more_strftime(self):
1566 # This tests fields beyond those tested by the TestDate.test_strftime.
Skip Montanarofc070d22008-03-15 16:04:45 +00001567 t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1568 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1569 "12 31 04 000047 33 22 06 366")
Tim Peters2a799bf2002-12-16 20:18:38 +00001570
1571 def test_extract(self):
1572 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1573 self.assertEqual(dt.date(), date(2002, 3, 4))
1574 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1575
1576 def test_combine(self):
1577 d = date(2002, 3, 4)
1578 t = time(18, 45, 3, 1234)
1579 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1580 combine = self.theclass.combine
1581 dt = combine(d, t)
1582 self.assertEqual(dt, expected)
1583
1584 dt = combine(time=t, date=d)
1585 self.assertEqual(dt, expected)
1586
1587 self.assertEqual(d, dt.date())
1588 self.assertEqual(t, dt.time())
1589 self.assertEqual(dt, combine(dt.date(), dt.time()))
1590
1591 self.assertRaises(TypeError, combine) # need an arg
1592 self.assertRaises(TypeError, combine, d) # need two args
1593 self.assertRaises(TypeError, combine, t, d) # args reversed
1594 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1595 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1596
Tim Peters12bf3392002-12-24 05:41:27 +00001597 def test_replace(self):
1598 cls = self.theclass
1599 args = [1, 2, 3, 4, 5, 6, 7]
1600 base = cls(*args)
1601 self.assertEqual(base, base.replace())
1602
1603 i = 0
1604 for name, newval in (("year", 2),
1605 ("month", 3),
1606 ("day", 4),
1607 ("hour", 5),
1608 ("minute", 6),
1609 ("second", 7),
1610 ("microsecond", 8)):
1611 newargs = args[:]
1612 newargs[i] = newval
1613 expected = cls(*newargs)
1614 got = base.replace(**{name: newval})
1615 self.assertEqual(expected, got)
1616 i += 1
1617
1618 # Out of bounds.
1619 base = cls(2000, 2, 29)
1620 self.assertRaises(ValueError, base.replace, year=2001)
1621
Tim Peters80475bb2002-12-25 07:40:55 +00001622 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001623 # Pretty boring! The TZ test is more interesting here. astimezone()
1624 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001625 dt = self.theclass.now()
1626 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001627 self.assertRaises(TypeError, dt.astimezone) # not enough args
1628 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1629 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001630 self.assertRaises(ValueError, dt.astimezone, f) # naive
1631 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001632
Tim Peters52dcce22003-01-23 16:36:11 +00001633 class Bogus(tzinfo):
1634 def utcoffset(self, dt): return None
1635 def dst(self, dt): return timedelta(0)
1636 bog = Bogus()
1637 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1638
1639 class AlsoBogus(tzinfo):
1640 def utcoffset(self, dt): return timedelta(0)
1641 def dst(self, dt): return None
1642 alsobog = AlsoBogus()
1643 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001644
Tim Petersa98924a2003-05-17 05:55:19 +00001645 def test_subclass_datetime(self):
1646
1647 class C(self.theclass):
1648 theAnswer = 42
1649
1650 def __new__(cls, *args, **kws):
1651 temp = kws.copy()
1652 extra = temp.pop('extra')
1653 result = self.theclass.__new__(cls, *args, **temp)
1654 result.extra = extra
1655 return result
1656
1657 def newmeth(self, start):
1658 return start + self.year + self.month + self.second
1659
1660 args = 2003, 4, 14, 12, 13, 41
1661
1662 dt1 = self.theclass(*args)
1663 dt2 = C(*args, **{'extra': 7})
1664
1665 self.assertEqual(dt2.__class__, C)
1666 self.assertEqual(dt2.theAnswer, 42)
1667 self.assertEqual(dt2.extra, 7)
1668 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1669 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1670 dt1.second - 7)
1671
Tim Peters604c0132004-06-07 23:04:33 +00001672class SubclassTime(time):
1673 sub_var = 1
1674
Collin Winterc2898c52007-04-25 17:29:52 +00001675class TestTime(HarmlessMixedComparison, unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001676
1677 theclass = time
1678
1679 def test_basic_attributes(self):
1680 t = self.theclass(12, 0)
1681 self.assertEqual(t.hour, 12)
1682 self.assertEqual(t.minute, 0)
1683 self.assertEqual(t.second, 0)
1684 self.assertEqual(t.microsecond, 0)
1685
1686 def test_basic_attributes_nonzero(self):
1687 # Make sure all attributes are non-zero so bugs in
1688 # bit-shifting access show up.
1689 t = self.theclass(12, 59, 59, 8000)
1690 self.assertEqual(t.hour, 12)
1691 self.assertEqual(t.minute, 59)
1692 self.assertEqual(t.second, 59)
1693 self.assertEqual(t.microsecond, 8000)
1694
1695 def test_roundtrip(self):
1696 t = self.theclass(1, 2, 3, 4)
1697
1698 # Verify t -> string -> time identity.
1699 s = repr(t)
1700 self.failUnless(s.startswith('datetime.'))
1701 s = s[9:]
1702 t2 = eval(s)
1703 self.assertEqual(t, t2)
1704
1705 # Verify identity via reconstructing from pieces.
1706 t2 = self.theclass(t.hour, t.minute, t.second,
1707 t.microsecond)
1708 self.assertEqual(t, t2)
1709
1710 def test_comparing(self):
1711 args = [1, 2, 3, 4]
1712 t1 = self.theclass(*args)
1713 t2 = self.theclass(*args)
1714 self.failUnless(t1 == t2)
1715 self.failUnless(t1 <= t2)
1716 self.failUnless(t1 >= t2)
1717 self.failUnless(not t1 != t2)
1718 self.failUnless(not t1 < t2)
1719 self.failUnless(not t1 > t2)
1720 self.assertEqual(cmp(t1, t2), 0)
1721 self.assertEqual(cmp(t2, t1), 0)
1722
1723 for i in range(len(args)):
1724 newargs = args[:]
1725 newargs[i] = args[i] + 1
1726 t2 = self.theclass(*newargs) # this is larger than t1
1727 self.failUnless(t1 < t2)
1728 self.failUnless(t2 > t1)
1729 self.failUnless(t1 <= t2)
1730 self.failUnless(t2 >= t1)
1731 self.failUnless(t1 != t2)
1732 self.failUnless(t2 != t1)
1733 self.failUnless(not t1 == t2)
1734 self.failUnless(not t2 == t1)
1735 self.failUnless(not t1 > t2)
1736 self.failUnless(not t2 < t1)
1737 self.failUnless(not t1 >= t2)
1738 self.failUnless(not t2 <= t1)
1739 self.assertEqual(cmp(t1, t2), -1)
1740 self.assertEqual(cmp(t2, t1), 1)
1741
Tim Peters68124bb2003-02-08 03:46:31 +00001742 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001743 self.assertEqual(t1 == badarg, False)
1744 self.assertEqual(t1 != badarg, True)
1745 self.assertEqual(badarg == t1, False)
1746 self.assertEqual(badarg != t1, True)
1747
Tim Peters2a799bf2002-12-16 20:18:38 +00001748 self.assertRaises(TypeError, lambda: t1 <= badarg)
1749 self.assertRaises(TypeError, lambda: t1 < badarg)
1750 self.assertRaises(TypeError, lambda: t1 > badarg)
1751 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001752 self.assertRaises(TypeError, lambda: badarg <= t1)
1753 self.assertRaises(TypeError, lambda: badarg < t1)
1754 self.assertRaises(TypeError, lambda: badarg > t1)
1755 self.assertRaises(TypeError, lambda: badarg >= t1)
1756
1757 def test_bad_constructor_arguments(self):
1758 # bad hours
1759 self.theclass(0, 0) # no exception
1760 self.theclass(23, 0) # no exception
1761 self.assertRaises(ValueError, self.theclass, -1, 0)
1762 self.assertRaises(ValueError, self.theclass, 24, 0)
1763 # bad minutes
1764 self.theclass(23, 0) # no exception
1765 self.theclass(23, 59) # no exception
1766 self.assertRaises(ValueError, self.theclass, 23, -1)
1767 self.assertRaises(ValueError, self.theclass, 23, 60)
1768 # bad seconds
1769 self.theclass(23, 59, 0) # no exception
1770 self.theclass(23, 59, 59) # no exception
1771 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1772 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1773 # bad microseconds
1774 self.theclass(23, 59, 59, 0) # no exception
1775 self.theclass(23, 59, 59, 999999) # no exception
1776 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1777 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1778
1779 def test_hash_equality(self):
1780 d = self.theclass(23, 30, 17)
1781 e = self.theclass(23, 30, 17)
1782 self.assertEqual(d, e)
1783 self.assertEqual(hash(d), hash(e))
1784
1785 dic = {d: 1}
1786 dic[e] = 2
1787 self.assertEqual(len(dic), 1)
1788 self.assertEqual(dic[d], 2)
1789 self.assertEqual(dic[e], 2)
1790
1791 d = self.theclass(0, 5, 17)
1792 e = self.theclass(0, 5, 17)
1793 self.assertEqual(d, e)
1794 self.assertEqual(hash(d), hash(e))
1795
1796 dic = {d: 1}
1797 dic[e] = 2
1798 self.assertEqual(len(dic), 1)
1799 self.assertEqual(dic[d], 2)
1800 self.assertEqual(dic[e], 2)
1801
1802 def test_isoformat(self):
1803 t = self.theclass(4, 5, 1, 123)
1804 self.assertEqual(t.isoformat(), "04:05:01.000123")
1805 self.assertEqual(t.isoformat(), str(t))
1806
1807 t = self.theclass()
1808 self.assertEqual(t.isoformat(), "00:00:00")
1809 self.assertEqual(t.isoformat(), str(t))
1810
1811 t = self.theclass(microsecond=1)
1812 self.assertEqual(t.isoformat(), "00:00:00.000001")
1813 self.assertEqual(t.isoformat(), str(t))
1814
1815 t = self.theclass(microsecond=10)
1816 self.assertEqual(t.isoformat(), "00:00:00.000010")
1817 self.assertEqual(t.isoformat(), str(t))
1818
1819 t = self.theclass(microsecond=100)
1820 self.assertEqual(t.isoformat(), "00:00:00.000100")
1821 self.assertEqual(t.isoformat(), str(t))
1822
1823 t = self.theclass(microsecond=1000)
1824 self.assertEqual(t.isoformat(), "00:00:00.001000")
1825 self.assertEqual(t.isoformat(), str(t))
1826
1827 t = self.theclass(microsecond=10000)
1828 self.assertEqual(t.isoformat(), "00:00:00.010000")
1829 self.assertEqual(t.isoformat(), str(t))
1830
1831 t = self.theclass(microsecond=100000)
1832 self.assertEqual(t.isoformat(), "00:00:00.100000")
1833 self.assertEqual(t.isoformat(), str(t))
1834
Martin v. Löwis4c11a922007-02-08 09:13:36 +00001835 def test_1653736(self):
1836 # verify it doesn't accept extra keyword arguments
1837 t = self.theclass(second=1)
1838 self.assertRaises(TypeError, t.isoformat, foo=3)
1839
Tim Peters2a799bf2002-12-16 20:18:38 +00001840 def test_strftime(self):
1841 t = self.theclass(1, 2, 3, 4)
Skip Montanarofc070d22008-03-15 16:04:45 +00001842 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
Tim Peters2a799bf2002-12-16 20:18:38 +00001843 # A naive object replaces %z and %Z with empty strings.
1844 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1845
Eric Smitha9f7d622008-02-17 19:46:49 +00001846 def test_format(self):
1847 t = self.theclass(1, 2, 3, 4)
1848 self.assertEqual(t.__format__(''), str(t))
1849
1850 # check that a derived class's __str__() gets called
1851 class A(self.theclass):
1852 def __str__(self):
1853 return 'A'
1854 a = A(1, 2, 3, 4)
1855 self.assertEqual(a.__format__(''), 'A')
1856
1857 # check that a derived class's strftime gets called
1858 class B(self.theclass):
1859 def strftime(self, format_spec):
1860 return 'B'
1861 b = B(1, 2, 3, 4)
1862 self.assertEqual(b.__format__(''), str(t))
1863
1864 for fmt in ['%H %M %S',
1865 ]:
1866 self.assertEqual(t.__format__(fmt), t.strftime(fmt))
1867 self.assertEqual(a.__format__(fmt), t.strftime(fmt))
1868 self.assertEqual(b.__format__(fmt), 'B')
1869
Tim Peters2a799bf2002-12-16 20:18:38 +00001870 def test_str(self):
1871 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1872 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1873 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1874 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1875 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1876
1877 def test_repr(self):
1878 name = 'datetime.' + self.theclass.__name__
1879 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1880 "%s(1, 2, 3, 4)" % name)
1881 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1882 "%s(10, 2, 3, 4000)" % name)
1883 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1884 "%s(0, 2, 3, 400000)" % name)
1885 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1886 "%s(12, 2, 3)" % name)
1887 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1888 "%s(23, 15)" % name)
1889
1890 def test_resolution_info(self):
1891 self.assert_(isinstance(self.theclass.min, self.theclass))
1892 self.assert_(isinstance(self.theclass.max, self.theclass))
1893 self.assert_(isinstance(self.theclass.resolution, timedelta))
1894 self.assert_(self.theclass.max > self.theclass.min)
1895
1896 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001897 args = 20, 59, 16, 64**2
1898 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001899 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001900 green = pickler.dumps(orig, proto)
1901 derived = unpickler.loads(green)
1902 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001903
Tim Peters604c0132004-06-07 23:04:33 +00001904 def test_pickling_subclass_time(self):
1905 args = 20, 59, 16, 64**2
1906 orig = SubclassTime(*args)
1907 for pickler, unpickler, proto in pickle_choices:
1908 green = pickler.dumps(orig, proto)
1909 derived = unpickler.loads(green)
1910 self.assertEqual(orig, derived)
1911
Tim Peters2a799bf2002-12-16 20:18:38 +00001912 def test_bool(self):
1913 cls = self.theclass
1914 self.failUnless(cls(1))
1915 self.failUnless(cls(0, 1))
1916 self.failUnless(cls(0, 0, 1))
1917 self.failUnless(cls(0, 0, 0, 1))
1918 self.failUnless(not cls(0))
1919 self.failUnless(not cls())
1920
Tim Peters12bf3392002-12-24 05:41:27 +00001921 def test_replace(self):
1922 cls = self.theclass
1923 args = [1, 2, 3, 4]
1924 base = cls(*args)
1925 self.assertEqual(base, base.replace())
1926
1927 i = 0
1928 for name, newval in (("hour", 5),
1929 ("minute", 6),
1930 ("second", 7),
1931 ("microsecond", 8)):
1932 newargs = args[:]
1933 newargs[i] = newval
1934 expected = cls(*newargs)
1935 got = base.replace(**{name: newval})
1936 self.assertEqual(expected, got)
1937 i += 1
1938
1939 # Out of bounds.
1940 base = cls(1)
1941 self.assertRaises(ValueError, base.replace, hour=24)
1942 self.assertRaises(ValueError, base.replace, minute=-1)
1943 self.assertRaises(ValueError, base.replace, second=100)
1944 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1945
Tim Petersa98924a2003-05-17 05:55:19 +00001946 def test_subclass_time(self):
1947
1948 class C(self.theclass):
1949 theAnswer = 42
1950
1951 def __new__(cls, *args, **kws):
1952 temp = kws.copy()
1953 extra = temp.pop('extra')
1954 result = self.theclass.__new__(cls, *args, **temp)
1955 result.extra = extra
1956 return result
1957
1958 def newmeth(self, start):
1959 return start + self.hour + self.second
1960
1961 args = 4, 5, 6
1962
1963 dt1 = self.theclass(*args)
1964 dt2 = C(*args, **{'extra': 7})
1965
1966 self.assertEqual(dt2.__class__, C)
1967 self.assertEqual(dt2.theAnswer, 42)
1968 self.assertEqual(dt2.extra, 7)
1969 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1970 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1971
Armin Rigof4afb212005-11-07 07:15:48 +00001972 def test_backdoor_resistance(self):
1973 # see TestDate.test_backdoor_resistance().
1974 base = '2:59.0'
1975 for hour_byte in ' ', '9', chr(24), '\xff':
1976 self.assertRaises(TypeError, self.theclass,
1977 hour_byte + base[1:])
1978
Tim Peters855fe882002-12-22 03:43:39 +00001979# A mixin for classes with a tzinfo= argument. Subclasses must define
1980# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001981# must be legit (which is true for time and datetime).
Collin Winterc2898c52007-04-25 17:29:52 +00001982class TZInfoBase:
Tim Peters2a799bf2002-12-16 20:18:38 +00001983
Tim Petersbad8ff02002-12-30 20:52:32 +00001984 def test_argument_passing(self):
1985 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001986 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001987 class introspective(tzinfo):
1988 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001989 def utcoffset(self, dt):
1990 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001991 dst = utcoffset
1992
1993 obj = cls(1, 2, 3, tzinfo=introspective())
1994
Tim Peters0bf60bd2003-01-08 20:40:01 +00001995 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001996 self.assertEqual(obj.tzname(), expected)
1997
Tim Peters0bf60bd2003-01-08 20:40:01 +00001998 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001999 self.assertEqual(obj.utcoffset(), expected)
2000 self.assertEqual(obj.dst(), expected)
2001
Tim Peters855fe882002-12-22 03:43:39 +00002002 def test_bad_tzinfo_classes(self):
2003 cls = self.theclass
2004 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00002005
Tim Peters855fe882002-12-22 03:43:39 +00002006 class NiceTry(object):
2007 def __init__(self): pass
2008 def utcoffset(self, dt): pass
2009 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2010
2011 class BetterTry(tzinfo):
2012 def __init__(self): pass
2013 def utcoffset(self, dt): pass
2014 b = BetterTry()
2015 t = cls(1, 1, 1, tzinfo=b)
2016 self.failUnless(t.tzinfo is b)
2017
2018 def test_utc_offset_out_of_bounds(self):
2019 class Edgy(tzinfo):
2020 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00002021 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00002022 def utcoffset(self, dt):
2023 return self.offset
2024
2025 cls = self.theclass
2026 for offset, legit in ((-1440, False),
2027 (-1439, True),
2028 (1439, True),
2029 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002030 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00002031 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002032 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00002033 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002034 else:
2035 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00002036 if legit:
2037 aofs = abs(offset)
2038 h, m = divmod(aofs, 60)
2039 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002040 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00002041 t = t.timetz()
2042 self.assertEqual(str(t), "01:02:03" + tag)
2043 else:
2044 self.assertRaises(ValueError, str, t)
2045
2046 def test_tzinfo_classes(self):
2047 cls = self.theclass
2048 class C1(tzinfo):
2049 def utcoffset(self, dt): return None
2050 def dst(self, dt): return None
2051 def tzname(self, dt): return None
2052 for t in (cls(1, 1, 1),
2053 cls(1, 1, 1, tzinfo=None),
2054 cls(1, 1, 1, tzinfo=C1())):
2055 self.failUnless(t.utcoffset() is None)
2056 self.failUnless(t.dst() is None)
2057 self.failUnless(t.tzname() is None)
2058
Tim Peters855fe882002-12-22 03:43:39 +00002059 class C3(tzinfo):
2060 def utcoffset(self, dt): return timedelta(minutes=-1439)
2061 def dst(self, dt): return timedelta(minutes=1439)
2062 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00002063 t = cls(1, 1, 1, tzinfo=C3())
2064 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2065 self.assertEqual(t.dst(), timedelta(minutes=1439))
2066 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00002067
2068 # Wrong types.
2069 class C4(tzinfo):
2070 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00002071 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00002072 def tzname(self, dt): return 0
2073 t = cls(1, 1, 1, tzinfo=C4())
2074 self.assertRaises(TypeError, t.utcoffset)
2075 self.assertRaises(TypeError, t.dst)
2076 self.assertRaises(TypeError, t.tzname)
2077
2078 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00002079 class C6(tzinfo):
2080 def utcoffset(self, dt): return timedelta(hours=-24)
2081 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00002082 t = cls(1, 1, 1, tzinfo=C6())
2083 self.assertRaises(ValueError, t.utcoffset)
2084 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00002085
2086 # Not a whole number of minutes.
2087 class C7(tzinfo):
2088 def utcoffset(self, dt): return timedelta(seconds=61)
2089 def dst(self, dt): return timedelta(microseconds=-81)
2090 t = cls(1, 1, 1, tzinfo=C7())
2091 self.assertRaises(ValueError, t.utcoffset)
2092 self.assertRaises(ValueError, t.dst)
2093
Tim Peters4c0db782002-12-26 05:01:19 +00002094 def test_aware_compare(self):
2095 cls = self.theclass
2096
Tim Peters60c76e42002-12-27 00:41:11 +00002097 # Ensure that utcoffset() gets ignored if the comparands have
2098 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002099 class OperandDependentOffset(tzinfo):
2100 def utcoffset(self, t):
2101 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002102 # d0 and d1 equal after adjustment
2103 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002104 else:
Tim Peters397301e2003-01-02 21:28:08 +00002105 # d2 off in the weeds
2106 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002107
2108 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2109 d0 = base.replace(minute=3)
2110 d1 = base.replace(minute=9)
2111 d2 = base.replace(minute=11)
2112 for x in d0, d1, d2:
2113 for y in d0, d1, d2:
2114 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002115 expected = cmp(x.minute, y.minute)
2116 self.assertEqual(got, expected)
2117
2118 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002119 # Note that a time can't actually have an operand-depedent offset,
2120 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2121 # so skip this test for time.
2122 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002123 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2124 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2125 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2126 for x in d0, d1, d2:
2127 for y in d0, d1, d2:
2128 got = cmp(x, y)
2129 if (x is d0 or x is d1) and (y is d0 or y is d1):
2130 expected = 0
2131 elif x is y is d2:
2132 expected = 0
2133 elif x is d2:
2134 expected = -1
2135 else:
2136 assert y is d2
2137 expected = 1
2138 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002139
Tim Peters855fe882002-12-22 03:43:39 +00002140
Tim Peters0bf60bd2003-01-08 20:40:01 +00002141# Testing time objects with a non-None tzinfo.
Collin Winterc2898c52007-04-25 17:29:52 +00002142class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002143 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002144
2145 def test_empty(self):
2146 t = self.theclass()
2147 self.assertEqual(t.hour, 0)
2148 self.assertEqual(t.minute, 0)
2149 self.assertEqual(t.second, 0)
2150 self.assertEqual(t.microsecond, 0)
2151 self.failUnless(t.tzinfo is None)
2152
Tim Peters2a799bf2002-12-16 20:18:38 +00002153 def test_zones(self):
2154 est = FixedOffset(-300, "EST", 1)
2155 utc = FixedOffset(0, "UTC", -2)
2156 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002157 t1 = time( 7, 47, tzinfo=est)
2158 t2 = time(12, 47, tzinfo=utc)
2159 t3 = time(13, 47, tzinfo=met)
2160 t4 = time(microsecond=40)
2161 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002162
2163 self.assertEqual(t1.tzinfo, est)
2164 self.assertEqual(t2.tzinfo, utc)
2165 self.assertEqual(t3.tzinfo, met)
2166 self.failUnless(t4.tzinfo is None)
2167 self.assertEqual(t5.tzinfo, utc)
2168
Tim Peters855fe882002-12-22 03:43:39 +00002169 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2170 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2171 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002172 self.failUnless(t4.utcoffset() is None)
2173 self.assertRaises(TypeError, t1.utcoffset, "no args")
2174
2175 self.assertEqual(t1.tzname(), "EST")
2176 self.assertEqual(t2.tzname(), "UTC")
2177 self.assertEqual(t3.tzname(), "MET")
2178 self.failUnless(t4.tzname() is None)
2179 self.assertRaises(TypeError, t1.tzname, "no args")
2180
Tim Peters855fe882002-12-22 03:43:39 +00002181 self.assertEqual(t1.dst(), timedelta(minutes=1))
2182 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2183 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002184 self.failUnless(t4.dst() is None)
2185 self.assertRaises(TypeError, t1.dst, "no args")
2186
2187 self.assertEqual(hash(t1), hash(t2))
2188 self.assertEqual(hash(t1), hash(t3))
2189 self.assertEqual(hash(t2), hash(t3))
2190
2191 self.assertEqual(t1, t2)
2192 self.assertEqual(t1, t3)
2193 self.assertEqual(t2, t3)
2194 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2195 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2196 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2197
2198 self.assertEqual(str(t1), "07:47:00-05:00")
2199 self.assertEqual(str(t2), "12:47:00+00:00")
2200 self.assertEqual(str(t3), "13:47:00+01:00")
2201 self.assertEqual(str(t4), "00:00:00.000040")
2202 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2203
2204 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2205 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2206 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2207 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2208 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2209
Tim Peters0bf60bd2003-01-08 20:40:01 +00002210 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002211 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2212 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2213 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2214 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2215 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2216
2217 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2218 "07:47:00 %Z=EST %z=-0500")
2219 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2220 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2221
2222 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002223 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002224 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2225 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2226
Tim Petersb92bb712002-12-21 17:44:07 +00002227 # Check that an invalid tzname result raises an exception.
2228 class Badtzname(tzinfo):
2229 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002230 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002231 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2232 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002233
2234 def test_hash_edge_cases(self):
2235 # Offsets that overflow a basic time.
2236 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2237 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2238 self.assertEqual(hash(t1), hash(t2))
2239
2240 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2241 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2242 self.assertEqual(hash(t1), hash(t2))
2243
Tim Peters2a799bf2002-12-16 20:18:38 +00002244 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002245 # Try one without a tzinfo.
2246 args = 20, 59, 16, 64**2
2247 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002248 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002249 green = pickler.dumps(orig, proto)
2250 derived = unpickler.loads(green)
2251 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002252
2253 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002254 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002255 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002256 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002257 green = pickler.dumps(orig, proto)
2258 derived = unpickler.loads(green)
2259 self.assertEqual(orig, derived)
2260 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2261 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2262 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002263
2264 def test_more_bool(self):
2265 # Test cases with non-None tzinfo.
2266 cls = self.theclass
2267
2268 t = cls(0, tzinfo=FixedOffset(-300, ""))
2269 self.failUnless(t)
2270
2271 t = cls(5, tzinfo=FixedOffset(-300, ""))
2272 self.failUnless(t)
2273
2274 t = cls(5, tzinfo=FixedOffset(300, ""))
2275 self.failUnless(not t)
2276
2277 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2278 self.failUnless(not t)
2279
2280 # Mostly ensuring this doesn't overflow internally.
2281 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2282 self.failUnless(t)
2283
2284 # But this should yield a value error -- the utcoffset is bogus.
2285 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2286 self.assertRaises(ValueError, lambda: bool(t))
2287
2288 # Likewise.
2289 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2290 self.assertRaises(ValueError, lambda: bool(t))
2291
Tim Peters12bf3392002-12-24 05:41:27 +00002292 def test_replace(self):
2293 cls = self.theclass
2294 z100 = FixedOffset(100, "+100")
2295 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2296 args = [1, 2, 3, 4, z100]
2297 base = cls(*args)
2298 self.assertEqual(base, base.replace())
2299
2300 i = 0
2301 for name, newval in (("hour", 5),
2302 ("minute", 6),
2303 ("second", 7),
2304 ("microsecond", 8),
2305 ("tzinfo", zm200)):
2306 newargs = args[:]
2307 newargs[i] = newval
2308 expected = cls(*newargs)
2309 got = base.replace(**{name: newval})
2310 self.assertEqual(expected, got)
2311 i += 1
2312
2313 # Ensure we can get rid of a tzinfo.
2314 self.assertEqual(base.tzname(), "+100")
2315 base2 = base.replace(tzinfo=None)
2316 self.failUnless(base2.tzinfo is None)
2317 self.failUnless(base2.tzname() is None)
2318
2319 # Ensure we can add one.
2320 base3 = base2.replace(tzinfo=z100)
2321 self.assertEqual(base, base3)
2322 self.failUnless(base.tzinfo is base3.tzinfo)
2323
2324 # Out of bounds.
2325 base = cls(1)
2326 self.assertRaises(ValueError, base.replace, hour=24)
2327 self.assertRaises(ValueError, base.replace, minute=-1)
2328 self.assertRaises(ValueError, base.replace, second=100)
2329 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2330
Tim Peters60c76e42002-12-27 00:41:11 +00002331 def test_mixed_compare(self):
2332 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002333 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002334 self.assertEqual(t1, t2)
2335 t2 = t2.replace(tzinfo=None)
2336 self.assertEqual(t1, t2)
2337 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2338 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002339 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2340 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002341
Tim Peters0bf60bd2003-01-08 20:40:01 +00002342 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002343 class Varies(tzinfo):
2344 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002345 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002346 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002347 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002348 return self.offset
2349
2350 v = Varies()
2351 t1 = t2.replace(tzinfo=v)
2352 t2 = t2.replace(tzinfo=v)
2353 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2354 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2355 self.assertEqual(t1, t2)
2356
2357 # But if they're not identical, it isn't ignored.
2358 t2 = t2.replace(tzinfo=Varies())
2359 self.failUnless(t1 < t2) # t1's offset counter still going up
2360
Tim Petersa98924a2003-05-17 05:55:19 +00002361 def test_subclass_timetz(self):
2362
2363 class C(self.theclass):
2364 theAnswer = 42
2365
2366 def __new__(cls, *args, **kws):
2367 temp = kws.copy()
2368 extra = temp.pop('extra')
2369 result = self.theclass.__new__(cls, *args, **temp)
2370 result.extra = extra
2371 return result
2372
2373 def newmeth(self, start):
2374 return start + self.hour + self.second
2375
2376 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2377
2378 dt1 = self.theclass(*args)
2379 dt2 = C(*args, **{'extra': 7})
2380
2381 self.assertEqual(dt2.__class__, C)
2382 self.assertEqual(dt2.theAnswer, 42)
2383 self.assertEqual(dt2.extra, 7)
2384 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2385 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2386
Tim Peters4c0db782002-12-26 05:01:19 +00002387
Tim Peters0bf60bd2003-01-08 20:40:01 +00002388# Testing datetime objects with a non-None tzinfo.
2389
Collin Winterc2898c52007-04-25 17:29:52 +00002390class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002391 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002392
2393 def test_trivial(self):
2394 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2395 self.assertEqual(dt.year, 1)
2396 self.assertEqual(dt.month, 2)
2397 self.assertEqual(dt.day, 3)
2398 self.assertEqual(dt.hour, 4)
2399 self.assertEqual(dt.minute, 5)
2400 self.assertEqual(dt.second, 6)
2401 self.assertEqual(dt.microsecond, 7)
2402 self.assertEqual(dt.tzinfo, None)
2403
2404 def test_even_more_compare(self):
2405 # The test_compare() and test_more_compare() inherited from TestDate
2406 # and TestDateTime covered non-tzinfo cases.
2407
2408 # Smallest possible after UTC adjustment.
2409 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2410 # Largest possible after UTC adjustment.
2411 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2412 tzinfo=FixedOffset(-1439, ""))
2413
2414 # Make sure those compare correctly, and w/o overflow.
2415 self.failUnless(t1 < t2)
2416 self.failUnless(t1 != t2)
2417 self.failUnless(t2 > t1)
2418
2419 self.failUnless(t1 == t1)
2420 self.failUnless(t2 == t2)
2421
2422 # Equal afer adjustment.
2423 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2424 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2425 self.assertEqual(t1, t2)
2426
2427 # Change t1 not to subtract a minute, and t1 should be larger.
2428 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2429 self.failUnless(t1 > t2)
2430
2431 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2432 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2433 self.failUnless(t1 < t2)
2434
2435 # Back to the original t1, but make seconds resolve it.
2436 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2437 second=1)
2438 self.failUnless(t1 > t2)
2439
2440 # Likewise, but make microseconds resolve it.
2441 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2442 microsecond=1)
2443 self.failUnless(t1 > t2)
2444
2445 # Make t2 naive and it should fail.
2446 t2 = self.theclass.min
2447 self.assertRaises(TypeError, lambda: t1 == t2)
2448 self.assertEqual(t2, t2)
2449
2450 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2451 class Naive(tzinfo):
2452 def utcoffset(self, dt): return None
2453 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2454 self.assertRaises(TypeError, lambda: t1 == t2)
2455 self.assertEqual(t2, t2)
2456
2457 # OTOH, it's OK to compare two of these mixing the two ways of being
2458 # naive.
2459 t1 = self.theclass(5, 6, 7)
2460 self.assertEqual(t1, t2)
2461
2462 # Try a bogus uctoffset.
2463 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002464 def utcoffset(self, dt):
2465 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002466 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2467 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002468 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002469
Tim Peters2a799bf2002-12-16 20:18:38 +00002470 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002471 # Try one without a tzinfo.
2472 args = 6, 7, 23, 20, 59, 1, 64**2
2473 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002474 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002475 green = pickler.dumps(orig, proto)
2476 derived = unpickler.loads(green)
2477 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002478
2479 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002480 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002481 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002482 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002483 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002484 green = pickler.dumps(orig, proto)
2485 derived = unpickler.loads(green)
2486 self.assertEqual(orig, derived)
2487 self.failUnless(isinstance(derived.tzinfo,
2488 PicklableFixedOffset))
2489 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2490 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002491
2492 def test_extreme_hashes(self):
2493 # If an attempt is made to hash these via subtracting the offset
2494 # then hashing a datetime object, OverflowError results. The
2495 # Python implementation used to blow up here.
2496 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2497 hash(t)
2498 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2499 tzinfo=FixedOffset(-1439, ""))
2500 hash(t)
2501
2502 # OTOH, an OOB offset should blow up.
2503 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2504 self.assertRaises(ValueError, hash, t)
2505
2506 def test_zones(self):
2507 est = FixedOffset(-300, "EST")
2508 utc = FixedOffset(0, "UTC")
2509 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002510 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2511 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2512 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002513 self.assertEqual(t1.tzinfo, est)
2514 self.assertEqual(t2.tzinfo, utc)
2515 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002516 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2517 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2518 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002519 self.assertEqual(t1.tzname(), "EST")
2520 self.assertEqual(t2.tzname(), "UTC")
2521 self.assertEqual(t3.tzname(), "MET")
2522 self.assertEqual(hash(t1), hash(t2))
2523 self.assertEqual(hash(t1), hash(t3))
2524 self.assertEqual(hash(t2), hash(t3))
2525 self.assertEqual(t1, t2)
2526 self.assertEqual(t1, t3)
2527 self.assertEqual(t2, t3)
2528 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2529 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2530 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002531 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002532 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2533 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2534 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2535
2536 def test_combine(self):
2537 met = FixedOffset(60, "MET")
2538 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002539 tz = time(18, 45, 3, 1234, tzinfo=met)
2540 dt = datetime.combine(d, tz)
2541 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002542 tzinfo=met))
2543
2544 def test_extract(self):
2545 met = FixedOffset(60, "MET")
2546 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2547 self.assertEqual(dt.date(), date(2002, 3, 4))
2548 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002549 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002550
2551 def test_tz_aware_arithmetic(self):
2552 import random
2553
2554 now = self.theclass.now()
2555 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002556 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002557 nowaware = self.theclass.combine(now.date(), timeaware)
2558 self.failUnless(nowaware.tzinfo is tz55)
2559 self.assertEqual(nowaware.timetz(), timeaware)
2560
2561 # Can't mix aware and non-aware.
2562 self.assertRaises(TypeError, lambda: now - nowaware)
2563 self.assertRaises(TypeError, lambda: nowaware - now)
2564
Tim Peters0bf60bd2003-01-08 20:40:01 +00002565 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002566 self.assertRaises(TypeError, lambda: now + nowaware)
2567 self.assertRaises(TypeError, lambda: nowaware + now)
2568 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2569
2570 # Subtracting should yield 0.
2571 self.assertEqual(now - now, timedelta(0))
2572 self.assertEqual(nowaware - nowaware, timedelta(0))
2573
2574 # Adding a delta should preserve tzinfo.
2575 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2576 nowawareplus = nowaware + delta
2577 self.failUnless(nowaware.tzinfo is tz55)
2578 nowawareplus2 = delta + nowaware
2579 self.failUnless(nowawareplus2.tzinfo is tz55)
2580 self.assertEqual(nowawareplus, nowawareplus2)
2581
2582 # that - delta should be what we started with, and that - what we
2583 # started with should be delta.
2584 diff = nowawareplus - delta
2585 self.failUnless(diff.tzinfo is tz55)
2586 self.assertEqual(nowaware, diff)
2587 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2588 self.assertEqual(nowawareplus - nowaware, delta)
2589
2590 # Make up a random timezone.
2591 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002592 # Attach it to nowawareplus.
2593 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002594 self.failUnless(nowawareplus.tzinfo is tzr)
2595 # Make sure the difference takes the timezone adjustments into account.
2596 got = nowaware - nowawareplus
2597 # Expected: (nowaware base - nowaware offset) -
2598 # (nowawareplus base - nowawareplus offset) =
2599 # (nowaware base - nowawareplus base) +
2600 # (nowawareplus offset - nowaware offset) =
2601 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002602 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002603 self.assertEqual(got, expected)
2604
2605 # Try max possible difference.
2606 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2607 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2608 tzinfo=FixedOffset(-1439, "max"))
2609 maxdiff = max - min
2610 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2611 timedelta(minutes=2*1439))
2612
2613 def test_tzinfo_now(self):
2614 meth = self.theclass.now
2615 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2616 base = meth()
2617 # Try with and without naming the keyword.
2618 off42 = FixedOffset(42, "42")
2619 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002620 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002621 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002622 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002623 # Bad argument with and w/o naming the keyword.
2624 self.assertRaises(TypeError, meth, 16)
2625 self.assertRaises(TypeError, meth, tzinfo=16)
2626 # Bad keyword name.
2627 self.assertRaises(TypeError, meth, tinfo=off42)
2628 # Too many args.
2629 self.assertRaises(TypeError, meth, off42, off42)
2630
Tim Peters10cadce2003-01-23 19:58:02 +00002631 # We don't know which time zone we're in, and don't have a tzinfo
2632 # class to represent it, so seeing whether a tz argument actually
2633 # does a conversion is tricky.
2634 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2635 utc = FixedOffset(0, "utc", 0)
2636 for dummy in range(3):
2637 now = datetime.now(weirdtz)
2638 self.failUnless(now.tzinfo is weirdtz)
2639 utcnow = datetime.utcnow().replace(tzinfo=utc)
2640 now2 = utcnow.astimezone(weirdtz)
2641 if abs(now - now2) < timedelta(seconds=30):
2642 break
2643 # Else the code is broken, or more than 30 seconds passed between
2644 # calls; assuming the latter, just try again.
2645 else:
2646 # Three strikes and we're out.
2647 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2648
Tim Peters2a799bf2002-12-16 20:18:38 +00002649 def test_tzinfo_fromtimestamp(self):
2650 import time
2651 meth = self.theclass.fromtimestamp
2652 ts = time.time()
2653 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2654 base = meth(ts)
2655 # Try with and without naming the keyword.
2656 off42 = FixedOffset(42, "42")
2657 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002658 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002659 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002660 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002661 # Bad argument with and w/o naming the keyword.
2662 self.assertRaises(TypeError, meth, ts, 16)
2663 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2664 # Bad keyword name.
2665 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2666 # Too many args.
2667 self.assertRaises(TypeError, meth, ts, off42, off42)
2668 # Too few args.
2669 self.assertRaises(TypeError, meth)
2670
Tim Peters2a44a8d2003-01-23 20:53:10 +00002671 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002672 timestamp = 1000000000
2673 utcdatetime = datetime.utcfromtimestamp(timestamp)
2674 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2675 # But on some flavor of Mac, it's nowhere near that. So we can't have
2676 # any idea here what time that actually is, we can only test that
2677 # relative changes match.
2678 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2679 tz = FixedOffset(utcoffset, "tz", 0)
2680 expected = utcdatetime + utcoffset
2681 got = datetime.fromtimestamp(timestamp, tz)
2682 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002683
Tim Peters2a799bf2002-12-16 20:18:38 +00002684 def test_tzinfo_utcnow(self):
2685 meth = self.theclass.utcnow
2686 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2687 base = meth()
2688 # Try with and without naming the keyword; for whatever reason,
2689 # utcnow() doesn't accept a tzinfo argument.
2690 off42 = FixedOffset(42, "42")
2691 self.assertRaises(TypeError, meth, off42)
2692 self.assertRaises(TypeError, meth, tzinfo=off42)
2693
2694 def test_tzinfo_utcfromtimestamp(self):
2695 import time
2696 meth = self.theclass.utcfromtimestamp
2697 ts = time.time()
2698 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2699 base = meth(ts)
2700 # Try with and without naming the keyword; for whatever reason,
2701 # utcfromtimestamp() doesn't accept a tzinfo argument.
2702 off42 = FixedOffset(42, "42")
2703 self.assertRaises(TypeError, meth, ts, off42)
2704 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2705
2706 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002707 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002708 # DST flag.
2709 class DST(tzinfo):
2710 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002711 if isinstance(dstvalue, int):
2712 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002713 self.dstvalue = dstvalue
2714 def dst(self, dt):
2715 return self.dstvalue
2716
2717 cls = self.theclass
2718 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2719 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2720 t = d.timetuple()
2721 self.assertEqual(1, t.tm_year)
2722 self.assertEqual(1, t.tm_mon)
2723 self.assertEqual(1, t.tm_mday)
2724 self.assertEqual(10, t.tm_hour)
2725 self.assertEqual(20, t.tm_min)
2726 self.assertEqual(30, t.tm_sec)
2727 self.assertEqual(0, t.tm_wday)
2728 self.assertEqual(1, t.tm_yday)
2729 self.assertEqual(flag, t.tm_isdst)
2730
2731 # dst() returns wrong type.
2732 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2733
2734 # dst() at the edge.
2735 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2736 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2737
2738 # dst() out of range.
2739 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2740 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2741
2742 def test_utctimetuple(self):
2743 class DST(tzinfo):
2744 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002745 if isinstance(dstvalue, int):
2746 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002747 self.dstvalue = dstvalue
2748 def dst(self, dt):
2749 return self.dstvalue
2750
2751 cls = self.theclass
2752 # This can't work: DST didn't implement utcoffset.
2753 self.assertRaises(NotImplementedError,
2754 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2755
2756 class UOFS(DST):
2757 def __init__(self, uofs, dofs=None):
2758 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002759 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002760 def utcoffset(self, dt):
2761 return self.uofs
2762
2763 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2764 # in effect for a UTC time.
2765 for dstvalue in -33, 33, 0, None:
2766 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2767 t = d.utctimetuple()
2768 self.assertEqual(d.year, t.tm_year)
2769 self.assertEqual(d.month, t.tm_mon)
2770 self.assertEqual(d.day, t.tm_mday)
2771 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2772 self.assertEqual(13, t.tm_min)
2773 self.assertEqual(d.second, t.tm_sec)
2774 self.assertEqual(d.weekday(), t.tm_wday)
2775 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2776 t.tm_yday)
2777 self.assertEqual(0, t.tm_isdst)
2778
2779 # At the edges, UTC adjustment can normalize into years out-of-range
2780 # for a datetime object. Ensure that a correct timetuple is
2781 # created anyway.
2782 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2783 # That goes back 1 minute less than a full day.
2784 t = tiny.utctimetuple()
2785 self.assertEqual(t.tm_year, MINYEAR-1)
2786 self.assertEqual(t.tm_mon, 12)
2787 self.assertEqual(t.tm_mday, 31)
2788 self.assertEqual(t.tm_hour, 0)
2789 self.assertEqual(t.tm_min, 1)
2790 self.assertEqual(t.tm_sec, 37)
2791 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2792 self.assertEqual(t.tm_isdst, 0)
2793
2794 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2795 # That goes forward 1 minute less than a full day.
2796 t = huge.utctimetuple()
2797 self.assertEqual(t.tm_year, MAXYEAR+1)
2798 self.assertEqual(t.tm_mon, 1)
2799 self.assertEqual(t.tm_mday, 1)
2800 self.assertEqual(t.tm_hour, 23)
2801 self.assertEqual(t.tm_min, 58)
2802 self.assertEqual(t.tm_sec, 37)
2803 self.assertEqual(t.tm_yday, 1)
2804 self.assertEqual(t.tm_isdst, 0)
2805
2806 def test_tzinfo_isoformat(self):
2807 zero = FixedOffset(0, "+00:00")
2808 plus = FixedOffset(220, "+03:40")
2809 minus = FixedOffset(-231, "-03:51")
2810 unknown = FixedOffset(None, "")
2811
2812 cls = self.theclass
2813 datestr = '0001-02-03'
2814 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002815 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002816 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2817 timestr = '04:05:59' + (us and '.987001' or '')
2818 ofsstr = ofs is not None and d.tzname() or ''
2819 tailstr = timestr + ofsstr
2820 iso = d.isoformat()
2821 self.assertEqual(iso, datestr + 'T' + tailstr)
2822 self.assertEqual(iso, d.isoformat('T'))
2823 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2824 self.assertEqual(str(d), datestr + ' ' + tailstr)
2825
Tim Peters12bf3392002-12-24 05:41:27 +00002826 def test_replace(self):
2827 cls = self.theclass
2828 z100 = FixedOffset(100, "+100")
2829 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2830 args = [1, 2, 3, 4, 5, 6, 7, z100]
2831 base = cls(*args)
2832 self.assertEqual(base, base.replace())
2833
2834 i = 0
2835 for name, newval in (("year", 2),
2836 ("month", 3),
2837 ("day", 4),
2838 ("hour", 5),
2839 ("minute", 6),
2840 ("second", 7),
2841 ("microsecond", 8),
2842 ("tzinfo", zm200)):
2843 newargs = args[:]
2844 newargs[i] = newval
2845 expected = cls(*newargs)
2846 got = base.replace(**{name: newval})
2847 self.assertEqual(expected, got)
2848 i += 1
2849
2850 # Ensure we can get rid of a tzinfo.
2851 self.assertEqual(base.tzname(), "+100")
2852 base2 = base.replace(tzinfo=None)
2853 self.failUnless(base2.tzinfo is None)
2854 self.failUnless(base2.tzname() is None)
2855
2856 # Ensure we can add one.
2857 base3 = base2.replace(tzinfo=z100)
2858 self.assertEqual(base, base3)
2859 self.failUnless(base.tzinfo is base3.tzinfo)
2860
2861 # Out of bounds.
2862 base = cls(2000, 2, 29)
2863 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002864
Tim Peters80475bb2002-12-25 07:40:55 +00002865 def test_more_astimezone(self):
2866 # The inherited test_astimezone covered some trivial and error cases.
2867 fnone = FixedOffset(None, "None")
2868 f44m = FixedOffset(44, "44")
2869 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2870
Tim Peters10cadce2003-01-23 19:58:02 +00002871 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002872 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002873 # Replacing with degenerate tzinfo raises an exception.
2874 self.assertRaises(ValueError, dt.astimezone, fnone)
2875 # Ditto with None tz.
2876 self.assertRaises(TypeError, dt.astimezone, None)
2877 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002878 x = dt.astimezone(dt.tzinfo)
2879 self.failUnless(x.tzinfo is f44m)
2880 self.assertEqual(x.date(), dt.date())
2881 self.assertEqual(x.time(), dt.time())
2882
2883 # Replacing with different tzinfo does adjust.
2884 got = dt.astimezone(fm5h)
2885 self.failUnless(got.tzinfo is fm5h)
2886 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2887 expected = dt - dt.utcoffset() # in effect, convert to UTC
2888 expected += fm5h.utcoffset(dt) # and from there to local time
2889 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2890 self.assertEqual(got.date(), expected.date())
2891 self.assertEqual(got.time(), expected.time())
2892 self.assertEqual(got.timetz(), expected.timetz())
2893 self.failUnless(got.tzinfo is expected.tzinfo)
2894 self.assertEqual(got, expected)
2895
Tim Peters4c0db782002-12-26 05:01:19 +00002896 def test_aware_subtract(self):
2897 cls = self.theclass
2898
Tim Peters60c76e42002-12-27 00:41:11 +00002899 # Ensure that utcoffset() is ignored when the operands have the
2900 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002901 class OperandDependentOffset(tzinfo):
2902 def utcoffset(self, t):
2903 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002904 # d0 and d1 equal after adjustment
2905 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002906 else:
Tim Peters397301e2003-01-02 21:28:08 +00002907 # d2 off in the weeds
2908 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002909
2910 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2911 d0 = base.replace(minute=3)
2912 d1 = base.replace(minute=9)
2913 d2 = base.replace(minute=11)
2914 for x in d0, d1, d2:
2915 for y in d0, d1, d2:
2916 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002917 expected = timedelta(minutes=x.minute - y.minute)
2918 self.assertEqual(got, expected)
2919
2920 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2921 # ignored.
2922 base = cls(8, 9, 10, 11, 12, 13, 14)
2923 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2924 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2925 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2926 for x in d0, d1, d2:
2927 for y in d0, d1, d2:
2928 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002929 if (x is d0 or x is d1) and (y is d0 or y is d1):
2930 expected = timedelta(0)
2931 elif x is y is d2:
2932 expected = timedelta(0)
2933 elif x is d2:
2934 expected = timedelta(minutes=(11-59)-0)
2935 else:
2936 assert y is d2
2937 expected = timedelta(minutes=0-(11-59))
2938 self.assertEqual(got, expected)
2939
Tim Peters60c76e42002-12-27 00:41:11 +00002940 def test_mixed_compare(self):
2941 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002942 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002943 self.assertEqual(t1, t2)
2944 t2 = t2.replace(tzinfo=None)
2945 self.assertEqual(t1, t2)
2946 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2947 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002948 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2949 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002950
Tim Peters0bf60bd2003-01-08 20:40:01 +00002951 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002952 class Varies(tzinfo):
2953 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002954 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002955 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002956 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002957 return self.offset
2958
2959 v = Varies()
2960 t1 = t2.replace(tzinfo=v)
2961 t2 = t2.replace(tzinfo=v)
2962 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2963 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2964 self.assertEqual(t1, t2)
2965
2966 # But if they're not identical, it isn't ignored.
2967 t2 = t2.replace(tzinfo=Varies())
2968 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002969
Tim Petersa98924a2003-05-17 05:55:19 +00002970 def test_subclass_datetimetz(self):
2971
2972 class C(self.theclass):
2973 theAnswer = 42
2974
2975 def __new__(cls, *args, **kws):
2976 temp = kws.copy()
2977 extra = temp.pop('extra')
2978 result = self.theclass.__new__(cls, *args, **temp)
2979 result.extra = extra
2980 return result
2981
2982 def newmeth(self, start):
2983 return start + self.hour + self.year
2984
2985 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2986
2987 dt1 = self.theclass(*args)
2988 dt2 = C(*args, **{'extra': 7})
2989
2990 self.assertEqual(dt2.__class__, C)
2991 self.assertEqual(dt2.theAnswer, 42)
2992 self.assertEqual(dt2.extra, 7)
2993 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2994 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2995
Tim Peters621818b2002-12-29 23:44:49 +00002996# Pain to set up DST-aware tzinfo classes.
2997
2998def first_sunday_on_or_after(dt):
2999 days_to_go = 6 - dt.weekday()
3000 if days_to_go:
3001 dt += timedelta(days_to_go)
3002 return dt
3003
3004ZERO = timedelta(0)
3005HOUR = timedelta(hours=1)
3006DAY = timedelta(days=1)
3007# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3008DSTSTART = datetime(1, 4, 1, 2)
3009# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00003010# which is the first Sunday on or after Oct 25. Because we view 1:MM as
3011# being standard time on that day, there is no spelling in local time of
3012# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3013DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00003014
3015class USTimeZone(tzinfo):
3016
3017 def __init__(self, hours, reprname, stdname, dstname):
3018 self.stdoffset = timedelta(hours=hours)
3019 self.reprname = reprname
3020 self.stdname = stdname
3021 self.dstname = dstname
3022
3023 def __repr__(self):
3024 return self.reprname
3025
3026 def tzname(self, dt):
3027 if self.dst(dt):
3028 return self.dstname
3029 else:
3030 return self.stdname
3031
3032 def utcoffset(self, dt):
3033 return self.stdoffset + self.dst(dt)
3034
3035 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00003036 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00003037 # An exception instead may be sensible here, in one or more of
3038 # the cases.
3039 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00003040 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00003041
3042 # Find first Sunday in April.
3043 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3044 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3045
3046 # Find last Sunday in October.
3047 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3048 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3049
Tim Peters621818b2002-12-29 23:44:49 +00003050 # Can't compare naive to aware objects, so strip the timezone from
3051 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00003052 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00003053 return HOUR
3054 else:
3055 return ZERO
3056
Tim Peters521fc152002-12-31 17:36:56 +00003057Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
3058Central = USTimeZone(-6, "Central", "CST", "CDT")
3059Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3060Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00003061utc_real = FixedOffset(0, "UTC", 0)
3062# For better test coverage, we want another flavor of UTC that's west of
3063# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00003064utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00003065
3066class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00003067 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00003068 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00003069 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00003070
Tim Peters0bf60bd2003-01-08 20:40:01 +00003071 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00003072
Tim Peters521fc152002-12-31 17:36:56 +00003073 # Check a time that's inside DST.
3074 def checkinside(self, dt, tz, utc, dston, dstoff):
3075 self.assertEqual(dt.dst(), HOUR)
3076
3077 # Conversion to our own timezone is always an identity.
3078 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00003079
3080 asutc = dt.astimezone(utc)
3081 there_and_back = asutc.astimezone(tz)
3082
3083 # Conversion to UTC and back isn't always an identity here,
3084 # because there are redundant spellings (in local time) of
3085 # UTC time when DST begins: the clock jumps from 1:59:59
3086 # to 3:00:00, and a local time of 2:MM:SS doesn't really
3087 # make sense then. The classes above treat 2:MM:SS as
3088 # daylight time then (it's "after 2am"), really an alias
3089 # for 1:MM:SS standard time. The latter form is what
3090 # conversion back from UTC produces.
3091 if dt.date() == dston.date() and dt.hour == 2:
3092 # We're in the redundant hour, and coming back from
3093 # UTC gives the 1:MM:SS standard-time spelling.
3094 self.assertEqual(there_and_back + HOUR, dt)
3095 # Although during was considered to be in daylight
3096 # time, there_and_back is not.
3097 self.assertEqual(there_and_back.dst(), ZERO)
3098 # They're the same times in UTC.
3099 self.assertEqual(there_and_back.astimezone(utc),
3100 dt.astimezone(utc))
3101 else:
3102 # We're not in the redundant hour.
3103 self.assertEqual(dt, there_and_back)
3104
Tim Peters327098a2003-01-20 22:54:38 +00003105 # Because we have a redundant spelling when DST begins, there is
3106 # (unforunately) an hour when DST ends that can't be spelled at all in
3107 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3108 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3109 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3110 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3111 # expressed in local time. Nevertheless, we want conversion back
3112 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003113 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003114 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003115 if dt.date() == dstoff.date() and dt.hour == 0:
3116 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003117 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003118 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3119 nexthour_utc += HOUR
3120 nexthour_tz = nexthour_utc.astimezone(tz)
3121 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003122 else:
Tim Peters327098a2003-01-20 22:54:38 +00003123 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003124
3125 # Check a time that's outside DST.
3126 def checkoutside(self, dt, tz, utc):
3127 self.assertEqual(dt.dst(), ZERO)
3128
3129 # Conversion to our own timezone is always an identity.
3130 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003131
3132 # Converting to UTC and back is an identity too.
3133 asutc = dt.astimezone(utc)
3134 there_and_back = asutc.astimezone(tz)
3135 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003136
Tim Peters1024bf82002-12-30 17:09:40 +00003137 def convert_between_tz_and_utc(self, tz, utc):
3138 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003139 # Because 1:MM on the day DST ends is taken as being standard time,
3140 # there is no spelling in tz for the last hour of daylight time.
3141 # For purposes of the test, the last hour of DST is 0:MM, which is
3142 # taken as being daylight time (and 1:MM is taken as being standard
3143 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003144 dstoff = self.dstoff.replace(tzinfo=tz)
3145 for delta in (timedelta(weeks=13),
3146 DAY,
3147 HOUR,
3148 timedelta(minutes=1),
3149 timedelta(microseconds=1)):
3150
Tim Peters521fc152002-12-31 17:36:56 +00003151 self.checkinside(dston, tz, utc, dston, dstoff)
3152 for during in dston + delta, dstoff - delta:
3153 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003154
Tim Peters521fc152002-12-31 17:36:56 +00003155 self.checkoutside(dstoff, tz, utc)
3156 for outside in dston - delta, dstoff + delta:
3157 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003158
Tim Peters621818b2002-12-29 23:44:49 +00003159 def test_easy(self):
3160 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003161 self.convert_between_tz_and_utc(Eastern, utc_real)
3162 self.convert_between_tz_and_utc(Pacific, utc_real)
3163 self.convert_between_tz_and_utc(Eastern, utc_fake)
3164 self.convert_between_tz_and_utc(Pacific, utc_fake)
3165 # The next is really dancing near the edge. It works because
3166 # Pacific and Eastern are far enough apart that their "problem
3167 # hours" don't overlap.
3168 self.convert_between_tz_and_utc(Eastern, Pacific)
3169 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003170 # OTOH, these fail! Don't enable them. The difficulty is that
3171 # the edge case tests assume that every hour is representable in
3172 # the "utc" class. This is always true for a fixed-offset tzinfo
3173 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3174 # For these adjacent DST-aware time zones, the range of time offsets
3175 # tested ends up creating hours in the one that aren't representable
3176 # in the other. For the same reason, we would see failures in the
3177 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3178 # offset deltas in convert_between_tz_and_utc().
3179 #
3180 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3181 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003182
Tim Petersf3615152003-01-01 21:51:37 +00003183 def test_tricky(self):
3184 # 22:00 on day before daylight starts.
3185 fourback = self.dston - timedelta(hours=4)
3186 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003187 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003188 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3189 # 2", we should get the 3 spelling.
3190 # If we plug 22:00 the day before into Eastern, it "looks like std
3191 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3192 # to 22:00 lands on 2:00, which makes no sense in local time (the
3193 # local clock jumps from 1 to 3). The point here is to make sure we
3194 # get the 3 spelling.
3195 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003196 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003197 self.assertEqual(expected, got)
3198
3199 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3200 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003201 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003202 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3203 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3204 # spelling.
3205 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003206 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003207 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003208
Tim Petersadf64202003-01-04 06:03:15 +00003209 # Now on the day DST ends, we want "repeat an hour" behavior.
3210 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3211 # EST 23:MM 0:MM 1:MM 2:MM
3212 # EDT 0:MM 1:MM 2:MM 3:MM
3213 # wall 0:MM 1:MM 1:MM 2:MM against these
3214 for utc in utc_real, utc_fake:
3215 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003216 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003217 # Convert that to UTC.
3218 first_std_hour -= tz.utcoffset(None)
3219 # Adjust for possibly fake UTC.
3220 asutc = first_std_hour + utc.utcoffset(None)
3221 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3222 # tz=Eastern.
3223 asutcbase = asutc.replace(tzinfo=utc)
3224 for tzhour in (0, 1, 1, 2):
3225 expectedbase = self.dstoff.replace(hour=tzhour)
3226 for minute in 0, 30, 59:
3227 expected = expectedbase.replace(minute=minute)
3228 asutc = asutcbase.replace(minute=minute)
3229 astz = asutc.astimezone(tz)
3230 self.assertEqual(astz.replace(tzinfo=None), expected)
3231 asutcbase += HOUR
3232
3233
Tim Peters710fb152003-01-02 19:35:54 +00003234 def test_bogus_dst(self):
3235 class ok(tzinfo):
3236 def utcoffset(self, dt): return HOUR
3237 def dst(self, dt): return HOUR
3238
3239 now = self.theclass.now().replace(tzinfo=utc_real)
3240 # Doesn't blow up.
3241 now.astimezone(ok())
3242
3243 # Does blow up.
3244 class notok(ok):
3245 def dst(self, dt): return None
3246 self.assertRaises(ValueError, now.astimezone, notok())
3247
Tim Peters52dcce22003-01-23 16:36:11 +00003248 def test_fromutc(self):
3249 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3250 now = datetime.utcnow().replace(tzinfo=utc_real)
3251 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3252 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3253 enow = Eastern.fromutc(now) # doesn't blow up
3254 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3255 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3256 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3257
3258 # Always converts UTC to standard time.
3259 class FauxUSTimeZone(USTimeZone):
3260 def fromutc(self, dt):
3261 return dt + self.stdoffset
3262 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3263
3264 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3265 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3266 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3267
3268 # Check around DST start.
3269 start = self.dston.replace(hour=4, tzinfo=Eastern)
3270 fstart = start.replace(tzinfo=FEastern)
3271 for wall in 23, 0, 1, 3, 4, 5:
3272 expected = start.replace(hour=wall)
3273 if wall == 23:
3274 expected -= timedelta(days=1)
3275 got = Eastern.fromutc(start)
3276 self.assertEqual(expected, got)
3277
3278 expected = fstart + FEastern.stdoffset
3279 got = FEastern.fromutc(fstart)
3280 self.assertEqual(expected, got)
3281
3282 # Ensure astimezone() calls fromutc() too.
3283 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3284 self.assertEqual(expected, got)
3285
3286 start += HOUR
3287 fstart += HOUR
3288
3289 # Check around DST end.
3290 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3291 fstart = start.replace(tzinfo=FEastern)
3292 for wall in 0, 1, 1, 2, 3, 4:
3293 expected = start.replace(hour=wall)
3294 got = Eastern.fromutc(start)
3295 self.assertEqual(expected, got)
3296
3297 expected = fstart + FEastern.stdoffset
3298 got = FEastern.fromutc(fstart)
3299 self.assertEqual(expected, got)
3300
3301 # Ensure astimezone() calls fromutc() too.
3302 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3303 self.assertEqual(expected, got)
3304
3305 start += HOUR
3306 fstart += HOUR
3307
Tim Peters710fb152003-01-02 19:35:54 +00003308
Tim Peters528ca532004-09-16 01:30:50 +00003309#############################################################################
3310# oddballs
3311
3312class Oddballs(unittest.TestCase):
3313
3314 def test_bug_1028306(self):
3315 # Trying to compare a date to a datetime should act like a mixed-
3316 # type comparison, despite that datetime is a subclass of date.
3317 as_date = date.today()
3318 as_datetime = datetime.combine(as_date, time())
3319 self.assert_(as_date != as_datetime)
3320 self.assert_(as_datetime != as_date)
3321 self.assert_(not as_date == as_datetime)
3322 self.assert_(not as_datetime == as_date)
3323 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3324 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3325 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3326 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3327 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3328 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3329 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3330 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3331
3332 # Neverthelss, comparison should work with the base-class (date)
3333 # projection if use of a date method is forced.
3334 self.assert_(as_date.__eq__(as_datetime))
3335 different_day = (as_date.day + 1) % 20 + 1
3336 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3337 different_day)))
3338
3339 # And date should compare with other subclasses of date. If a
3340 # subclass wants to stop this, it's up to the subclass to do so.
3341 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3342 self.assertEqual(as_date, date_sc)
3343 self.assertEqual(date_sc, as_date)
3344
3345 # Ditto for datetimes.
3346 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3347 as_date.day, 0, 0, 0)
3348 self.assertEqual(as_datetime, datetime_sc)
3349 self.assertEqual(datetime_sc, as_datetime)
3350
Tim Peters2a799bf2002-12-16 20:18:38 +00003351def test_main():
Collin Winterbec754c2007-04-25 17:37:35 +00003352 test_support.run_unittest(__name__)
Tim Peters2a799bf2002-12-16 20:18:38 +00003353
3354if __name__ == "__main__":
3355 test_main()