blob: 2528b4a0299b8372b35e2bd2ebec7e7e0e393638 [file] [log] [blame]
Tim Peters0bf60bd2003-01-08 20:40:01 +00001"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
Tim Peters2a799bf2002-12-16 20:18:38 +00005
6import sys
Guido van Rossum177e41a2003-01-30 22:06:23 +00007import pickle
8import cPickle
Tim Peters2a799bf2002-12-16 20:18:38 +00009import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
Tim Peters0bf60bd2003-01-08 20:40:01 +000016from datetime import time
17from datetime import date, datetime
18
Tim Peters35ad6412003-02-05 04:08:07 +000019pickle_choices = [(pickler, unpickler, proto)
20 for pickler in pickle, cPickle
21 for unpickler in pickle, cPickle
22 for proto in range(3)]
23assert len(pickle_choices) == 2*2*3
Guido van Rossum177e41a2003-01-30 22:06:23 +000024
Tim Peters68124bb2003-02-08 03:46:31 +000025# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
Tim Peters0bf60bd2003-01-08 20:40:01 +000028
Tim Peters2a799bf2002-12-16 20:18:38 +000029
30#############################################################################
31# module tests
32
33class TestModule(unittest.TestCase):
34
35 def test_constants(self):
36 import datetime
37 self.assertEqual(datetime.MINYEAR, 1)
38 self.assertEqual(datetime.MAXYEAR, 9999)
39
40#############################################################################
41# tzinfo tests
42
43class FixedOffset(tzinfo):
44 def __init__(self, offset, name, dstoffset=42):
Tim Peters397301e2003-01-02 21:28:08 +000045 if isinstance(offset, int):
46 offset = timedelta(minutes=offset)
47 if isinstance(dstoffset, int):
48 dstoffset = timedelta(minutes=dstoffset)
Tim Peters2a799bf2002-12-16 20:18:38 +000049 self.__offset = offset
50 self.__name = name
51 self.__dstoffset = dstoffset
52 def __repr__(self):
53 return self.__name.lower()
54 def utcoffset(self, dt):
55 return self.__offset
56 def tzname(self, dt):
57 return self.__name
58 def dst(self, dt):
59 return self.__dstoffset
60
Tim Petersfb8472c2002-12-21 05:04:42 +000061class PicklableFixedOffset(FixedOffset):
62 def __init__(self, offset=None, name=None, dstoffset=None):
63 FixedOffset.__init__(self, offset, name, dstoffset)
64
Tim Peters2a799bf2002-12-16 20:18:38 +000065class TestTZInfo(unittest.TestCase):
66
67 def test_non_abstractness(self):
68 # In order to allow subclasses to get pickled, the C implementation
69 # wasn't able to get away with having __init__ raise
70 # NotImplementedError.
71 useless = tzinfo()
72 dt = datetime.max
73 self.assertRaises(NotImplementedError, useless.tzname, dt)
74 self.assertRaises(NotImplementedError, useless.utcoffset, dt)
75 self.assertRaises(NotImplementedError, useless.dst, dt)
76
77 def test_subclass_must_override(self):
78 class NotEnough(tzinfo):
79 def __init__(self, offset, name):
80 self.__offset = offset
81 self.__name = name
82 self.failUnless(issubclass(NotEnough, tzinfo))
83 ne = NotEnough(3, "NotByALongShot")
84 self.failUnless(isinstance(ne, tzinfo))
85
86 dt = datetime.now()
87 self.assertRaises(NotImplementedError, ne.tzname, dt)
88 self.assertRaises(NotImplementedError, ne.utcoffset, dt)
89 self.assertRaises(NotImplementedError, ne.dst, dt)
90
91 def test_normal(self):
92 fo = FixedOffset(3, "Three")
93 self.failUnless(isinstance(fo, tzinfo))
94 for dt in datetime.now(), None:
Tim Peters397301e2003-01-02 21:28:08 +000095 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +000096 self.assertEqual(fo.tzname(dt), "Three")
Tim Peters397301e2003-01-02 21:28:08 +000097 self.assertEqual(fo.dst(dt), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +000098
99 def test_pickling_base(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000100 # There's no point to pickling tzinfo objects on their own (they
101 # carry no data), but they need to be picklable anyway else
102 # concrete subclasses can't be pickled.
103 orig = tzinfo.__new__(tzinfo)
104 self.failUnless(type(orig) is tzinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000105 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000106 green = pickler.dumps(orig, proto)
107 derived = unpickler.loads(green)
108 self.failUnless(type(derived) is tzinfo)
Tim Peters2a799bf2002-12-16 20:18:38 +0000109
110 def test_pickling_subclass(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000111 # Make sure we can pickle/unpickle an instance of a subclass.
Tim Peters397301e2003-01-02 21:28:08 +0000112 offset = timedelta(minutes=-300)
113 orig = PicklableFixedOffset(offset, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000114 self.failUnless(isinstance(orig, tzinfo))
Tim Petersfb8472c2002-12-21 05:04:42 +0000115 self.failUnless(type(orig) is PicklableFixedOffset)
Tim Peters397301e2003-01-02 21:28:08 +0000116 self.assertEqual(orig.utcoffset(None), offset)
Tim Peters2a799bf2002-12-16 20:18:38 +0000117 self.assertEqual(orig.tzname(None), 'cookie')
Guido van Rossum177e41a2003-01-30 22:06:23 +0000118 for pickler, unpickler, proto in pickle_choices:
Tim Petersf2715e02003-02-19 02:35:07 +0000119 green = pickler.dumps(orig, proto)
120 derived = unpickler.loads(green)
121 self.failUnless(isinstance(derived, tzinfo))
122 self.failUnless(type(derived) is PicklableFixedOffset)
123 self.assertEqual(derived.utcoffset(None), offset)
124 self.assertEqual(derived.tzname(None), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +0000125
126#############################################################################
Tim Peters07534a62003-02-07 22:50:28 +0000127# Base clase for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison(unittest.TestCase):
131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
132
133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
134 # legit constructor.
135
136 def test_harmless_mixed_comparison(self):
137 me = self.theclass(1, 1, 1)
138
139 self.failIf(me == ())
140 self.failUnless(me != ())
141 self.failIf(() == me)
142 self.failUnless(() != me)
143
144 self.failUnless(me in [1, 20L, [], me])
145 self.failIf(me not in [1, 20L, [], me])
146
147 self.failUnless([] in [me, 1, 20L, []])
148 self.failIf([] not in [me, 1, 20L, []])
149
150 def test_harmful_mixed_comparison(self):
151 me = self.theclass(1, 1, 1)
152
153 self.assertRaises(TypeError, lambda: me < ())
154 self.assertRaises(TypeError, lambda: me <= ())
155 self.assertRaises(TypeError, lambda: me > ())
156 self.assertRaises(TypeError, lambda: me >= ())
157
158 self.assertRaises(TypeError, lambda: () < me)
159 self.assertRaises(TypeError, lambda: () <= me)
160 self.assertRaises(TypeError, lambda: () > me)
161 self.assertRaises(TypeError, lambda: () >= me)
162
163 self.assertRaises(TypeError, cmp, (), me)
164 self.assertRaises(TypeError, cmp, me, ())
165
166#############################################################################
Tim Peters2a799bf2002-12-16 20:18:38 +0000167# timedelta tests
168
Tim Peters07534a62003-02-07 22:50:28 +0000169class TestTimeDelta(HarmlessMixedComparison):
170
171 theclass = timedelta
Tim Peters2a799bf2002-12-16 20:18:38 +0000172
173 def test_constructor(self):
174 eq = self.assertEqual
175 td = timedelta
176
177 # Check keyword args to constructor
178 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
179 milliseconds=0, microseconds=0))
180 eq(td(1), td(days=1))
181 eq(td(0, 1), td(seconds=1))
182 eq(td(0, 0, 1), td(microseconds=1))
183 eq(td(weeks=1), td(days=7))
184 eq(td(days=1), td(hours=24))
185 eq(td(hours=1), td(minutes=60))
186 eq(td(minutes=1), td(seconds=60))
187 eq(td(seconds=1), td(milliseconds=1000))
188 eq(td(milliseconds=1), td(microseconds=1000))
189
190 # Check float args to constructor
191 eq(td(weeks=1.0/7), td(days=1))
192 eq(td(days=1.0/24), td(hours=1))
193 eq(td(hours=1.0/60), td(minutes=1))
194 eq(td(minutes=1.0/60), td(seconds=1))
195 eq(td(seconds=0.001), td(milliseconds=1))
196 eq(td(milliseconds=0.001), td(microseconds=1))
197
198 def test_computations(self):
199 eq = self.assertEqual
200 td = timedelta
201
202 a = td(7) # One week
203 b = td(0, 60) # One minute
204 c = td(0, 0, 1000) # One millisecond
205 eq(a+b+c, td(7, 60, 1000))
206 eq(a-b, td(6, 24*3600 - 60))
207 eq(-a, td(-7))
208 eq(+a, td(7))
209 eq(-b, td(-1, 24*3600 - 60))
210 eq(-c, td(-1, 24*3600 - 1, 999000))
211 eq(abs(a), a)
212 eq(abs(-a), a)
213 eq(td(6, 24*3600), a)
214 eq(td(0, 0, 60*1000000), b)
215 eq(a*10, td(70))
216 eq(a*10, 10*a)
217 eq(a*10L, 10*a)
218 eq(b*10, td(0, 600))
219 eq(10*b, td(0, 600))
220 eq(b*10L, td(0, 600))
221 eq(c*10, td(0, 0, 10000))
222 eq(10*c, td(0, 0, 10000))
223 eq(c*10L, td(0, 0, 10000))
224 eq(a*-1, -a)
225 eq(b*-2, -b-b)
226 eq(c*-2, -c+-c)
227 eq(b*(60*24), (b*60)*24)
228 eq(b*(60*24), (60*b)*24)
229 eq(c*1000, td(0, 1))
230 eq(1000*c, td(0, 1))
231 eq(a//7, td(1))
232 eq(b//10, td(0, 6))
233 eq(c//1000, td(0, 0, 1))
234 eq(a//10, td(0, 7*24*360))
235 eq(a//3600000, td(0, 0, 7*24*1000))
236
237 def test_disallowed_computations(self):
238 a = timedelta(42)
239
240 # Add/sub ints, longs, floats should be illegal
241 for i in 1, 1L, 1.0:
242 self.assertRaises(TypeError, lambda: a+i)
243 self.assertRaises(TypeError, lambda: a-i)
244 self.assertRaises(TypeError, lambda: i+a)
245 self.assertRaises(TypeError, lambda: i-a)
246
247 # Mul/div by float isn't supported.
248 x = 2.3
249 self.assertRaises(TypeError, lambda: a*x)
250 self.assertRaises(TypeError, lambda: x*a)
251 self.assertRaises(TypeError, lambda: a/x)
252 self.assertRaises(TypeError, lambda: x/a)
253 self.assertRaises(TypeError, lambda: a // x)
254 self.assertRaises(TypeError, lambda: x // a)
255
256 # Divison of int by timedelta doesn't make sense.
257 # Division by zero doesn't make sense.
258 for zero in 0, 0L:
259 self.assertRaises(TypeError, lambda: zero // a)
260 self.assertRaises(ZeroDivisionError, lambda: a // zero)
261
262 def test_basic_attributes(self):
263 days, seconds, us = 1, 7, 31
264 td = timedelta(days, seconds, us)
265 self.assertEqual(td.days, days)
266 self.assertEqual(td.seconds, seconds)
267 self.assertEqual(td.microseconds, us)
268
269 def test_carries(self):
270 t1 = timedelta(days=100,
271 weeks=-7,
272 hours=-24*(100-49),
273 minutes=-3,
274 seconds=12,
275 microseconds=(3*60 - 12) * 1e6 + 1)
276 t2 = timedelta(microseconds=1)
277 self.assertEqual(t1, t2)
278
279 def test_hash_equality(self):
280 t1 = timedelta(days=100,
281 weeks=-7,
282 hours=-24*(100-49),
283 minutes=-3,
284 seconds=12,
285 microseconds=(3*60 - 12) * 1000000)
286 t2 = timedelta()
287 self.assertEqual(hash(t1), hash(t2))
288
289 t1 += timedelta(weeks=7)
290 t2 += timedelta(days=7*7)
291 self.assertEqual(t1, t2)
292 self.assertEqual(hash(t1), hash(t2))
293
294 d = {t1: 1}
295 d[t2] = 2
296 self.assertEqual(len(d), 1)
297 self.assertEqual(d[t1], 2)
298
299 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000300 args = 12, 34, 56
301 orig = timedelta(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000302 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000303 green = pickler.dumps(orig, proto)
304 derived = unpickler.loads(green)
305 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000306
307 def test_compare(self):
308 t1 = timedelta(2, 3, 4)
309 t2 = timedelta(2, 3, 4)
310 self.failUnless(t1 == t2)
311 self.failUnless(t1 <= t2)
312 self.failUnless(t1 >= t2)
313 self.failUnless(not t1 != t2)
314 self.failUnless(not t1 < t2)
315 self.failUnless(not t1 > t2)
316 self.assertEqual(cmp(t1, t2), 0)
317 self.assertEqual(cmp(t2, t1), 0)
318
319 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
320 t2 = timedelta(*args) # this is larger than t1
321 self.failUnless(t1 < t2)
322 self.failUnless(t2 > t1)
323 self.failUnless(t1 <= t2)
324 self.failUnless(t2 >= t1)
325 self.failUnless(t1 != t2)
326 self.failUnless(t2 != t1)
327 self.failUnless(not t1 == t2)
328 self.failUnless(not t2 == t1)
329 self.failUnless(not t1 > t2)
330 self.failUnless(not t2 < t1)
331 self.failUnless(not t1 >= t2)
332 self.failUnless(not t2 <= t1)
333 self.assertEqual(cmp(t1, t2), -1)
334 self.assertEqual(cmp(t2, t1), 1)
335
Tim Peters68124bb2003-02-08 03:46:31 +0000336 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000337 self.assertEqual(t1 == badarg, False)
338 self.assertEqual(t1 != badarg, True)
339 self.assertEqual(badarg == t1, False)
340 self.assertEqual(badarg != t1, True)
341
Tim Peters2a799bf2002-12-16 20:18:38 +0000342 self.assertRaises(TypeError, lambda: t1 <= badarg)
343 self.assertRaises(TypeError, lambda: t1 < badarg)
344 self.assertRaises(TypeError, lambda: t1 > badarg)
345 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000346 self.assertRaises(TypeError, lambda: badarg <= t1)
347 self.assertRaises(TypeError, lambda: badarg < t1)
348 self.assertRaises(TypeError, lambda: badarg > t1)
349 self.assertRaises(TypeError, lambda: badarg >= t1)
350
351 def test_str(self):
352 td = timedelta
353 eq = self.assertEqual
354
355 eq(str(td(1)), "1 day, 0:00:00")
356 eq(str(td(-1)), "-1 day, 0:00:00")
357 eq(str(td(2)), "2 days, 0:00:00")
358 eq(str(td(-2)), "-2 days, 0:00:00")
359
360 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
361 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
362 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
363 "-210 days, 23:12:34")
364
365 eq(str(td(milliseconds=1)), "0:00:00.001000")
366 eq(str(td(microseconds=3)), "0:00:00.000003")
367
368 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
369 microseconds=999999)),
370 "999999999 days, 23:59:59.999999")
371
372 def test_roundtrip(self):
373 for td in (timedelta(days=999999999, hours=23, minutes=59,
374 seconds=59, microseconds=999999),
375 timedelta(days=-999999999),
376 timedelta(days=1, seconds=2, microseconds=3)):
377
378 # Verify td -> string -> td identity.
379 s = repr(td)
380 self.failUnless(s.startswith('datetime.'))
381 s = s[9:]
382 td2 = eval(s)
383 self.assertEqual(td, td2)
384
385 # Verify identity via reconstructing from pieces.
386 td2 = timedelta(td.days, td.seconds, td.microseconds)
387 self.assertEqual(td, td2)
388
389 def test_resolution_info(self):
390 self.assert_(isinstance(timedelta.min, timedelta))
391 self.assert_(isinstance(timedelta.max, timedelta))
392 self.assert_(isinstance(timedelta.resolution, timedelta))
393 self.assert_(timedelta.max > timedelta.min)
394 self.assertEqual(timedelta.min, timedelta(-999999999))
395 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
396 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
397
398 def test_overflow(self):
399 tiny = timedelta.resolution
400
401 td = timedelta.min + tiny
402 td -= tiny # no problem
403 self.assertRaises(OverflowError, td.__sub__, tiny)
404 self.assertRaises(OverflowError, td.__add__, -tiny)
405
406 td = timedelta.max - tiny
407 td += tiny # no problem
408 self.assertRaises(OverflowError, td.__add__, tiny)
409 self.assertRaises(OverflowError, td.__sub__, -tiny)
410
411 self.assertRaises(OverflowError, lambda: -timedelta.max)
412
413 def test_microsecond_rounding(self):
414 td = timedelta
415 eq = self.assertEqual
416
417 # Single-field rounding.
418 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0
419 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0
420 eq(td(milliseconds=0.6/1000), td(microseconds=1))
421 eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
422
423 # Rounding due to contributions from more than one field.
424 us_per_hour = 3600e6
425 us_per_day = us_per_hour * 24
426 eq(td(days=.4/us_per_day), td(0))
427 eq(td(hours=.2/us_per_hour), td(0))
428 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
429
430 eq(td(days=-.4/us_per_day), td(0))
431 eq(td(hours=-.2/us_per_hour), td(0))
432 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
433
434 def test_massive_normalization(self):
435 td = timedelta(microseconds=-1)
436 self.assertEqual((td.days, td.seconds, td.microseconds),
437 (-1, 24*3600-1, 999999))
438
439 def test_bool(self):
440 self.failUnless(timedelta(1))
441 self.failUnless(timedelta(0, 1))
442 self.failUnless(timedelta(0, 0, 1))
443 self.failUnless(timedelta(microseconds=1))
444 self.failUnless(not timedelta(0))
445
Tim Petersb0c854d2003-05-17 15:57:00 +0000446 def test_subclass_timedelta(self):
447
448 class T(timedelta):
Guido van Rossum5a8a0372005-01-16 00:25:31 +0000449 @staticmethod
Tim Petersb0c854d2003-05-17 15:57:00 +0000450 def from_td(td):
451 return T(td.days, td.seconds, td.microseconds)
Tim Petersb0c854d2003-05-17 15:57:00 +0000452
453 def as_hours(self):
454 sum = (self.days * 24 +
455 self.seconds / 3600.0 +
456 self.microseconds / 3600e6)
457 return round(sum)
458
459 t1 = T(days=1)
460 self.assert_(type(t1) is T)
461 self.assertEqual(t1.as_hours(), 24)
462
463 t2 = T(days=-1, seconds=-3600)
464 self.assert_(type(t2) is T)
465 self.assertEqual(t2.as_hours(), -25)
466
467 t3 = t1 + t2
468 self.assert_(type(t3) is timedelta)
469 t4 = T.from_td(t3)
470 self.assert_(type(t4) is T)
471 self.assertEqual(t3.days, t4.days)
472 self.assertEqual(t3.seconds, t4.seconds)
473 self.assertEqual(t3.microseconds, t4.microseconds)
474 self.assertEqual(str(t3), str(t4))
475 self.assertEqual(t4.as_hours(), -1)
476
Tim Peters2a799bf2002-12-16 20:18:38 +0000477#############################################################################
478# date tests
479
480class TestDateOnly(unittest.TestCase):
481 # Tests here won't pass if also run on datetime objects, so don't
482 # subclass this to test datetimes too.
483
484 def test_delta_non_days_ignored(self):
485 dt = date(2000, 1, 2)
486 delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
487 microseconds=5)
488 days = timedelta(delta.days)
489 self.assertEqual(days, timedelta(1))
490
491 dt2 = dt + delta
492 self.assertEqual(dt2, dt + days)
493
494 dt2 = delta + dt
495 self.assertEqual(dt2, dt + days)
496
497 dt2 = dt - delta
498 self.assertEqual(dt2, dt - days)
499
500 delta = -delta
501 days = timedelta(delta.days)
502 self.assertEqual(days, timedelta(-2))
503
504 dt2 = dt + delta
505 self.assertEqual(dt2, dt + days)
506
507 dt2 = delta + dt
508 self.assertEqual(dt2, dt + days)
509
510 dt2 = dt - delta
511 self.assertEqual(dt2, dt - days)
512
Tim Peters604c0132004-06-07 23:04:33 +0000513class SubclassDate(date):
514 sub_var = 1
515
Tim Peters07534a62003-02-07 22:50:28 +0000516class TestDate(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +0000517 # Tests here should pass for both dates and datetimes, except for a
518 # few tests that TestDateTime overrides.
519
520 theclass = date
521
522 def test_basic_attributes(self):
523 dt = self.theclass(2002, 3, 1)
524 self.assertEqual(dt.year, 2002)
525 self.assertEqual(dt.month, 3)
526 self.assertEqual(dt.day, 1)
527
528 def test_roundtrip(self):
529 for dt in (self.theclass(1, 2, 3),
530 self.theclass.today()):
531 # Verify dt -> string -> date identity.
532 s = repr(dt)
533 self.failUnless(s.startswith('datetime.'))
534 s = s[9:]
535 dt2 = eval(s)
536 self.assertEqual(dt, dt2)
537
538 # Verify identity via reconstructing from pieces.
539 dt2 = self.theclass(dt.year, dt.month, dt.day)
540 self.assertEqual(dt, dt2)
541
542 def test_ordinal_conversions(self):
543 # Check some fixed values.
544 for y, m, d, n in [(1, 1, 1, 1), # calendar origin
545 (1, 12, 31, 365),
546 (2, 1, 1, 366),
547 # first example from "Calendrical Calculations"
548 (1945, 11, 12, 710347)]:
549 d = self.theclass(y, m, d)
550 self.assertEqual(n, d.toordinal())
551 fromord = self.theclass.fromordinal(n)
552 self.assertEqual(d, fromord)
553 if hasattr(fromord, "hour"):
Tim Petersf2715e02003-02-19 02:35:07 +0000554 # if we're checking something fancier than a date, verify
555 # the extra fields have been zeroed out
Tim Peters2a799bf2002-12-16 20:18:38 +0000556 self.assertEqual(fromord.hour, 0)
557 self.assertEqual(fromord.minute, 0)
558 self.assertEqual(fromord.second, 0)
559 self.assertEqual(fromord.microsecond, 0)
560
Tim Peters0bf60bd2003-01-08 20:40:01 +0000561 # Check first and last days of year spottily across the whole
562 # range of years supported.
563 for year in xrange(MINYEAR, MAXYEAR+1, 7):
Tim Peters2a799bf2002-12-16 20:18:38 +0000564 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
565 d = self.theclass(year, 1, 1)
566 n = d.toordinal()
567 d2 = self.theclass.fromordinal(n)
568 self.assertEqual(d, d2)
Tim Peters0bf60bd2003-01-08 20:40:01 +0000569 # Verify that moving back a day gets to the end of year-1.
570 if year > 1:
571 d = self.theclass.fromordinal(n-1)
572 d2 = self.theclass(year-1, 12, 31)
573 self.assertEqual(d, d2)
574 self.assertEqual(d2.toordinal(), n-1)
Tim Peters2a799bf2002-12-16 20:18:38 +0000575
576 # Test every day in a leap-year and a non-leap year.
577 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
578 for year, isleap in (2000, True), (2002, False):
579 n = self.theclass(year, 1, 1).toordinal()
580 for month, maxday in zip(range(1, 13), dim):
581 if month == 2 and isleap:
582 maxday += 1
583 for day in range(1, maxday+1):
584 d = self.theclass(year, month, day)
585 self.assertEqual(d.toordinal(), n)
586 self.assertEqual(d, self.theclass.fromordinal(n))
587 n += 1
588
589 def test_extreme_ordinals(self):
590 a = self.theclass.min
591 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
592 aord = a.toordinal()
593 b = a.fromordinal(aord)
594 self.assertEqual(a, b)
595
596 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
597
598 b = a + timedelta(days=1)
599 self.assertEqual(b.toordinal(), aord + 1)
600 self.assertEqual(b, self.theclass.fromordinal(aord + 1))
601
602 a = self.theclass.max
603 a = self.theclass(a.year, a.month, a.day) # get rid of time parts
604 aord = a.toordinal()
605 b = a.fromordinal(aord)
606 self.assertEqual(a, b)
607
608 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
609
610 b = a - timedelta(days=1)
611 self.assertEqual(b.toordinal(), aord - 1)
612 self.assertEqual(b, self.theclass.fromordinal(aord - 1))
613
614 def test_bad_constructor_arguments(self):
615 # bad years
616 self.theclass(MINYEAR, 1, 1) # no exception
617 self.theclass(MAXYEAR, 1, 1) # no exception
618 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
619 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
620 # bad months
621 self.theclass(2000, 1, 1) # no exception
622 self.theclass(2000, 12, 1) # no exception
623 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
624 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
625 # bad days
626 self.theclass(2000, 2, 29) # no exception
627 self.theclass(2004, 2, 29) # no exception
628 self.theclass(2400, 2, 29) # no exception
629 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
630 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
631 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
632 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
633 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
634 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
635
636 def test_hash_equality(self):
637 d = self.theclass(2000, 12, 31)
638 # same thing
639 e = self.theclass(2000, 12, 31)
640 self.assertEqual(d, e)
641 self.assertEqual(hash(d), hash(e))
642
643 dic = {d: 1}
644 dic[e] = 2
645 self.assertEqual(len(dic), 1)
646 self.assertEqual(dic[d], 2)
647 self.assertEqual(dic[e], 2)
648
649 d = self.theclass(2001, 1, 1)
650 # same thing
651 e = self.theclass(2001, 1, 1)
652 self.assertEqual(d, e)
653 self.assertEqual(hash(d), hash(e))
654
655 dic = {d: 1}
656 dic[e] = 2
657 self.assertEqual(len(dic), 1)
658 self.assertEqual(dic[d], 2)
659 self.assertEqual(dic[e], 2)
660
661 def test_computations(self):
662 a = self.theclass(2002, 1, 31)
663 b = self.theclass(1956, 1, 31)
664
665 diff = a-b
666 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
667 self.assertEqual(diff.seconds, 0)
668 self.assertEqual(diff.microseconds, 0)
669
670 day = timedelta(1)
671 week = timedelta(7)
672 a = self.theclass(2002, 3, 2)
673 self.assertEqual(a + day, self.theclass(2002, 3, 3))
674 self.assertEqual(day + a, self.theclass(2002, 3, 3))
675 self.assertEqual(a - day, self.theclass(2002, 3, 1))
676 self.assertEqual(-day + a, self.theclass(2002, 3, 1))
677 self.assertEqual(a + week, self.theclass(2002, 3, 9))
678 self.assertEqual(a - week, self.theclass(2002, 2, 23))
679 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
680 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
681 self.assertEqual((a + week) - a, week)
682 self.assertEqual((a + day) - a, day)
683 self.assertEqual((a - week) - a, -week)
684 self.assertEqual((a - day) - a, -day)
685 self.assertEqual(a - (a + week), -week)
686 self.assertEqual(a - (a + day), -day)
687 self.assertEqual(a - (a - week), week)
688 self.assertEqual(a - (a - day), day)
689
690 # Add/sub ints, longs, floats should be illegal
691 for i in 1, 1L, 1.0:
692 self.assertRaises(TypeError, lambda: a+i)
693 self.assertRaises(TypeError, lambda: a-i)
694 self.assertRaises(TypeError, lambda: i+a)
695 self.assertRaises(TypeError, lambda: i-a)
696
697 # delta - date is senseless.
698 self.assertRaises(TypeError, lambda: day - a)
699 # mixing date and (delta or date) via * or // is senseless
700 self.assertRaises(TypeError, lambda: day * a)
701 self.assertRaises(TypeError, lambda: a * day)
702 self.assertRaises(TypeError, lambda: day // a)
703 self.assertRaises(TypeError, lambda: a // day)
704 self.assertRaises(TypeError, lambda: a * a)
705 self.assertRaises(TypeError, lambda: a // a)
706 # date + date is senseless
707 self.assertRaises(TypeError, lambda: a + a)
708
709 def test_overflow(self):
710 tiny = self.theclass.resolution
711
712 dt = self.theclass.min + tiny
713 dt -= tiny # no problem
714 self.assertRaises(OverflowError, dt.__sub__, tiny)
715 self.assertRaises(OverflowError, dt.__add__, -tiny)
716
717 dt = self.theclass.max - tiny
718 dt += tiny # no problem
719 self.assertRaises(OverflowError, dt.__add__, tiny)
720 self.assertRaises(OverflowError, dt.__sub__, -tiny)
721
722 def test_fromtimestamp(self):
723 import time
724
725 # Try an arbitrary fixed value.
726 year, month, day = 1999, 9, 19
727 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
728 d = self.theclass.fromtimestamp(ts)
729 self.assertEqual(d.year, year)
730 self.assertEqual(d.month, month)
731 self.assertEqual(d.day, day)
732
Tim Peters1b6f7a92004-06-20 02:50:16 +0000733 def test_insane_fromtimestamp(self):
734 # It's possible that some platform maps time_t to double,
735 # and that this test will fail there. This test should
736 # exempt such platforms (provided they return reasonable
737 # results!).
738 for insane in -1e200, 1e200:
739 self.assertRaises(ValueError, self.theclass.fromtimestamp,
740 insane)
741
Tim Peters2a799bf2002-12-16 20:18:38 +0000742 def test_today(self):
743 import time
744
745 # We claim that today() is like fromtimestamp(time.time()), so
746 # prove it.
747 for dummy in range(3):
748 today = self.theclass.today()
749 ts = time.time()
750 todayagain = self.theclass.fromtimestamp(ts)
751 if today == todayagain:
752 break
753 # There are several legit reasons that could fail:
754 # 1. It recently became midnight, between the today() and the
755 # time() calls.
756 # 2. The platform time() has such fine resolution that we'll
757 # never get the same value twice.
758 # 3. The platform time() has poor resolution, and we just
759 # happened to call today() right before a resolution quantum
760 # boundary.
761 # 4. The system clock got fiddled between calls.
762 # In any case, wait a little while and try again.
763 time.sleep(0.1)
764
765 # It worked or it didn't. If it didn't, assume it's reason #2, and
766 # let the test pass if they're within half a second of each other.
767 self.failUnless(today == todayagain or
768 abs(todayagain - today) < timedelta(seconds=0.5))
769
770 def test_weekday(self):
771 for i in range(7):
772 # March 4, 2002 is a Monday
773 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
774 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
775 # January 2, 1956 is a Monday
776 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
777 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
778
779 def test_isocalendar(self):
780 # Check examples from
781 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
782 for i in range(7):
783 d = self.theclass(2003, 12, 22+i)
784 self.assertEqual(d.isocalendar(), (2003, 52, i+1))
785 d = self.theclass(2003, 12, 29) + timedelta(i)
786 self.assertEqual(d.isocalendar(), (2004, 1, i+1))
787 d = self.theclass(2004, 1, 5+i)
788 self.assertEqual(d.isocalendar(), (2004, 2, i+1))
789 d = self.theclass(2009, 12, 21+i)
790 self.assertEqual(d.isocalendar(), (2009, 52, i+1))
791 d = self.theclass(2009, 12, 28) + timedelta(i)
792 self.assertEqual(d.isocalendar(), (2009, 53, i+1))
793 d = self.theclass(2010, 1, 4+i)
794 self.assertEqual(d.isocalendar(), (2010, 1, i+1))
795
796 def test_iso_long_years(self):
797 # Calculate long ISO years and compare to table from
798 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
799 ISO_LONG_YEARS_TABLE = """
800 4 32 60 88
801 9 37 65 93
802 15 43 71 99
803 20 48 76
804 26 54 82
805
806 105 133 161 189
807 111 139 167 195
808 116 144 172
809 122 150 178
810 128 156 184
811
812 201 229 257 285
813 207 235 263 291
814 212 240 268 296
815 218 246 274
816 224 252 280
817
818 303 331 359 387
819 308 336 364 392
820 314 342 370 398
821 320 348 376
822 325 353 381
823 """
824 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
825 iso_long_years.sort()
826 L = []
827 for i in range(400):
828 d = self.theclass(2000+i, 12, 31)
829 d1 = self.theclass(1600+i, 12, 31)
830 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
831 if d.isocalendar()[1] == 53:
832 L.append(i)
833 self.assertEqual(L, iso_long_years)
834
835 def test_isoformat(self):
836 t = self.theclass(2, 3, 2)
837 self.assertEqual(t.isoformat(), "0002-03-02")
838
839 def test_ctime(self):
840 t = self.theclass(2002, 3, 2)
841 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002")
842
843 def test_strftime(self):
844 t = self.theclass(2005, 3, 2)
845 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
Raymond Hettingerf69d9f62003-06-27 08:14:17 +0000846 self.assertEqual(t.strftime(""), "") # SF bug #761337
Tim Peters2a799bf2002-12-16 20:18:38 +0000847
848 self.assertRaises(TypeError, t.strftime) # needs an arg
849 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
850 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
851
852 # A naive object replaces %z and %Z w/ empty strings.
853 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
854
855 def test_resolution_info(self):
856 self.assert_(isinstance(self.theclass.min, self.theclass))
857 self.assert_(isinstance(self.theclass.max, self.theclass))
858 self.assert_(isinstance(self.theclass.resolution, timedelta))
859 self.assert_(self.theclass.max > self.theclass.min)
860
861 def test_extreme_timedelta(self):
862 big = self.theclass.max - self.theclass.min
863 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
864 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
865 # n == 315537897599999999 ~= 2**58.13
866 justasbig = timedelta(0, 0, n)
867 self.assertEqual(big, justasbig)
868 self.assertEqual(self.theclass.min + big, self.theclass.max)
869 self.assertEqual(self.theclass.max - big, self.theclass.min)
870
871 def test_timetuple(self):
872 for i in range(7):
873 # January 2, 1956 is a Monday (0)
874 d = self.theclass(1956, 1, 2+i)
875 t = d.timetuple()
876 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
877 # February 1, 1956 is a Wednesday (2)
878 d = self.theclass(1956, 2, 1+i)
879 t = d.timetuple()
880 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
881 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
882 # of the year.
883 d = self.theclass(1956, 3, 1+i)
884 t = d.timetuple()
885 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
886 self.assertEqual(t.tm_year, 1956)
887 self.assertEqual(t.tm_mon, 3)
888 self.assertEqual(t.tm_mday, 1+i)
889 self.assertEqual(t.tm_hour, 0)
890 self.assertEqual(t.tm_min, 0)
891 self.assertEqual(t.tm_sec, 0)
892 self.assertEqual(t.tm_wday, (3+i)%7)
893 self.assertEqual(t.tm_yday, 61+i)
894 self.assertEqual(t.tm_isdst, -1)
895
896 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000897 args = 6, 7, 23
898 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000899 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000900 green = pickler.dumps(orig, proto)
901 derived = unpickler.loads(green)
902 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000903
904 def test_compare(self):
905 t1 = self.theclass(2, 3, 4)
906 t2 = self.theclass(2, 3, 4)
907 self.failUnless(t1 == t2)
908 self.failUnless(t1 <= t2)
909 self.failUnless(t1 >= t2)
910 self.failUnless(not t1 != t2)
911 self.failUnless(not t1 < t2)
912 self.failUnless(not t1 > t2)
913 self.assertEqual(cmp(t1, t2), 0)
914 self.assertEqual(cmp(t2, t1), 0)
915
916 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
917 t2 = self.theclass(*args) # this is larger than t1
918 self.failUnless(t1 < t2)
919 self.failUnless(t2 > t1)
920 self.failUnless(t1 <= t2)
921 self.failUnless(t2 >= t1)
922 self.failUnless(t1 != t2)
923 self.failUnless(t2 != t1)
924 self.failUnless(not t1 == t2)
925 self.failUnless(not t2 == t1)
926 self.failUnless(not t1 > t2)
927 self.failUnless(not t2 < t1)
928 self.failUnless(not t1 >= t2)
929 self.failUnless(not t2 <= t1)
930 self.assertEqual(cmp(t1, t2), -1)
931 self.assertEqual(cmp(t2, t1), 1)
932
Tim Peters68124bb2003-02-08 03:46:31 +0000933 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000934 self.assertEqual(t1 == badarg, False)
935 self.assertEqual(t1 != badarg, True)
936 self.assertEqual(badarg == t1, False)
937 self.assertEqual(badarg != t1, True)
938
Tim Peters2a799bf2002-12-16 20:18:38 +0000939 self.assertRaises(TypeError, lambda: t1 < badarg)
940 self.assertRaises(TypeError, lambda: t1 > badarg)
941 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000942 self.assertRaises(TypeError, lambda: badarg <= t1)
943 self.assertRaises(TypeError, lambda: badarg < t1)
944 self.assertRaises(TypeError, lambda: badarg > t1)
945 self.assertRaises(TypeError, lambda: badarg >= t1)
946
Tim Peters8d81a012003-01-24 22:36:34 +0000947 def test_mixed_compare(self):
948 our = self.theclass(2000, 4, 5)
949 self.assertRaises(TypeError, cmp, our, 1)
950 self.assertRaises(TypeError, cmp, 1, our)
951
952 class AnotherDateTimeClass(object):
953 def __cmp__(self, other):
954 # Return "equal" so calling this can't be confused with
955 # compare-by-address (which never says "equal" for distinct
956 # objects).
957 return 0
958
959 # This still errors, because date and datetime comparison raise
960 # TypeError instead of NotImplemented when they don't know what to
961 # do, in order to stop comparison from falling back to the default
962 # compare-by-address.
963 their = AnotherDateTimeClass()
964 self.assertRaises(TypeError, cmp, our, their)
965 # Oops: The next stab raises TypeError in the C implementation,
966 # but not in the Python implementation of datetime. The difference
967 # is due to that the Python implementation defines __cmp__ but
968 # the C implementation defines tp_richcompare. This is more pain
969 # to fix than it's worth, so commenting out the test.
970 # self.assertEqual(cmp(their, our), 0)
971
972 # But date and datetime comparison return NotImplemented instead if the
973 # other object has a timetuple attr. This gives the other object a
974 # chance to do the comparison.
975 class Comparable(AnotherDateTimeClass):
976 def timetuple(self):
977 return ()
978
979 their = Comparable()
980 self.assertEqual(cmp(our, their), 0)
981 self.assertEqual(cmp(their, our), 0)
982 self.failUnless(our == their)
983 self.failUnless(their == our)
984
Tim Peters2a799bf2002-12-16 20:18:38 +0000985 def test_bool(self):
986 # All dates are considered true.
987 self.failUnless(self.theclass.min)
988 self.failUnless(self.theclass.max)
989
Tim Petersd6844152002-12-22 20:58:42 +0000990 def test_srftime_out_of_range(self):
991 # For nasty technical reasons, we can't handle years before 1900.
992 cls = self.theclass
993 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
994 for y in 1, 49, 51, 99, 100, 1000, 1899:
995 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000996
997 def test_replace(self):
998 cls = self.theclass
999 args = [1, 2, 3]
1000 base = cls(*args)
1001 self.assertEqual(base, base.replace())
1002
1003 i = 0
1004 for name, newval in (("year", 2),
1005 ("month", 3),
1006 ("day", 4)):
1007 newargs = args[:]
1008 newargs[i] = newval
1009 expected = cls(*newargs)
1010 got = base.replace(**{name: newval})
1011 self.assertEqual(expected, got)
1012 i += 1
1013
1014 # Out of bounds.
1015 base = cls(2000, 2, 29)
1016 self.assertRaises(ValueError, base.replace, year=2001)
1017
Tim Petersa98924a2003-05-17 05:55:19 +00001018 def test_subclass_date(self):
1019
1020 class C(self.theclass):
1021 theAnswer = 42
1022
1023 def __new__(cls, *args, **kws):
1024 temp = kws.copy()
1025 extra = temp.pop('extra')
1026 result = self.theclass.__new__(cls, *args, **temp)
1027 result.extra = extra
1028 return result
1029
1030 def newmeth(self, start):
1031 return start + self.year + self.month
1032
1033 args = 2003, 4, 14
1034
1035 dt1 = self.theclass(*args)
1036 dt2 = C(*args, **{'extra': 7})
1037
1038 self.assertEqual(dt2.__class__, C)
1039 self.assertEqual(dt2.theAnswer, 42)
1040 self.assertEqual(dt2.extra, 7)
1041 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1042 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1043
Tim Peters604c0132004-06-07 23:04:33 +00001044 def test_pickling_subclass_date(self):
1045
1046 args = 6, 7, 23
1047 orig = SubclassDate(*args)
1048 for pickler, unpickler, proto in pickle_choices:
1049 green = pickler.dumps(orig, proto)
1050 derived = unpickler.loads(green)
1051 self.assertEqual(orig, derived)
1052
Tim Peters3f606292004-03-21 23:38:41 +00001053 def test_backdoor_resistance(self):
1054 # For fast unpickling, the constructor accepts a pickle string.
1055 # This is a low-overhead backdoor. A user can (by intent or
1056 # mistake) pass a string directly, which (if it's the right length)
1057 # will get treated like a pickle, and bypass the normal sanity
1058 # checks in the constructor. This can create insane objects.
1059 # The constructor doesn't want to burn the time to validate all
1060 # fields, but does check the month field. This stops, e.g.,
1061 # datetime.datetime('1995-03-25') from yielding an insane object.
1062 base = '1995-03-25'
1063 if not issubclass(self.theclass, datetime):
1064 base = base[:4]
1065 for month_byte in '9', chr(0), chr(13), '\xff':
1066 self.assertRaises(TypeError, self.theclass,
1067 base[:2] + month_byte + base[3:])
1068 for ord_byte in range(1, 13):
1069 # This shouldn't blow up because of the month byte alone. If
1070 # the implementation changes to do more-careful checking, it may
1071 # blow up because other fields are insane.
1072 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001073
Tim Peters2a799bf2002-12-16 20:18:38 +00001074#############################################################################
1075# datetime tests
1076
Tim Peters604c0132004-06-07 23:04:33 +00001077class SubclassDatetime(datetime):
1078 sub_var = 1
1079
Tim Peters2a799bf2002-12-16 20:18:38 +00001080class TestDateTime(TestDate):
1081
1082 theclass = datetime
1083
1084 def test_basic_attributes(self):
1085 dt = self.theclass(2002, 3, 1, 12, 0)
1086 self.assertEqual(dt.year, 2002)
1087 self.assertEqual(dt.month, 3)
1088 self.assertEqual(dt.day, 1)
1089 self.assertEqual(dt.hour, 12)
1090 self.assertEqual(dt.minute, 0)
1091 self.assertEqual(dt.second, 0)
1092 self.assertEqual(dt.microsecond, 0)
1093
1094 def test_basic_attributes_nonzero(self):
1095 # Make sure all attributes are non-zero so bugs in
1096 # bit-shifting access show up.
1097 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1098 self.assertEqual(dt.year, 2002)
1099 self.assertEqual(dt.month, 3)
1100 self.assertEqual(dt.day, 1)
1101 self.assertEqual(dt.hour, 12)
1102 self.assertEqual(dt.minute, 59)
1103 self.assertEqual(dt.second, 59)
1104 self.assertEqual(dt.microsecond, 8000)
1105
1106 def test_roundtrip(self):
1107 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1108 self.theclass.now()):
1109 # Verify dt -> string -> datetime identity.
1110 s = repr(dt)
1111 self.failUnless(s.startswith('datetime.'))
1112 s = s[9:]
1113 dt2 = eval(s)
1114 self.assertEqual(dt, dt2)
1115
1116 # Verify identity via reconstructing from pieces.
1117 dt2 = self.theclass(dt.year, dt.month, dt.day,
1118 dt.hour, dt.minute, dt.second,
1119 dt.microsecond)
1120 self.assertEqual(dt, dt2)
1121
1122 def test_isoformat(self):
1123 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1124 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1125 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1126 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1127 # str is ISO format with the separator forced to a blank.
1128 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1129
1130 t = self.theclass(2, 3, 2)
1131 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1132 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1133 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1134 # str is ISO format with the separator forced to a blank.
1135 self.assertEqual(str(t), "0002-03-02 00:00:00")
1136
1137 def test_more_ctime(self):
1138 # Test fields that TestDate doesn't touch.
1139 import time
1140
1141 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1142 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1143 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1144 # out. The difference is that t.ctime() produces " 2" for the day,
1145 # but platform ctime() produces "02" for the day. According to
1146 # C99, t.ctime() is correct here.
1147 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1148
1149 # So test a case where that difference doesn't matter.
1150 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1151 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1152
1153 def test_tz_independent_comparing(self):
1154 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1155 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1156 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1157 self.assertEqual(dt1, dt3)
1158 self.assert_(dt2 > dt3)
1159
1160 # Make sure comparison doesn't forget microseconds, and isn't done
1161 # via comparing a float timestamp (an IEEE double doesn't have enough
1162 # precision to span microsecond resolution across years 1 thru 9999,
1163 # so comparing via timestamp necessarily calls some distinct values
1164 # equal).
1165 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1166 us = timedelta(microseconds=1)
1167 dt2 = dt1 + us
1168 self.assertEqual(dt2 - dt1, us)
1169 self.assert_(dt1 < dt2)
1170
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001171 def test_strftime_with_bad_tzname_replace(self):
1172 # verify ok if tzinfo.tzname().replace() returns a non-string
1173 class MyTzInfo(FixedOffset):
1174 def tzname(self, dt):
1175 class MyStr(str):
1176 def replace(self, *args):
1177 return None
1178 return MyStr('name')
1179 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1180 self.assertRaises(TypeError, t.strftime, '%Z')
1181
Tim Peters2a799bf2002-12-16 20:18:38 +00001182 def test_bad_constructor_arguments(self):
1183 # bad years
1184 self.theclass(MINYEAR, 1, 1) # no exception
1185 self.theclass(MAXYEAR, 1, 1) # no exception
1186 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1187 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1188 # bad months
1189 self.theclass(2000, 1, 1) # no exception
1190 self.theclass(2000, 12, 1) # no exception
1191 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1192 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1193 # bad days
1194 self.theclass(2000, 2, 29) # no exception
1195 self.theclass(2004, 2, 29) # no exception
1196 self.theclass(2400, 2, 29) # no exception
1197 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1198 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1199 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1200 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1201 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1202 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1203 # bad hours
1204 self.theclass(2000, 1, 31, 0) # no exception
1205 self.theclass(2000, 1, 31, 23) # no exception
1206 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1207 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1208 # bad minutes
1209 self.theclass(2000, 1, 31, 23, 0) # no exception
1210 self.theclass(2000, 1, 31, 23, 59) # no exception
1211 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1212 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1213 # bad seconds
1214 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1215 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1216 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1217 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1218 # bad microseconds
1219 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1220 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1221 self.assertRaises(ValueError, self.theclass,
1222 2000, 1, 31, 23, 59, 59, -1)
1223 self.assertRaises(ValueError, self.theclass,
1224 2000, 1, 31, 23, 59, 59,
1225 1000000)
1226
1227 def test_hash_equality(self):
1228 d = self.theclass(2000, 12, 31, 23, 30, 17)
1229 e = self.theclass(2000, 12, 31, 23, 30, 17)
1230 self.assertEqual(d, e)
1231 self.assertEqual(hash(d), hash(e))
1232
1233 dic = {d: 1}
1234 dic[e] = 2
1235 self.assertEqual(len(dic), 1)
1236 self.assertEqual(dic[d], 2)
1237 self.assertEqual(dic[e], 2)
1238
1239 d = self.theclass(2001, 1, 1, 0, 5, 17)
1240 e = self.theclass(2001, 1, 1, 0, 5, 17)
1241 self.assertEqual(d, e)
1242 self.assertEqual(hash(d), hash(e))
1243
1244 dic = {d: 1}
1245 dic[e] = 2
1246 self.assertEqual(len(dic), 1)
1247 self.assertEqual(dic[d], 2)
1248 self.assertEqual(dic[e], 2)
1249
1250 def test_computations(self):
1251 a = self.theclass(2002, 1, 31)
1252 b = self.theclass(1956, 1, 31)
1253 diff = a-b
1254 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1255 self.assertEqual(diff.seconds, 0)
1256 self.assertEqual(diff.microseconds, 0)
1257 a = self.theclass(2002, 3, 2, 17, 6)
1258 millisec = timedelta(0, 0, 1000)
1259 hour = timedelta(0, 3600)
1260 day = timedelta(1)
1261 week = timedelta(7)
1262 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1263 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1264 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1265 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1266 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1267 self.assertEqual(a - hour, a + -hour)
1268 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1269 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1270 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1271 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1272 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1273 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1274 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1275 self.assertEqual((a + week) - a, week)
1276 self.assertEqual((a + day) - a, day)
1277 self.assertEqual((a + hour) - a, hour)
1278 self.assertEqual((a + millisec) - a, millisec)
1279 self.assertEqual((a - week) - a, -week)
1280 self.assertEqual((a - day) - a, -day)
1281 self.assertEqual((a - hour) - a, -hour)
1282 self.assertEqual((a - millisec) - a, -millisec)
1283 self.assertEqual(a - (a + week), -week)
1284 self.assertEqual(a - (a + day), -day)
1285 self.assertEqual(a - (a + hour), -hour)
1286 self.assertEqual(a - (a + millisec), -millisec)
1287 self.assertEqual(a - (a - week), week)
1288 self.assertEqual(a - (a - day), day)
1289 self.assertEqual(a - (a - hour), hour)
1290 self.assertEqual(a - (a - millisec), millisec)
1291 self.assertEqual(a + (week + day + hour + millisec),
1292 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1293 self.assertEqual(a + (week + day + hour + millisec),
1294 (((a + week) + day) + hour) + millisec)
1295 self.assertEqual(a - (week + day + hour + millisec),
1296 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1297 self.assertEqual(a - (week + day + hour + millisec),
1298 (((a - week) - day) - hour) - millisec)
1299 # Add/sub ints, longs, floats should be illegal
1300 for i in 1, 1L, 1.0:
1301 self.assertRaises(TypeError, lambda: a+i)
1302 self.assertRaises(TypeError, lambda: a-i)
1303 self.assertRaises(TypeError, lambda: i+a)
1304 self.assertRaises(TypeError, lambda: i-a)
1305
1306 # delta - datetime is senseless.
1307 self.assertRaises(TypeError, lambda: day - a)
1308 # mixing datetime and (delta or datetime) via * or // is senseless
1309 self.assertRaises(TypeError, lambda: day * a)
1310 self.assertRaises(TypeError, lambda: a * day)
1311 self.assertRaises(TypeError, lambda: day // a)
1312 self.assertRaises(TypeError, lambda: a // day)
1313 self.assertRaises(TypeError, lambda: a * a)
1314 self.assertRaises(TypeError, lambda: a // a)
1315 # datetime + datetime is senseless
1316 self.assertRaises(TypeError, lambda: a + a)
1317
1318 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001319 args = 6, 7, 23, 20, 59, 1, 64**2
1320 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001321 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001322 green = pickler.dumps(orig, proto)
1323 derived = unpickler.loads(green)
1324 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001325
Guido van Rossum275666f2003-02-07 21:49:01 +00001326 def test_more_pickling(self):
1327 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1328 s = pickle.dumps(a)
1329 b = pickle.loads(s)
1330 self.assertEqual(b.year, 2003)
1331 self.assertEqual(b.month, 2)
1332 self.assertEqual(b.day, 7)
1333
Tim Peters604c0132004-06-07 23:04:33 +00001334 def test_pickling_subclass_datetime(self):
1335 args = 6, 7, 23, 20, 59, 1, 64**2
1336 orig = SubclassDatetime(*args)
1337 for pickler, unpickler, proto in pickle_choices:
1338 green = pickler.dumps(orig, proto)
1339 derived = unpickler.loads(green)
1340 self.assertEqual(orig, derived)
1341
Tim Peters2a799bf2002-12-16 20:18:38 +00001342 def test_more_compare(self):
1343 # The test_compare() inherited from TestDate covers the error cases.
1344 # We just want to test lexicographic ordering on the members datetime
1345 # has that date lacks.
1346 args = [2000, 11, 29, 20, 58, 16, 999998]
1347 t1 = self.theclass(*args)
1348 t2 = self.theclass(*args)
1349 self.failUnless(t1 == t2)
1350 self.failUnless(t1 <= t2)
1351 self.failUnless(t1 >= t2)
1352 self.failUnless(not t1 != t2)
1353 self.failUnless(not t1 < t2)
1354 self.failUnless(not t1 > t2)
1355 self.assertEqual(cmp(t1, t2), 0)
1356 self.assertEqual(cmp(t2, t1), 0)
1357
1358 for i in range(len(args)):
1359 newargs = args[:]
1360 newargs[i] = args[i] + 1
1361 t2 = self.theclass(*newargs) # this is larger than t1
1362 self.failUnless(t1 < t2)
1363 self.failUnless(t2 > t1)
1364 self.failUnless(t1 <= t2)
1365 self.failUnless(t2 >= t1)
1366 self.failUnless(t1 != t2)
1367 self.failUnless(t2 != t1)
1368 self.failUnless(not t1 == t2)
1369 self.failUnless(not t2 == t1)
1370 self.failUnless(not t1 > t2)
1371 self.failUnless(not t2 < t1)
1372 self.failUnless(not t1 >= t2)
1373 self.failUnless(not t2 <= t1)
1374 self.assertEqual(cmp(t1, t2), -1)
1375 self.assertEqual(cmp(t2, t1), 1)
1376
1377
1378 # A helper for timestamp constructor tests.
1379 def verify_field_equality(self, expected, got):
1380 self.assertEqual(expected.tm_year, got.year)
1381 self.assertEqual(expected.tm_mon, got.month)
1382 self.assertEqual(expected.tm_mday, got.day)
1383 self.assertEqual(expected.tm_hour, got.hour)
1384 self.assertEqual(expected.tm_min, got.minute)
1385 self.assertEqual(expected.tm_sec, got.second)
1386
1387 def test_fromtimestamp(self):
1388 import time
1389
1390 ts = time.time()
1391 expected = time.localtime(ts)
1392 got = self.theclass.fromtimestamp(ts)
1393 self.verify_field_equality(expected, got)
1394
1395 def test_utcfromtimestamp(self):
1396 import time
1397
1398 ts = time.time()
1399 expected = time.gmtime(ts)
1400 got = self.theclass.utcfromtimestamp(ts)
1401 self.verify_field_equality(expected, got)
1402
Tim Peters1b6f7a92004-06-20 02:50:16 +00001403 def test_insane_fromtimestamp(self):
1404 # It's possible that some platform maps time_t to double,
1405 # and that this test will fail there. This test should
1406 # exempt such platforms (provided they return reasonable
1407 # results!).
1408 for insane in -1e200, 1e200:
1409 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1410 insane)
1411
1412 def test_insane_utcfromtimestamp(self):
1413 # It's possible that some platform maps time_t to double,
1414 # and that this test will fail there. This test should
1415 # exempt such platforms (provided they return reasonable
1416 # results!).
1417 for insane in -1e200, 1e200:
1418 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1419 insane)
1420
Tim Peters2a799bf2002-12-16 20:18:38 +00001421 def test_utcnow(self):
1422 import time
1423
1424 # Call it a success if utcnow() and utcfromtimestamp() are within
1425 # a second of each other.
1426 tolerance = timedelta(seconds=1)
1427 for dummy in range(3):
1428 from_now = self.theclass.utcnow()
1429 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1430 if abs(from_timestamp - from_now) <= tolerance:
1431 break
1432 # Else try again a few times.
1433 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1434
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001435 def test_strptime(self):
1436 import time
1437
1438 string = '2004-12-01 13:02:47'
1439 format = '%Y-%m-%d %H:%M:%S'
1440 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1441 got = self.theclass.strptime(string, format)
1442 self.assertEqual(expected, got)
1443
Tim Peters2a799bf2002-12-16 20:18:38 +00001444 def test_more_timetuple(self):
1445 # This tests fields beyond those tested by the TestDate.test_timetuple.
1446 t = self.theclass(2004, 12, 31, 6, 22, 33)
1447 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1448 self.assertEqual(t.timetuple(),
1449 (t.year, t.month, t.day,
1450 t.hour, t.minute, t.second,
1451 t.weekday(),
1452 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1453 -1))
1454 tt = t.timetuple()
1455 self.assertEqual(tt.tm_year, t.year)
1456 self.assertEqual(tt.tm_mon, t.month)
1457 self.assertEqual(tt.tm_mday, t.day)
1458 self.assertEqual(tt.tm_hour, t.hour)
1459 self.assertEqual(tt.tm_min, t.minute)
1460 self.assertEqual(tt.tm_sec, t.second)
1461 self.assertEqual(tt.tm_wday, t.weekday())
1462 self.assertEqual(tt.tm_yday, t.toordinal() -
1463 date(t.year, 1, 1).toordinal() + 1)
1464 self.assertEqual(tt.tm_isdst, -1)
1465
1466 def test_more_strftime(self):
1467 # This tests fields beyond those tested by the TestDate.test_strftime.
1468 t = self.theclass(2004, 12, 31, 6, 22, 33)
1469 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1470 "12 31 04 33 22 06 366")
1471
1472 def test_extract(self):
1473 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1474 self.assertEqual(dt.date(), date(2002, 3, 4))
1475 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1476
1477 def test_combine(self):
1478 d = date(2002, 3, 4)
1479 t = time(18, 45, 3, 1234)
1480 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1481 combine = self.theclass.combine
1482 dt = combine(d, t)
1483 self.assertEqual(dt, expected)
1484
1485 dt = combine(time=t, date=d)
1486 self.assertEqual(dt, expected)
1487
1488 self.assertEqual(d, dt.date())
1489 self.assertEqual(t, dt.time())
1490 self.assertEqual(dt, combine(dt.date(), dt.time()))
1491
1492 self.assertRaises(TypeError, combine) # need an arg
1493 self.assertRaises(TypeError, combine, d) # need two args
1494 self.assertRaises(TypeError, combine, t, d) # args reversed
1495 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1496 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1497
Tim Peters12bf3392002-12-24 05:41:27 +00001498 def test_replace(self):
1499 cls = self.theclass
1500 args = [1, 2, 3, 4, 5, 6, 7]
1501 base = cls(*args)
1502 self.assertEqual(base, base.replace())
1503
1504 i = 0
1505 for name, newval in (("year", 2),
1506 ("month", 3),
1507 ("day", 4),
1508 ("hour", 5),
1509 ("minute", 6),
1510 ("second", 7),
1511 ("microsecond", 8)):
1512 newargs = args[:]
1513 newargs[i] = newval
1514 expected = cls(*newargs)
1515 got = base.replace(**{name: newval})
1516 self.assertEqual(expected, got)
1517 i += 1
1518
1519 # Out of bounds.
1520 base = cls(2000, 2, 29)
1521 self.assertRaises(ValueError, base.replace, year=2001)
1522
Tim Peters80475bb2002-12-25 07:40:55 +00001523 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001524 # Pretty boring! The TZ test is more interesting here. astimezone()
1525 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001526 dt = self.theclass.now()
1527 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001528 self.assertRaises(TypeError, dt.astimezone) # not enough args
1529 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1530 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001531 self.assertRaises(ValueError, dt.astimezone, f) # naive
1532 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001533
Tim Peters52dcce22003-01-23 16:36:11 +00001534 class Bogus(tzinfo):
1535 def utcoffset(self, dt): return None
1536 def dst(self, dt): return timedelta(0)
1537 bog = Bogus()
1538 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1539
1540 class AlsoBogus(tzinfo):
1541 def utcoffset(self, dt): return timedelta(0)
1542 def dst(self, dt): return None
1543 alsobog = AlsoBogus()
1544 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001545
Tim Petersa98924a2003-05-17 05:55:19 +00001546 def test_subclass_datetime(self):
1547
1548 class C(self.theclass):
1549 theAnswer = 42
1550
1551 def __new__(cls, *args, **kws):
1552 temp = kws.copy()
1553 extra = temp.pop('extra')
1554 result = self.theclass.__new__(cls, *args, **temp)
1555 result.extra = extra
1556 return result
1557
1558 def newmeth(self, start):
1559 return start + self.year + self.month + self.second
1560
1561 args = 2003, 4, 14, 12, 13, 41
1562
1563 dt1 = self.theclass(*args)
1564 dt2 = C(*args, **{'extra': 7})
1565
1566 self.assertEqual(dt2.__class__, C)
1567 self.assertEqual(dt2.theAnswer, 42)
1568 self.assertEqual(dt2.extra, 7)
1569 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1570 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1571 dt1.second - 7)
1572
Tim Peters604c0132004-06-07 23:04:33 +00001573class SubclassTime(time):
1574 sub_var = 1
1575
Tim Peters07534a62003-02-07 22:50:28 +00001576class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001577
1578 theclass = time
1579
1580 def test_basic_attributes(self):
1581 t = self.theclass(12, 0)
1582 self.assertEqual(t.hour, 12)
1583 self.assertEqual(t.minute, 0)
1584 self.assertEqual(t.second, 0)
1585 self.assertEqual(t.microsecond, 0)
1586
1587 def test_basic_attributes_nonzero(self):
1588 # Make sure all attributes are non-zero so bugs in
1589 # bit-shifting access show up.
1590 t = self.theclass(12, 59, 59, 8000)
1591 self.assertEqual(t.hour, 12)
1592 self.assertEqual(t.minute, 59)
1593 self.assertEqual(t.second, 59)
1594 self.assertEqual(t.microsecond, 8000)
1595
1596 def test_roundtrip(self):
1597 t = self.theclass(1, 2, 3, 4)
1598
1599 # Verify t -> string -> time identity.
1600 s = repr(t)
1601 self.failUnless(s.startswith('datetime.'))
1602 s = s[9:]
1603 t2 = eval(s)
1604 self.assertEqual(t, t2)
1605
1606 # Verify identity via reconstructing from pieces.
1607 t2 = self.theclass(t.hour, t.minute, t.second,
1608 t.microsecond)
1609 self.assertEqual(t, t2)
1610
1611 def test_comparing(self):
1612 args = [1, 2, 3, 4]
1613 t1 = self.theclass(*args)
1614 t2 = self.theclass(*args)
1615 self.failUnless(t1 == t2)
1616 self.failUnless(t1 <= t2)
1617 self.failUnless(t1 >= t2)
1618 self.failUnless(not t1 != t2)
1619 self.failUnless(not t1 < t2)
1620 self.failUnless(not t1 > t2)
1621 self.assertEqual(cmp(t1, t2), 0)
1622 self.assertEqual(cmp(t2, t1), 0)
1623
1624 for i in range(len(args)):
1625 newargs = args[:]
1626 newargs[i] = args[i] + 1
1627 t2 = self.theclass(*newargs) # this is larger than t1
1628 self.failUnless(t1 < t2)
1629 self.failUnless(t2 > t1)
1630 self.failUnless(t1 <= t2)
1631 self.failUnless(t2 >= t1)
1632 self.failUnless(t1 != t2)
1633 self.failUnless(t2 != t1)
1634 self.failUnless(not t1 == t2)
1635 self.failUnless(not t2 == t1)
1636 self.failUnless(not t1 > t2)
1637 self.failUnless(not t2 < t1)
1638 self.failUnless(not t1 >= t2)
1639 self.failUnless(not t2 <= t1)
1640 self.assertEqual(cmp(t1, t2), -1)
1641 self.assertEqual(cmp(t2, t1), 1)
1642
Tim Peters68124bb2003-02-08 03:46:31 +00001643 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001644 self.assertEqual(t1 == badarg, False)
1645 self.assertEqual(t1 != badarg, True)
1646 self.assertEqual(badarg == t1, False)
1647 self.assertEqual(badarg != t1, True)
1648
Tim Peters2a799bf2002-12-16 20:18:38 +00001649 self.assertRaises(TypeError, lambda: t1 <= badarg)
1650 self.assertRaises(TypeError, lambda: t1 < badarg)
1651 self.assertRaises(TypeError, lambda: t1 > badarg)
1652 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001653 self.assertRaises(TypeError, lambda: badarg <= t1)
1654 self.assertRaises(TypeError, lambda: badarg < t1)
1655 self.assertRaises(TypeError, lambda: badarg > t1)
1656 self.assertRaises(TypeError, lambda: badarg >= t1)
1657
1658 def test_bad_constructor_arguments(self):
1659 # bad hours
1660 self.theclass(0, 0) # no exception
1661 self.theclass(23, 0) # no exception
1662 self.assertRaises(ValueError, self.theclass, -1, 0)
1663 self.assertRaises(ValueError, self.theclass, 24, 0)
1664 # bad minutes
1665 self.theclass(23, 0) # no exception
1666 self.theclass(23, 59) # no exception
1667 self.assertRaises(ValueError, self.theclass, 23, -1)
1668 self.assertRaises(ValueError, self.theclass, 23, 60)
1669 # bad seconds
1670 self.theclass(23, 59, 0) # no exception
1671 self.theclass(23, 59, 59) # no exception
1672 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1673 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1674 # bad microseconds
1675 self.theclass(23, 59, 59, 0) # no exception
1676 self.theclass(23, 59, 59, 999999) # no exception
1677 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1678 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1679
1680 def test_hash_equality(self):
1681 d = self.theclass(23, 30, 17)
1682 e = self.theclass(23, 30, 17)
1683 self.assertEqual(d, e)
1684 self.assertEqual(hash(d), hash(e))
1685
1686 dic = {d: 1}
1687 dic[e] = 2
1688 self.assertEqual(len(dic), 1)
1689 self.assertEqual(dic[d], 2)
1690 self.assertEqual(dic[e], 2)
1691
1692 d = self.theclass(0, 5, 17)
1693 e = self.theclass(0, 5, 17)
1694 self.assertEqual(d, e)
1695 self.assertEqual(hash(d), hash(e))
1696
1697 dic = {d: 1}
1698 dic[e] = 2
1699 self.assertEqual(len(dic), 1)
1700 self.assertEqual(dic[d], 2)
1701 self.assertEqual(dic[e], 2)
1702
1703 def test_isoformat(self):
1704 t = self.theclass(4, 5, 1, 123)
1705 self.assertEqual(t.isoformat(), "04:05:01.000123")
1706 self.assertEqual(t.isoformat(), str(t))
1707
1708 t = self.theclass()
1709 self.assertEqual(t.isoformat(), "00:00:00")
1710 self.assertEqual(t.isoformat(), str(t))
1711
1712 t = self.theclass(microsecond=1)
1713 self.assertEqual(t.isoformat(), "00:00:00.000001")
1714 self.assertEqual(t.isoformat(), str(t))
1715
1716 t = self.theclass(microsecond=10)
1717 self.assertEqual(t.isoformat(), "00:00:00.000010")
1718 self.assertEqual(t.isoformat(), str(t))
1719
1720 t = self.theclass(microsecond=100)
1721 self.assertEqual(t.isoformat(), "00:00:00.000100")
1722 self.assertEqual(t.isoformat(), str(t))
1723
1724 t = self.theclass(microsecond=1000)
1725 self.assertEqual(t.isoformat(), "00:00:00.001000")
1726 self.assertEqual(t.isoformat(), str(t))
1727
1728 t = self.theclass(microsecond=10000)
1729 self.assertEqual(t.isoformat(), "00:00:00.010000")
1730 self.assertEqual(t.isoformat(), str(t))
1731
1732 t = self.theclass(microsecond=100000)
1733 self.assertEqual(t.isoformat(), "00:00:00.100000")
1734 self.assertEqual(t.isoformat(), str(t))
1735
1736 def test_strftime(self):
1737 t = self.theclass(1, 2, 3, 4)
1738 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1739 # A naive object replaces %z and %Z with empty strings.
1740 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1741
1742 def test_str(self):
1743 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1744 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1745 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1746 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1747 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1748
1749 def test_repr(self):
1750 name = 'datetime.' + self.theclass.__name__
1751 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1752 "%s(1, 2, 3, 4)" % name)
1753 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1754 "%s(10, 2, 3, 4000)" % name)
1755 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1756 "%s(0, 2, 3, 400000)" % name)
1757 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1758 "%s(12, 2, 3)" % name)
1759 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1760 "%s(23, 15)" % name)
1761
1762 def test_resolution_info(self):
1763 self.assert_(isinstance(self.theclass.min, self.theclass))
1764 self.assert_(isinstance(self.theclass.max, self.theclass))
1765 self.assert_(isinstance(self.theclass.resolution, timedelta))
1766 self.assert_(self.theclass.max > self.theclass.min)
1767
1768 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001769 args = 20, 59, 16, 64**2
1770 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001771 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001772 green = pickler.dumps(orig, proto)
1773 derived = unpickler.loads(green)
1774 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001775
Tim Peters604c0132004-06-07 23:04:33 +00001776 def test_pickling_subclass_time(self):
1777 args = 20, 59, 16, 64**2
1778 orig = SubclassTime(*args)
1779 for pickler, unpickler, proto in pickle_choices:
1780 green = pickler.dumps(orig, proto)
1781 derived = unpickler.loads(green)
1782 self.assertEqual(orig, derived)
1783
Tim Peters2a799bf2002-12-16 20:18:38 +00001784 def test_bool(self):
1785 cls = self.theclass
1786 self.failUnless(cls(1))
1787 self.failUnless(cls(0, 1))
1788 self.failUnless(cls(0, 0, 1))
1789 self.failUnless(cls(0, 0, 0, 1))
1790 self.failUnless(not cls(0))
1791 self.failUnless(not cls())
1792
Tim Peters12bf3392002-12-24 05:41:27 +00001793 def test_replace(self):
1794 cls = self.theclass
1795 args = [1, 2, 3, 4]
1796 base = cls(*args)
1797 self.assertEqual(base, base.replace())
1798
1799 i = 0
1800 for name, newval in (("hour", 5),
1801 ("minute", 6),
1802 ("second", 7),
1803 ("microsecond", 8)):
1804 newargs = args[:]
1805 newargs[i] = newval
1806 expected = cls(*newargs)
1807 got = base.replace(**{name: newval})
1808 self.assertEqual(expected, got)
1809 i += 1
1810
1811 # Out of bounds.
1812 base = cls(1)
1813 self.assertRaises(ValueError, base.replace, hour=24)
1814 self.assertRaises(ValueError, base.replace, minute=-1)
1815 self.assertRaises(ValueError, base.replace, second=100)
1816 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1817
Tim Petersa98924a2003-05-17 05:55:19 +00001818 def test_subclass_time(self):
1819
1820 class C(self.theclass):
1821 theAnswer = 42
1822
1823 def __new__(cls, *args, **kws):
1824 temp = kws.copy()
1825 extra = temp.pop('extra')
1826 result = self.theclass.__new__(cls, *args, **temp)
1827 result.extra = extra
1828 return result
1829
1830 def newmeth(self, start):
1831 return start + self.hour + self.second
1832
1833 args = 4, 5, 6
1834
1835 dt1 = self.theclass(*args)
1836 dt2 = C(*args, **{'extra': 7})
1837
1838 self.assertEqual(dt2.__class__, C)
1839 self.assertEqual(dt2.theAnswer, 42)
1840 self.assertEqual(dt2.extra, 7)
1841 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1842 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1843
Armin Rigof4afb212005-11-07 07:15:48 +00001844 def test_backdoor_resistance(self):
1845 # see TestDate.test_backdoor_resistance().
1846 base = '2:59.0'
1847 for hour_byte in ' ', '9', chr(24), '\xff':
1848 self.assertRaises(TypeError, self.theclass,
1849 hour_byte + base[1:])
1850
Tim Peters855fe882002-12-22 03:43:39 +00001851# A mixin for classes with a tzinfo= argument. Subclasses must define
1852# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001853# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001854class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001855
Tim Petersbad8ff02002-12-30 20:52:32 +00001856 def test_argument_passing(self):
1857 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001858 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001859 class introspective(tzinfo):
1860 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001861 def utcoffset(self, dt):
1862 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001863 dst = utcoffset
1864
1865 obj = cls(1, 2, 3, tzinfo=introspective())
1866
Tim Peters0bf60bd2003-01-08 20:40:01 +00001867 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001868 self.assertEqual(obj.tzname(), expected)
1869
Tim Peters0bf60bd2003-01-08 20:40:01 +00001870 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001871 self.assertEqual(obj.utcoffset(), expected)
1872 self.assertEqual(obj.dst(), expected)
1873
Tim Peters855fe882002-12-22 03:43:39 +00001874 def test_bad_tzinfo_classes(self):
1875 cls = self.theclass
1876 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001877
Tim Peters855fe882002-12-22 03:43:39 +00001878 class NiceTry(object):
1879 def __init__(self): pass
1880 def utcoffset(self, dt): pass
1881 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1882
1883 class BetterTry(tzinfo):
1884 def __init__(self): pass
1885 def utcoffset(self, dt): pass
1886 b = BetterTry()
1887 t = cls(1, 1, 1, tzinfo=b)
1888 self.failUnless(t.tzinfo is b)
1889
1890 def test_utc_offset_out_of_bounds(self):
1891 class Edgy(tzinfo):
1892 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001893 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001894 def utcoffset(self, dt):
1895 return self.offset
1896
1897 cls = self.theclass
1898 for offset, legit in ((-1440, False),
1899 (-1439, True),
1900 (1439, True),
1901 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001902 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001903 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001904 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001905 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001906 else:
1907 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001908 if legit:
1909 aofs = abs(offset)
1910 h, m = divmod(aofs, 60)
1911 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001912 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001913 t = t.timetz()
1914 self.assertEqual(str(t), "01:02:03" + tag)
1915 else:
1916 self.assertRaises(ValueError, str, t)
1917
1918 def test_tzinfo_classes(self):
1919 cls = self.theclass
1920 class C1(tzinfo):
1921 def utcoffset(self, dt): return None
1922 def dst(self, dt): return None
1923 def tzname(self, dt): return None
1924 for t in (cls(1, 1, 1),
1925 cls(1, 1, 1, tzinfo=None),
1926 cls(1, 1, 1, tzinfo=C1())):
1927 self.failUnless(t.utcoffset() is None)
1928 self.failUnless(t.dst() is None)
1929 self.failUnless(t.tzname() is None)
1930
Tim Peters855fe882002-12-22 03:43:39 +00001931 class C3(tzinfo):
1932 def utcoffset(self, dt): return timedelta(minutes=-1439)
1933 def dst(self, dt): return timedelta(minutes=1439)
1934 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001935 t = cls(1, 1, 1, tzinfo=C3())
1936 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1937 self.assertEqual(t.dst(), timedelta(minutes=1439))
1938 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001939
1940 # Wrong types.
1941 class C4(tzinfo):
1942 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001943 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001944 def tzname(self, dt): return 0
1945 t = cls(1, 1, 1, tzinfo=C4())
1946 self.assertRaises(TypeError, t.utcoffset)
1947 self.assertRaises(TypeError, t.dst)
1948 self.assertRaises(TypeError, t.tzname)
1949
1950 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001951 class C6(tzinfo):
1952 def utcoffset(self, dt): return timedelta(hours=-24)
1953 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001954 t = cls(1, 1, 1, tzinfo=C6())
1955 self.assertRaises(ValueError, t.utcoffset)
1956 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001957
1958 # Not a whole number of minutes.
1959 class C7(tzinfo):
1960 def utcoffset(self, dt): return timedelta(seconds=61)
1961 def dst(self, dt): return timedelta(microseconds=-81)
1962 t = cls(1, 1, 1, tzinfo=C7())
1963 self.assertRaises(ValueError, t.utcoffset)
1964 self.assertRaises(ValueError, t.dst)
1965
Tim Peters4c0db782002-12-26 05:01:19 +00001966 def test_aware_compare(self):
1967 cls = self.theclass
1968
Tim Peters60c76e42002-12-27 00:41:11 +00001969 # Ensure that utcoffset() gets ignored if the comparands have
1970 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001971 class OperandDependentOffset(tzinfo):
1972 def utcoffset(self, t):
1973 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001974 # d0 and d1 equal after adjustment
1975 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001976 else:
Tim Peters397301e2003-01-02 21:28:08 +00001977 # d2 off in the weeds
1978 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00001979
1980 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
1981 d0 = base.replace(minute=3)
1982 d1 = base.replace(minute=9)
1983 d2 = base.replace(minute=11)
1984 for x in d0, d1, d2:
1985 for y in d0, d1, d2:
1986 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00001987 expected = cmp(x.minute, y.minute)
1988 self.assertEqual(got, expected)
1989
1990 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00001991 # Note that a time can't actually have an operand-depedent offset,
1992 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
1993 # so skip this test for time.
1994 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00001995 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
1996 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
1997 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
1998 for x in d0, d1, d2:
1999 for y in d0, d1, d2:
2000 got = cmp(x, y)
2001 if (x is d0 or x is d1) and (y is d0 or y is d1):
2002 expected = 0
2003 elif x is y is d2:
2004 expected = 0
2005 elif x is d2:
2006 expected = -1
2007 else:
2008 assert y is d2
2009 expected = 1
2010 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002011
Tim Peters855fe882002-12-22 03:43:39 +00002012
Tim Peters0bf60bd2003-01-08 20:40:01 +00002013# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002014class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002015 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002016
2017 def test_empty(self):
2018 t = self.theclass()
2019 self.assertEqual(t.hour, 0)
2020 self.assertEqual(t.minute, 0)
2021 self.assertEqual(t.second, 0)
2022 self.assertEqual(t.microsecond, 0)
2023 self.failUnless(t.tzinfo is None)
2024
Tim Peters2a799bf2002-12-16 20:18:38 +00002025 def test_zones(self):
2026 est = FixedOffset(-300, "EST", 1)
2027 utc = FixedOffset(0, "UTC", -2)
2028 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002029 t1 = time( 7, 47, tzinfo=est)
2030 t2 = time(12, 47, tzinfo=utc)
2031 t3 = time(13, 47, tzinfo=met)
2032 t4 = time(microsecond=40)
2033 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002034
2035 self.assertEqual(t1.tzinfo, est)
2036 self.assertEqual(t2.tzinfo, utc)
2037 self.assertEqual(t3.tzinfo, met)
2038 self.failUnless(t4.tzinfo is None)
2039 self.assertEqual(t5.tzinfo, utc)
2040
Tim Peters855fe882002-12-22 03:43:39 +00002041 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2042 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2043 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002044 self.failUnless(t4.utcoffset() is None)
2045 self.assertRaises(TypeError, t1.utcoffset, "no args")
2046
2047 self.assertEqual(t1.tzname(), "EST")
2048 self.assertEqual(t2.tzname(), "UTC")
2049 self.assertEqual(t3.tzname(), "MET")
2050 self.failUnless(t4.tzname() is None)
2051 self.assertRaises(TypeError, t1.tzname, "no args")
2052
Tim Peters855fe882002-12-22 03:43:39 +00002053 self.assertEqual(t1.dst(), timedelta(minutes=1))
2054 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2055 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002056 self.failUnless(t4.dst() is None)
2057 self.assertRaises(TypeError, t1.dst, "no args")
2058
2059 self.assertEqual(hash(t1), hash(t2))
2060 self.assertEqual(hash(t1), hash(t3))
2061 self.assertEqual(hash(t2), hash(t3))
2062
2063 self.assertEqual(t1, t2)
2064 self.assertEqual(t1, t3)
2065 self.assertEqual(t2, t3)
2066 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2067 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2068 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2069
2070 self.assertEqual(str(t1), "07:47:00-05:00")
2071 self.assertEqual(str(t2), "12:47:00+00:00")
2072 self.assertEqual(str(t3), "13:47:00+01:00")
2073 self.assertEqual(str(t4), "00:00:00.000040")
2074 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2075
2076 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2077 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2078 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2079 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2080 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2081
Tim Peters0bf60bd2003-01-08 20:40:01 +00002082 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002083 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2084 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2085 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2086 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2087 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2088
2089 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2090 "07:47:00 %Z=EST %z=-0500")
2091 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2092 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2093
2094 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002095 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002096 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2097 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2098
Tim Petersb92bb712002-12-21 17:44:07 +00002099 # Check that an invalid tzname result raises an exception.
2100 class Badtzname(tzinfo):
2101 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002102 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002103 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2104 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002105
2106 def test_hash_edge_cases(self):
2107 # Offsets that overflow a basic time.
2108 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2109 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2110 self.assertEqual(hash(t1), hash(t2))
2111
2112 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2113 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2114 self.assertEqual(hash(t1), hash(t2))
2115
Tim Peters2a799bf2002-12-16 20:18:38 +00002116 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002117 # Try one without a tzinfo.
2118 args = 20, 59, 16, 64**2
2119 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002120 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002121 green = pickler.dumps(orig, proto)
2122 derived = unpickler.loads(green)
2123 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002124
2125 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002126 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002127 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002128 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002129 green = pickler.dumps(orig, proto)
2130 derived = unpickler.loads(green)
2131 self.assertEqual(orig, derived)
2132 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2133 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2134 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002135
2136 def test_more_bool(self):
2137 # Test cases with non-None tzinfo.
2138 cls = self.theclass
2139
2140 t = cls(0, tzinfo=FixedOffset(-300, ""))
2141 self.failUnless(t)
2142
2143 t = cls(5, tzinfo=FixedOffset(-300, ""))
2144 self.failUnless(t)
2145
2146 t = cls(5, tzinfo=FixedOffset(300, ""))
2147 self.failUnless(not t)
2148
2149 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2150 self.failUnless(not t)
2151
2152 # Mostly ensuring this doesn't overflow internally.
2153 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2154 self.failUnless(t)
2155
2156 # But this should yield a value error -- the utcoffset is bogus.
2157 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2158 self.assertRaises(ValueError, lambda: bool(t))
2159
2160 # Likewise.
2161 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2162 self.assertRaises(ValueError, lambda: bool(t))
2163
Tim Peters12bf3392002-12-24 05:41:27 +00002164 def test_replace(self):
2165 cls = self.theclass
2166 z100 = FixedOffset(100, "+100")
2167 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2168 args = [1, 2, 3, 4, z100]
2169 base = cls(*args)
2170 self.assertEqual(base, base.replace())
2171
2172 i = 0
2173 for name, newval in (("hour", 5),
2174 ("minute", 6),
2175 ("second", 7),
2176 ("microsecond", 8),
2177 ("tzinfo", zm200)):
2178 newargs = args[:]
2179 newargs[i] = newval
2180 expected = cls(*newargs)
2181 got = base.replace(**{name: newval})
2182 self.assertEqual(expected, got)
2183 i += 1
2184
2185 # Ensure we can get rid of a tzinfo.
2186 self.assertEqual(base.tzname(), "+100")
2187 base2 = base.replace(tzinfo=None)
2188 self.failUnless(base2.tzinfo is None)
2189 self.failUnless(base2.tzname() is None)
2190
2191 # Ensure we can add one.
2192 base3 = base2.replace(tzinfo=z100)
2193 self.assertEqual(base, base3)
2194 self.failUnless(base.tzinfo is base3.tzinfo)
2195
2196 # Out of bounds.
2197 base = cls(1)
2198 self.assertRaises(ValueError, base.replace, hour=24)
2199 self.assertRaises(ValueError, base.replace, minute=-1)
2200 self.assertRaises(ValueError, base.replace, second=100)
2201 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2202
Tim Peters60c76e42002-12-27 00:41:11 +00002203 def test_mixed_compare(self):
2204 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002205 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002206 self.assertEqual(t1, t2)
2207 t2 = t2.replace(tzinfo=None)
2208 self.assertEqual(t1, t2)
2209 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2210 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002211 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2212 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002213
Tim Peters0bf60bd2003-01-08 20:40:01 +00002214 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002215 class Varies(tzinfo):
2216 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002217 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002218 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002219 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002220 return self.offset
2221
2222 v = Varies()
2223 t1 = t2.replace(tzinfo=v)
2224 t2 = t2.replace(tzinfo=v)
2225 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2226 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2227 self.assertEqual(t1, t2)
2228
2229 # But if they're not identical, it isn't ignored.
2230 t2 = t2.replace(tzinfo=Varies())
2231 self.failUnless(t1 < t2) # t1's offset counter still going up
2232
Tim Petersa98924a2003-05-17 05:55:19 +00002233 def test_subclass_timetz(self):
2234
2235 class C(self.theclass):
2236 theAnswer = 42
2237
2238 def __new__(cls, *args, **kws):
2239 temp = kws.copy()
2240 extra = temp.pop('extra')
2241 result = self.theclass.__new__(cls, *args, **temp)
2242 result.extra = extra
2243 return result
2244
2245 def newmeth(self, start):
2246 return start + self.hour + self.second
2247
2248 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2249
2250 dt1 = self.theclass(*args)
2251 dt2 = C(*args, **{'extra': 7})
2252
2253 self.assertEqual(dt2.__class__, C)
2254 self.assertEqual(dt2.theAnswer, 42)
2255 self.assertEqual(dt2.extra, 7)
2256 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2257 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2258
Tim Peters4c0db782002-12-26 05:01:19 +00002259
Tim Peters0bf60bd2003-01-08 20:40:01 +00002260# Testing datetime objects with a non-None tzinfo.
2261
Tim Peters855fe882002-12-22 03:43:39 +00002262class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002263 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002264
2265 def test_trivial(self):
2266 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2267 self.assertEqual(dt.year, 1)
2268 self.assertEqual(dt.month, 2)
2269 self.assertEqual(dt.day, 3)
2270 self.assertEqual(dt.hour, 4)
2271 self.assertEqual(dt.minute, 5)
2272 self.assertEqual(dt.second, 6)
2273 self.assertEqual(dt.microsecond, 7)
2274 self.assertEqual(dt.tzinfo, None)
2275
2276 def test_even_more_compare(self):
2277 # The test_compare() and test_more_compare() inherited from TestDate
2278 # and TestDateTime covered non-tzinfo cases.
2279
2280 # Smallest possible after UTC adjustment.
2281 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2282 # Largest possible after UTC adjustment.
2283 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2284 tzinfo=FixedOffset(-1439, ""))
2285
2286 # Make sure those compare correctly, and w/o overflow.
2287 self.failUnless(t1 < t2)
2288 self.failUnless(t1 != t2)
2289 self.failUnless(t2 > t1)
2290
2291 self.failUnless(t1 == t1)
2292 self.failUnless(t2 == t2)
2293
2294 # Equal afer adjustment.
2295 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2296 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2297 self.assertEqual(t1, t2)
2298
2299 # Change t1 not to subtract a minute, and t1 should be larger.
2300 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2301 self.failUnless(t1 > t2)
2302
2303 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2304 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2305 self.failUnless(t1 < t2)
2306
2307 # Back to the original t1, but make seconds resolve it.
2308 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2309 second=1)
2310 self.failUnless(t1 > t2)
2311
2312 # Likewise, but make microseconds resolve it.
2313 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2314 microsecond=1)
2315 self.failUnless(t1 > t2)
2316
2317 # Make t2 naive and it should fail.
2318 t2 = self.theclass.min
2319 self.assertRaises(TypeError, lambda: t1 == t2)
2320 self.assertEqual(t2, t2)
2321
2322 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2323 class Naive(tzinfo):
2324 def utcoffset(self, dt): return None
2325 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2326 self.assertRaises(TypeError, lambda: t1 == t2)
2327 self.assertEqual(t2, t2)
2328
2329 # OTOH, it's OK to compare two of these mixing the two ways of being
2330 # naive.
2331 t1 = self.theclass(5, 6, 7)
2332 self.assertEqual(t1, t2)
2333
2334 # Try a bogus uctoffset.
2335 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002336 def utcoffset(self, dt):
2337 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002338 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2339 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002340 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002341
Tim Peters2a799bf2002-12-16 20:18:38 +00002342 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002343 # Try one without a tzinfo.
2344 args = 6, 7, 23, 20, 59, 1, 64**2
2345 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002346 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002347 green = pickler.dumps(orig, proto)
2348 derived = unpickler.loads(green)
2349 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002350
2351 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002352 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002353 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002354 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002355 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002356 green = pickler.dumps(orig, proto)
2357 derived = unpickler.loads(green)
2358 self.assertEqual(orig, derived)
2359 self.failUnless(isinstance(derived.tzinfo,
2360 PicklableFixedOffset))
2361 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2362 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002363
2364 def test_extreme_hashes(self):
2365 # If an attempt is made to hash these via subtracting the offset
2366 # then hashing a datetime object, OverflowError results. The
2367 # Python implementation used to blow up here.
2368 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2369 hash(t)
2370 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2371 tzinfo=FixedOffset(-1439, ""))
2372 hash(t)
2373
2374 # OTOH, an OOB offset should blow up.
2375 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2376 self.assertRaises(ValueError, hash, t)
2377
2378 def test_zones(self):
2379 est = FixedOffset(-300, "EST")
2380 utc = FixedOffset(0, "UTC")
2381 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002382 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2383 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2384 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002385 self.assertEqual(t1.tzinfo, est)
2386 self.assertEqual(t2.tzinfo, utc)
2387 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002388 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2389 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2390 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002391 self.assertEqual(t1.tzname(), "EST")
2392 self.assertEqual(t2.tzname(), "UTC")
2393 self.assertEqual(t3.tzname(), "MET")
2394 self.assertEqual(hash(t1), hash(t2))
2395 self.assertEqual(hash(t1), hash(t3))
2396 self.assertEqual(hash(t2), hash(t3))
2397 self.assertEqual(t1, t2)
2398 self.assertEqual(t1, t3)
2399 self.assertEqual(t2, t3)
2400 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2401 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2402 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002403 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002404 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2405 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2406 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2407
2408 def test_combine(self):
2409 met = FixedOffset(60, "MET")
2410 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002411 tz = time(18, 45, 3, 1234, tzinfo=met)
2412 dt = datetime.combine(d, tz)
2413 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002414 tzinfo=met))
2415
2416 def test_extract(self):
2417 met = FixedOffset(60, "MET")
2418 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2419 self.assertEqual(dt.date(), date(2002, 3, 4))
2420 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002421 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002422
2423 def test_tz_aware_arithmetic(self):
2424 import random
2425
2426 now = self.theclass.now()
2427 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002428 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002429 nowaware = self.theclass.combine(now.date(), timeaware)
2430 self.failUnless(nowaware.tzinfo is tz55)
2431 self.assertEqual(nowaware.timetz(), timeaware)
2432
2433 # Can't mix aware and non-aware.
2434 self.assertRaises(TypeError, lambda: now - nowaware)
2435 self.assertRaises(TypeError, lambda: nowaware - now)
2436
Tim Peters0bf60bd2003-01-08 20:40:01 +00002437 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002438 self.assertRaises(TypeError, lambda: now + nowaware)
2439 self.assertRaises(TypeError, lambda: nowaware + now)
2440 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2441
2442 # Subtracting should yield 0.
2443 self.assertEqual(now - now, timedelta(0))
2444 self.assertEqual(nowaware - nowaware, timedelta(0))
2445
2446 # Adding a delta should preserve tzinfo.
2447 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2448 nowawareplus = nowaware + delta
2449 self.failUnless(nowaware.tzinfo is tz55)
2450 nowawareplus2 = delta + nowaware
2451 self.failUnless(nowawareplus2.tzinfo is tz55)
2452 self.assertEqual(nowawareplus, nowawareplus2)
2453
2454 # that - delta should be what we started with, and that - what we
2455 # started with should be delta.
2456 diff = nowawareplus - delta
2457 self.failUnless(diff.tzinfo is tz55)
2458 self.assertEqual(nowaware, diff)
2459 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2460 self.assertEqual(nowawareplus - nowaware, delta)
2461
2462 # Make up a random timezone.
2463 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002464 # Attach it to nowawareplus.
2465 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002466 self.failUnless(nowawareplus.tzinfo is tzr)
2467 # Make sure the difference takes the timezone adjustments into account.
2468 got = nowaware - nowawareplus
2469 # Expected: (nowaware base - nowaware offset) -
2470 # (nowawareplus base - nowawareplus offset) =
2471 # (nowaware base - nowawareplus base) +
2472 # (nowawareplus offset - nowaware offset) =
2473 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002474 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002475 self.assertEqual(got, expected)
2476
2477 # Try max possible difference.
2478 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2479 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2480 tzinfo=FixedOffset(-1439, "max"))
2481 maxdiff = max - min
2482 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2483 timedelta(minutes=2*1439))
2484
2485 def test_tzinfo_now(self):
2486 meth = self.theclass.now
2487 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2488 base = meth()
2489 # Try with and without naming the keyword.
2490 off42 = FixedOffset(42, "42")
2491 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002492 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002493 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002494 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002495 # Bad argument with and w/o naming the keyword.
2496 self.assertRaises(TypeError, meth, 16)
2497 self.assertRaises(TypeError, meth, tzinfo=16)
2498 # Bad keyword name.
2499 self.assertRaises(TypeError, meth, tinfo=off42)
2500 # Too many args.
2501 self.assertRaises(TypeError, meth, off42, off42)
2502
Tim Peters10cadce2003-01-23 19:58:02 +00002503 # We don't know which time zone we're in, and don't have a tzinfo
2504 # class to represent it, so seeing whether a tz argument actually
2505 # does a conversion is tricky.
2506 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2507 utc = FixedOffset(0, "utc", 0)
2508 for dummy in range(3):
2509 now = datetime.now(weirdtz)
2510 self.failUnless(now.tzinfo is weirdtz)
2511 utcnow = datetime.utcnow().replace(tzinfo=utc)
2512 now2 = utcnow.astimezone(weirdtz)
2513 if abs(now - now2) < timedelta(seconds=30):
2514 break
2515 # Else the code is broken, or more than 30 seconds passed between
2516 # calls; assuming the latter, just try again.
2517 else:
2518 # Three strikes and we're out.
2519 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2520
Tim Peters2a799bf2002-12-16 20:18:38 +00002521 def test_tzinfo_fromtimestamp(self):
2522 import time
2523 meth = self.theclass.fromtimestamp
2524 ts = time.time()
2525 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2526 base = meth(ts)
2527 # Try with and without naming the keyword.
2528 off42 = FixedOffset(42, "42")
2529 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002530 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002531 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002532 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002533 # Bad argument with and w/o naming the keyword.
2534 self.assertRaises(TypeError, meth, ts, 16)
2535 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2536 # Bad keyword name.
2537 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2538 # Too many args.
2539 self.assertRaises(TypeError, meth, ts, off42, off42)
2540 # Too few args.
2541 self.assertRaises(TypeError, meth)
2542
Tim Peters2a44a8d2003-01-23 20:53:10 +00002543 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002544 timestamp = 1000000000
2545 utcdatetime = datetime.utcfromtimestamp(timestamp)
2546 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2547 # But on some flavor of Mac, it's nowhere near that. So we can't have
2548 # any idea here what time that actually is, we can only test that
2549 # relative changes match.
2550 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2551 tz = FixedOffset(utcoffset, "tz", 0)
2552 expected = utcdatetime + utcoffset
2553 got = datetime.fromtimestamp(timestamp, tz)
2554 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002555
Tim Peters2a799bf2002-12-16 20:18:38 +00002556 def test_tzinfo_utcnow(self):
2557 meth = self.theclass.utcnow
2558 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2559 base = meth()
2560 # Try with and without naming the keyword; for whatever reason,
2561 # utcnow() doesn't accept a tzinfo argument.
2562 off42 = FixedOffset(42, "42")
2563 self.assertRaises(TypeError, meth, off42)
2564 self.assertRaises(TypeError, meth, tzinfo=off42)
2565
2566 def test_tzinfo_utcfromtimestamp(self):
2567 import time
2568 meth = self.theclass.utcfromtimestamp
2569 ts = time.time()
2570 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2571 base = meth(ts)
2572 # Try with and without naming the keyword; for whatever reason,
2573 # utcfromtimestamp() doesn't accept a tzinfo argument.
2574 off42 = FixedOffset(42, "42")
2575 self.assertRaises(TypeError, meth, ts, off42)
2576 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2577
2578 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002579 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002580 # DST flag.
2581 class DST(tzinfo):
2582 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002583 if isinstance(dstvalue, int):
2584 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002585 self.dstvalue = dstvalue
2586 def dst(self, dt):
2587 return self.dstvalue
2588
2589 cls = self.theclass
2590 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2591 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2592 t = d.timetuple()
2593 self.assertEqual(1, t.tm_year)
2594 self.assertEqual(1, t.tm_mon)
2595 self.assertEqual(1, t.tm_mday)
2596 self.assertEqual(10, t.tm_hour)
2597 self.assertEqual(20, t.tm_min)
2598 self.assertEqual(30, t.tm_sec)
2599 self.assertEqual(0, t.tm_wday)
2600 self.assertEqual(1, t.tm_yday)
2601 self.assertEqual(flag, t.tm_isdst)
2602
2603 # dst() returns wrong type.
2604 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2605
2606 # dst() at the edge.
2607 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2608 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2609
2610 # dst() out of range.
2611 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2612 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2613
2614 def test_utctimetuple(self):
2615 class DST(tzinfo):
2616 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002617 if isinstance(dstvalue, int):
2618 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002619 self.dstvalue = dstvalue
2620 def dst(self, dt):
2621 return self.dstvalue
2622
2623 cls = self.theclass
2624 # This can't work: DST didn't implement utcoffset.
2625 self.assertRaises(NotImplementedError,
2626 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2627
2628 class UOFS(DST):
2629 def __init__(self, uofs, dofs=None):
2630 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002631 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002632 def utcoffset(self, dt):
2633 return self.uofs
2634
2635 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2636 # in effect for a UTC time.
2637 for dstvalue in -33, 33, 0, None:
2638 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2639 t = d.utctimetuple()
2640 self.assertEqual(d.year, t.tm_year)
2641 self.assertEqual(d.month, t.tm_mon)
2642 self.assertEqual(d.day, t.tm_mday)
2643 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2644 self.assertEqual(13, t.tm_min)
2645 self.assertEqual(d.second, t.tm_sec)
2646 self.assertEqual(d.weekday(), t.tm_wday)
2647 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2648 t.tm_yday)
2649 self.assertEqual(0, t.tm_isdst)
2650
2651 # At the edges, UTC adjustment can normalize into years out-of-range
2652 # for a datetime object. Ensure that a correct timetuple is
2653 # created anyway.
2654 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2655 # That goes back 1 minute less than a full day.
2656 t = tiny.utctimetuple()
2657 self.assertEqual(t.tm_year, MINYEAR-1)
2658 self.assertEqual(t.tm_mon, 12)
2659 self.assertEqual(t.tm_mday, 31)
2660 self.assertEqual(t.tm_hour, 0)
2661 self.assertEqual(t.tm_min, 1)
2662 self.assertEqual(t.tm_sec, 37)
2663 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2664 self.assertEqual(t.tm_isdst, 0)
2665
2666 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2667 # That goes forward 1 minute less than a full day.
2668 t = huge.utctimetuple()
2669 self.assertEqual(t.tm_year, MAXYEAR+1)
2670 self.assertEqual(t.tm_mon, 1)
2671 self.assertEqual(t.tm_mday, 1)
2672 self.assertEqual(t.tm_hour, 23)
2673 self.assertEqual(t.tm_min, 58)
2674 self.assertEqual(t.tm_sec, 37)
2675 self.assertEqual(t.tm_yday, 1)
2676 self.assertEqual(t.tm_isdst, 0)
2677
2678 def test_tzinfo_isoformat(self):
2679 zero = FixedOffset(0, "+00:00")
2680 plus = FixedOffset(220, "+03:40")
2681 minus = FixedOffset(-231, "-03:51")
2682 unknown = FixedOffset(None, "")
2683
2684 cls = self.theclass
2685 datestr = '0001-02-03'
2686 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002687 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002688 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2689 timestr = '04:05:59' + (us and '.987001' or '')
2690 ofsstr = ofs is not None and d.tzname() or ''
2691 tailstr = timestr + ofsstr
2692 iso = d.isoformat()
2693 self.assertEqual(iso, datestr + 'T' + tailstr)
2694 self.assertEqual(iso, d.isoformat('T'))
2695 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2696 self.assertEqual(str(d), datestr + ' ' + tailstr)
2697
Tim Peters12bf3392002-12-24 05:41:27 +00002698 def test_replace(self):
2699 cls = self.theclass
2700 z100 = FixedOffset(100, "+100")
2701 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2702 args = [1, 2, 3, 4, 5, 6, 7, z100]
2703 base = cls(*args)
2704 self.assertEqual(base, base.replace())
2705
2706 i = 0
2707 for name, newval in (("year", 2),
2708 ("month", 3),
2709 ("day", 4),
2710 ("hour", 5),
2711 ("minute", 6),
2712 ("second", 7),
2713 ("microsecond", 8),
2714 ("tzinfo", zm200)):
2715 newargs = args[:]
2716 newargs[i] = newval
2717 expected = cls(*newargs)
2718 got = base.replace(**{name: newval})
2719 self.assertEqual(expected, got)
2720 i += 1
2721
2722 # Ensure we can get rid of a tzinfo.
2723 self.assertEqual(base.tzname(), "+100")
2724 base2 = base.replace(tzinfo=None)
2725 self.failUnless(base2.tzinfo is None)
2726 self.failUnless(base2.tzname() is None)
2727
2728 # Ensure we can add one.
2729 base3 = base2.replace(tzinfo=z100)
2730 self.assertEqual(base, base3)
2731 self.failUnless(base.tzinfo is base3.tzinfo)
2732
2733 # Out of bounds.
2734 base = cls(2000, 2, 29)
2735 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002736
Tim Peters80475bb2002-12-25 07:40:55 +00002737 def test_more_astimezone(self):
2738 # The inherited test_astimezone covered some trivial and error cases.
2739 fnone = FixedOffset(None, "None")
2740 f44m = FixedOffset(44, "44")
2741 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2742
Tim Peters10cadce2003-01-23 19:58:02 +00002743 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002744 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002745 # Replacing with degenerate tzinfo raises an exception.
2746 self.assertRaises(ValueError, dt.astimezone, fnone)
2747 # Ditto with None tz.
2748 self.assertRaises(TypeError, dt.astimezone, None)
2749 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002750 x = dt.astimezone(dt.tzinfo)
2751 self.failUnless(x.tzinfo is f44m)
2752 self.assertEqual(x.date(), dt.date())
2753 self.assertEqual(x.time(), dt.time())
2754
2755 # Replacing with different tzinfo does adjust.
2756 got = dt.astimezone(fm5h)
2757 self.failUnless(got.tzinfo is fm5h)
2758 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2759 expected = dt - dt.utcoffset() # in effect, convert to UTC
2760 expected += fm5h.utcoffset(dt) # and from there to local time
2761 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2762 self.assertEqual(got.date(), expected.date())
2763 self.assertEqual(got.time(), expected.time())
2764 self.assertEqual(got.timetz(), expected.timetz())
2765 self.failUnless(got.tzinfo is expected.tzinfo)
2766 self.assertEqual(got, expected)
2767
Tim Peters4c0db782002-12-26 05:01:19 +00002768 def test_aware_subtract(self):
2769 cls = self.theclass
2770
Tim Peters60c76e42002-12-27 00:41:11 +00002771 # Ensure that utcoffset() is ignored when the operands have the
2772 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002773 class OperandDependentOffset(tzinfo):
2774 def utcoffset(self, t):
2775 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002776 # d0 and d1 equal after adjustment
2777 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002778 else:
Tim Peters397301e2003-01-02 21:28:08 +00002779 # d2 off in the weeds
2780 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002781
2782 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2783 d0 = base.replace(minute=3)
2784 d1 = base.replace(minute=9)
2785 d2 = base.replace(minute=11)
2786 for x in d0, d1, d2:
2787 for y in d0, d1, d2:
2788 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002789 expected = timedelta(minutes=x.minute - y.minute)
2790 self.assertEqual(got, expected)
2791
2792 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2793 # ignored.
2794 base = cls(8, 9, 10, 11, 12, 13, 14)
2795 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2796 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2797 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2798 for x in d0, d1, d2:
2799 for y in d0, d1, d2:
2800 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002801 if (x is d0 or x is d1) and (y is d0 or y is d1):
2802 expected = timedelta(0)
2803 elif x is y is d2:
2804 expected = timedelta(0)
2805 elif x is d2:
2806 expected = timedelta(minutes=(11-59)-0)
2807 else:
2808 assert y is d2
2809 expected = timedelta(minutes=0-(11-59))
2810 self.assertEqual(got, expected)
2811
Tim Peters60c76e42002-12-27 00:41:11 +00002812 def test_mixed_compare(self):
2813 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002814 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002815 self.assertEqual(t1, t2)
2816 t2 = t2.replace(tzinfo=None)
2817 self.assertEqual(t1, t2)
2818 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2819 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002820 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2821 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002822
Tim Peters0bf60bd2003-01-08 20:40:01 +00002823 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002824 class Varies(tzinfo):
2825 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002826 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002827 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002828 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002829 return self.offset
2830
2831 v = Varies()
2832 t1 = t2.replace(tzinfo=v)
2833 t2 = t2.replace(tzinfo=v)
2834 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2835 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2836 self.assertEqual(t1, t2)
2837
2838 # But if they're not identical, it isn't ignored.
2839 t2 = t2.replace(tzinfo=Varies())
2840 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002841
Tim Petersa98924a2003-05-17 05:55:19 +00002842 def test_subclass_datetimetz(self):
2843
2844 class C(self.theclass):
2845 theAnswer = 42
2846
2847 def __new__(cls, *args, **kws):
2848 temp = kws.copy()
2849 extra = temp.pop('extra')
2850 result = self.theclass.__new__(cls, *args, **temp)
2851 result.extra = extra
2852 return result
2853
2854 def newmeth(self, start):
2855 return start + self.hour + self.year
2856
2857 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2858
2859 dt1 = self.theclass(*args)
2860 dt2 = C(*args, **{'extra': 7})
2861
2862 self.assertEqual(dt2.__class__, C)
2863 self.assertEqual(dt2.theAnswer, 42)
2864 self.assertEqual(dt2.extra, 7)
2865 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2866 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2867
Tim Peters621818b2002-12-29 23:44:49 +00002868# Pain to set up DST-aware tzinfo classes.
2869
2870def first_sunday_on_or_after(dt):
2871 days_to_go = 6 - dt.weekday()
2872 if days_to_go:
2873 dt += timedelta(days_to_go)
2874 return dt
2875
2876ZERO = timedelta(0)
2877HOUR = timedelta(hours=1)
2878DAY = timedelta(days=1)
2879# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2880DSTSTART = datetime(1, 4, 1, 2)
2881# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002882# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2883# being standard time on that day, there is no spelling in local time of
2884# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2885DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002886
2887class USTimeZone(tzinfo):
2888
2889 def __init__(self, hours, reprname, stdname, dstname):
2890 self.stdoffset = timedelta(hours=hours)
2891 self.reprname = reprname
2892 self.stdname = stdname
2893 self.dstname = dstname
2894
2895 def __repr__(self):
2896 return self.reprname
2897
2898 def tzname(self, dt):
2899 if self.dst(dt):
2900 return self.dstname
2901 else:
2902 return self.stdname
2903
2904 def utcoffset(self, dt):
2905 return self.stdoffset + self.dst(dt)
2906
2907 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002908 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002909 # An exception instead may be sensible here, in one or more of
2910 # the cases.
2911 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002912 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002913
2914 # Find first Sunday in April.
2915 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2916 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2917
2918 # Find last Sunday in October.
2919 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2920 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2921
Tim Peters621818b2002-12-29 23:44:49 +00002922 # Can't compare naive to aware objects, so strip the timezone from
2923 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002924 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002925 return HOUR
2926 else:
2927 return ZERO
2928
Tim Peters521fc152002-12-31 17:36:56 +00002929Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2930Central = USTimeZone(-6, "Central", "CST", "CDT")
2931Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2932Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002933utc_real = FixedOffset(0, "UTC", 0)
2934# For better test coverage, we want another flavor of UTC that's west of
2935# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002936utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002937
2938class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002939 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002940 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002941 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002942
Tim Peters0bf60bd2003-01-08 20:40:01 +00002943 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002944
Tim Peters521fc152002-12-31 17:36:56 +00002945 # Check a time that's inside DST.
2946 def checkinside(self, dt, tz, utc, dston, dstoff):
2947 self.assertEqual(dt.dst(), HOUR)
2948
2949 # Conversion to our own timezone is always an identity.
2950 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002951
2952 asutc = dt.astimezone(utc)
2953 there_and_back = asutc.astimezone(tz)
2954
2955 # Conversion to UTC and back isn't always an identity here,
2956 # because there are redundant spellings (in local time) of
2957 # UTC time when DST begins: the clock jumps from 1:59:59
2958 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2959 # make sense then. The classes above treat 2:MM:SS as
2960 # daylight time then (it's "after 2am"), really an alias
2961 # for 1:MM:SS standard time. The latter form is what
2962 # conversion back from UTC produces.
2963 if dt.date() == dston.date() and dt.hour == 2:
2964 # We're in the redundant hour, and coming back from
2965 # UTC gives the 1:MM:SS standard-time spelling.
2966 self.assertEqual(there_and_back + HOUR, dt)
2967 # Although during was considered to be in daylight
2968 # time, there_and_back is not.
2969 self.assertEqual(there_and_back.dst(), ZERO)
2970 # They're the same times in UTC.
2971 self.assertEqual(there_and_back.astimezone(utc),
2972 dt.astimezone(utc))
2973 else:
2974 # We're not in the redundant hour.
2975 self.assertEqual(dt, there_and_back)
2976
Tim Peters327098a2003-01-20 22:54:38 +00002977 # Because we have a redundant spelling when DST begins, there is
2978 # (unforunately) an hour when DST ends that can't be spelled at all in
2979 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
2980 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
2981 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
2982 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
2983 # expressed in local time. Nevertheless, we want conversion back
2984 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00002985 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00002986 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00002987 if dt.date() == dstoff.date() and dt.hour == 0:
2988 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00002989 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00002990 self.assertEqual(nexthour_tz, dt.replace(hour=1))
2991 nexthour_utc += HOUR
2992 nexthour_tz = nexthour_utc.astimezone(tz)
2993 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00002994 else:
Tim Peters327098a2003-01-20 22:54:38 +00002995 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00002996
2997 # Check a time that's outside DST.
2998 def checkoutside(self, dt, tz, utc):
2999 self.assertEqual(dt.dst(), ZERO)
3000
3001 # Conversion to our own timezone is always an identity.
3002 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003003
3004 # Converting to UTC and back is an identity too.
3005 asutc = dt.astimezone(utc)
3006 there_and_back = asutc.astimezone(tz)
3007 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003008
Tim Peters1024bf82002-12-30 17:09:40 +00003009 def convert_between_tz_and_utc(self, tz, utc):
3010 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003011 # Because 1:MM on the day DST ends is taken as being standard time,
3012 # there is no spelling in tz for the last hour of daylight time.
3013 # For purposes of the test, the last hour of DST is 0:MM, which is
3014 # taken as being daylight time (and 1:MM is taken as being standard
3015 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003016 dstoff = self.dstoff.replace(tzinfo=tz)
3017 for delta in (timedelta(weeks=13),
3018 DAY,
3019 HOUR,
3020 timedelta(minutes=1),
3021 timedelta(microseconds=1)):
3022
Tim Peters521fc152002-12-31 17:36:56 +00003023 self.checkinside(dston, tz, utc, dston, dstoff)
3024 for during in dston + delta, dstoff - delta:
3025 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003026
Tim Peters521fc152002-12-31 17:36:56 +00003027 self.checkoutside(dstoff, tz, utc)
3028 for outside in dston - delta, dstoff + delta:
3029 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003030
Tim Peters621818b2002-12-29 23:44:49 +00003031 def test_easy(self):
3032 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003033 self.convert_between_tz_and_utc(Eastern, utc_real)
3034 self.convert_between_tz_and_utc(Pacific, utc_real)
3035 self.convert_between_tz_and_utc(Eastern, utc_fake)
3036 self.convert_between_tz_and_utc(Pacific, utc_fake)
3037 # The next is really dancing near the edge. It works because
3038 # Pacific and Eastern are far enough apart that their "problem
3039 # hours" don't overlap.
3040 self.convert_between_tz_and_utc(Eastern, Pacific)
3041 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003042 # OTOH, these fail! Don't enable them. The difficulty is that
3043 # the edge case tests assume that every hour is representable in
3044 # the "utc" class. This is always true for a fixed-offset tzinfo
3045 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3046 # For these adjacent DST-aware time zones, the range of time offsets
3047 # tested ends up creating hours in the one that aren't representable
3048 # in the other. For the same reason, we would see failures in the
3049 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3050 # offset deltas in convert_between_tz_and_utc().
3051 #
3052 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3053 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003054
Tim Petersf3615152003-01-01 21:51:37 +00003055 def test_tricky(self):
3056 # 22:00 on day before daylight starts.
3057 fourback = self.dston - timedelta(hours=4)
3058 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003059 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003060 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3061 # 2", we should get the 3 spelling.
3062 # If we plug 22:00 the day before into Eastern, it "looks like std
3063 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3064 # to 22:00 lands on 2:00, which makes no sense in local time (the
3065 # local clock jumps from 1 to 3). The point here is to make sure we
3066 # get the 3 spelling.
3067 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003068 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003069 self.assertEqual(expected, got)
3070
3071 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3072 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003073 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003074 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3075 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3076 # spelling.
3077 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003078 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003079 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003080
Tim Petersadf64202003-01-04 06:03:15 +00003081 # Now on the day DST ends, we want "repeat an hour" behavior.
3082 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3083 # EST 23:MM 0:MM 1:MM 2:MM
3084 # EDT 0:MM 1:MM 2:MM 3:MM
3085 # wall 0:MM 1:MM 1:MM 2:MM against these
3086 for utc in utc_real, utc_fake:
3087 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003088 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003089 # Convert that to UTC.
3090 first_std_hour -= tz.utcoffset(None)
3091 # Adjust for possibly fake UTC.
3092 asutc = first_std_hour + utc.utcoffset(None)
3093 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3094 # tz=Eastern.
3095 asutcbase = asutc.replace(tzinfo=utc)
3096 for tzhour in (0, 1, 1, 2):
3097 expectedbase = self.dstoff.replace(hour=tzhour)
3098 for minute in 0, 30, 59:
3099 expected = expectedbase.replace(minute=minute)
3100 asutc = asutcbase.replace(minute=minute)
3101 astz = asutc.astimezone(tz)
3102 self.assertEqual(astz.replace(tzinfo=None), expected)
3103 asutcbase += HOUR
3104
3105
Tim Peters710fb152003-01-02 19:35:54 +00003106 def test_bogus_dst(self):
3107 class ok(tzinfo):
3108 def utcoffset(self, dt): return HOUR
3109 def dst(self, dt): return HOUR
3110
3111 now = self.theclass.now().replace(tzinfo=utc_real)
3112 # Doesn't blow up.
3113 now.astimezone(ok())
3114
3115 # Does blow up.
3116 class notok(ok):
3117 def dst(self, dt): return None
3118 self.assertRaises(ValueError, now.astimezone, notok())
3119
Tim Peters52dcce22003-01-23 16:36:11 +00003120 def test_fromutc(self):
3121 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3122 now = datetime.utcnow().replace(tzinfo=utc_real)
3123 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3124 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3125 enow = Eastern.fromutc(now) # doesn't blow up
3126 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3127 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3128 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3129
3130 # Always converts UTC to standard time.
3131 class FauxUSTimeZone(USTimeZone):
3132 def fromutc(self, dt):
3133 return dt + self.stdoffset
3134 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3135
3136 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3137 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3138 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3139
3140 # Check around DST start.
3141 start = self.dston.replace(hour=4, tzinfo=Eastern)
3142 fstart = start.replace(tzinfo=FEastern)
3143 for wall in 23, 0, 1, 3, 4, 5:
3144 expected = start.replace(hour=wall)
3145 if wall == 23:
3146 expected -= timedelta(days=1)
3147 got = Eastern.fromutc(start)
3148 self.assertEqual(expected, got)
3149
3150 expected = fstart + FEastern.stdoffset
3151 got = FEastern.fromutc(fstart)
3152 self.assertEqual(expected, got)
3153
3154 # Ensure astimezone() calls fromutc() too.
3155 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3156 self.assertEqual(expected, got)
3157
3158 start += HOUR
3159 fstart += HOUR
3160
3161 # Check around DST end.
3162 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3163 fstart = start.replace(tzinfo=FEastern)
3164 for wall in 0, 1, 1, 2, 3, 4:
3165 expected = start.replace(hour=wall)
3166 got = Eastern.fromutc(start)
3167 self.assertEqual(expected, got)
3168
3169 expected = fstart + FEastern.stdoffset
3170 got = FEastern.fromutc(fstart)
3171 self.assertEqual(expected, got)
3172
3173 # Ensure astimezone() calls fromutc() too.
3174 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3175 self.assertEqual(expected, got)
3176
3177 start += HOUR
3178 fstart += HOUR
3179
Tim Peters710fb152003-01-02 19:35:54 +00003180
Tim Peters528ca532004-09-16 01:30:50 +00003181#############################################################################
3182# oddballs
3183
3184class Oddballs(unittest.TestCase):
3185
3186 def test_bug_1028306(self):
3187 # Trying to compare a date to a datetime should act like a mixed-
3188 # type comparison, despite that datetime is a subclass of date.
3189 as_date = date.today()
3190 as_datetime = datetime.combine(as_date, time())
3191 self.assert_(as_date != as_datetime)
3192 self.assert_(as_datetime != as_date)
3193 self.assert_(not as_date == as_datetime)
3194 self.assert_(not as_datetime == as_date)
3195 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3196 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3197 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3198 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3199 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3200 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3201 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3202 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3203
3204 # Neverthelss, comparison should work with the base-class (date)
3205 # projection if use of a date method is forced.
3206 self.assert_(as_date.__eq__(as_datetime))
3207 different_day = (as_date.day + 1) % 20 + 1
3208 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3209 different_day)))
3210
3211 # And date should compare with other subclasses of date. If a
3212 # subclass wants to stop this, it's up to the subclass to do so.
3213 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3214 self.assertEqual(as_date, date_sc)
3215 self.assertEqual(date_sc, as_date)
3216
3217 # Ditto for datetimes.
3218 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3219 as_date.day, 0, 0, 0)
3220 self.assertEqual(as_datetime, datetime_sc)
3221 self.assertEqual(datetime_sc, as_datetime)
3222
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003223def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003224 allsuites = [unittest.makeSuite(klass, 'test')
3225 for klass in (TestModule,
3226 TestTZInfo,
3227 TestTimeDelta,
3228 TestDateOnly,
3229 TestDate,
3230 TestDateTime,
3231 TestTime,
3232 TestTimeTZ,
3233 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003234 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003235 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003236 )
3237 ]
3238 return unittest.TestSuite(allsuites)
3239
3240def test_main():
3241 import gc
3242 import sys
3243
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003244 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003245 lastrc = None
3246 while True:
3247 test_support.run_suite(thesuite)
3248 if 1: # change to 0, under a debug build, for some leak detection
3249 break
3250 gc.collect()
3251 if gc.garbage:
3252 raise SystemError("gc.garbage not empty after test run: %r" %
3253 gc.garbage)
3254 if hasattr(sys, 'gettotalrefcount'):
3255 thisrc = sys.gettotalrefcount()
3256 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3257 if lastrc:
3258 print >> sys.stderr, 'delta:', thisrc - lastrc
3259 else:
3260 print >> sys.stderr
3261 lastrc = thisrc
3262
3263if __name__ == "__main__":
3264 test_main()