blob: b0341a40da94092734bde6561de78e57c40d34b5 [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
Georg Brandl4ddfcd32006-09-30 11:17:34 +0000847 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
Tim Peters2a799bf2002-12-16 20:18:38 +0000848
849 self.assertRaises(TypeError, t.strftime) # needs an arg
850 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
851 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
852
853 # A naive object replaces %z and %Z w/ empty strings.
854 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
855
856 def test_resolution_info(self):
857 self.assert_(isinstance(self.theclass.min, self.theclass))
858 self.assert_(isinstance(self.theclass.max, self.theclass))
859 self.assert_(isinstance(self.theclass.resolution, timedelta))
860 self.assert_(self.theclass.max > self.theclass.min)
861
862 def test_extreme_timedelta(self):
863 big = self.theclass.max - self.theclass.min
864 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
865 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
866 # n == 315537897599999999 ~= 2**58.13
867 justasbig = timedelta(0, 0, n)
868 self.assertEqual(big, justasbig)
869 self.assertEqual(self.theclass.min + big, self.theclass.max)
870 self.assertEqual(self.theclass.max - big, self.theclass.min)
871
872 def test_timetuple(self):
873 for i in range(7):
874 # January 2, 1956 is a Monday (0)
875 d = self.theclass(1956, 1, 2+i)
876 t = d.timetuple()
877 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
878 # February 1, 1956 is a Wednesday (2)
879 d = self.theclass(1956, 2, 1+i)
880 t = d.timetuple()
881 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
882 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
883 # of the year.
884 d = self.theclass(1956, 3, 1+i)
885 t = d.timetuple()
886 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
887 self.assertEqual(t.tm_year, 1956)
888 self.assertEqual(t.tm_mon, 3)
889 self.assertEqual(t.tm_mday, 1+i)
890 self.assertEqual(t.tm_hour, 0)
891 self.assertEqual(t.tm_min, 0)
892 self.assertEqual(t.tm_sec, 0)
893 self.assertEqual(t.tm_wday, (3+i)%7)
894 self.assertEqual(t.tm_yday, 61+i)
895 self.assertEqual(t.tm_isdst, -1)
896
897 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +0000898 args = 6, 7, 23
899 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +0000900 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +0000901 green = pickler.dumps(orig, proto)
902 derived = unpickler.loads(green)
903 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +0000904
905 def test_compare(self):
906 t1 = self.theclass(2, 3, 4)
907 t2 = self.theclass(2, 3, 4)
908 self.failUnless(t1 == t2)
909 self.failUnless(t1 <= t2)
910 self.failUnless(t1 >= t2)
911 self.failUnless(not t1 != t2)
912 self.failUnless(not t1 < t2)
913 self.failUnless(not t1 > t2)
914 self.assertEqual(cmp(t1, t2), 0)
915 self.assertEqual(cmp(t2, t1), 0)
916
917 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
918 t2 = self.theclass(*args) # this is larger than t1
919 self.failUnless(t1 < t2)
920 self.failUnless(t2 > t1)
921 self.failUnless(t1 <= t2)
922 self.failUnless(t2 >= t1)
923 self.failUnless(t1 != t2)
924 self.failUnless(t2 != t1)
925 self.failUnless(not t1 == t2)
926 self.failUnless(not t2 == t1)
927 self.failUnless(not t1 > t2)
928 self.failUnless(not t2 < t1)
929 self.failUnless(not t1 >= t2)
930 self.failUnless(not t2 <= t1)
931 self.assertEqual(cmp(t1, t2), -1)
932 self.assertEqual(cmp(t2, t1), 1)
933
Tim Peters68124bb2003-02-08 03:46:31 +0000934 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +0000935 self.assertEqual(t1 == badarg, False)
936 self.assertEqual(t1 != badarg, True)
937 self.assertEqual(badarg == t1, False)
938 self.assertEqual(badarg != t1, True)
939
Tim Peters2a799bf2002-12-16 20:18:38 +0000940 self.assertRaises(TypeError, lambda: t1 < badarg)
941 self.assertRaises(TypeError, lambda: t1 > badarg)
942 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +0000943 self.assertRaises(TypeError, lambda: badarg <= t1)
944 self.assertRaises(TypeError, lambda: badarg < t1)
945 self.assertRaises(TypeError, lambda: badarg > t1)
946 self.assertRaises(TypeError, lambda: badarg >= t1)
947
Tim Peters8d81a012003-01-24 22:36:34 +0000948 def test_mixed_compare(self):
949 our = self.theclass(2000, 4, 5)
950 self.assertRaises(TypeError, cmp, our, 1)
951 self.assertRaises(TypeError, cmp, 1, our)
952
953 class AnotherDateTimeClass(object):
954 def __cmp__(self, other):
955 # Return "equal" so calling this can't be confused with
956 # compare-by-address (which never says "equal" for distinct
957 # objects).
958 return 0
959
960 # This still errors, because date and datetime comparison raise
961 # TypeError instead of NotImplemented when they don't know what to
962 # do, in order to stop comparison from falling back to the default
963 # compare-by-address.
964 their = AnotherDateTimeClass()
965 self.assertRaises(TypeError, cmp, our, their)
966 # Oops: The next stab raises TypeError in the C implementation,
967 # but not in the Python implementation of datetime. The difference
968 # is due to that the Python implementation defines __cmp__ but
969 # the C implementation defines tp_richcompare. This is more pain
970 # to fix than it's worth, so commenting out the test.
971 # self.assertEqual(cmp(their, our), 0)
972
973 # But date and datetime comparison return NotImplemented instead if the
974 # other object has a timetuple attr. This gives the other object a
975 # chance to do the comparison.
976 class Comparable(AnotherDateTimeClass):
977 def timetuple(self):
978 return ()
979
980 their = Comparable()
981 self.assertEqual(cmp(our, their), 0)
982 self.assertEqual(cmp(their, our), 0)
983 self.failUnless(our == their)
984 self.failUnless(their == our)
985
Tim Peters2a799bf2002-12-16 20:18:38 +0000986 def test_bool(self):
987 # All dates are considered true.
988 self.failUnless(self.theclass.min)
989 self.failUnless(self.theclass.max)
990
Tim Petersd6844152002-12-22 20:58:42 +0000991 def test_srftime_out_of_range(self):
992 # For nasty technical reasons, we can't handle years before 1900.
993 cls = self.theclass
994 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
995 for y in 1, 49, 51, 99, 100, 1000, 1899:
996 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
Tim Peters12bf3392002-12-24 05:41:27 +0000997
998 def test_replace(self):
999 cls = self.theclass
1000 args = [1, 2, 3]
1001 base = cls(*args)
1002 self.assertEqual(base, base.replace())
1003
1004 i = 0
1005 for name, newval in (("year", 2),
1006 ("month", 3),
1007 ("day", 4)):
1008 newargs = args[:]
1009 newargs[i] = newval
1010 expected = cls(*newargs)
1011 got = base.replace(**{name: newval})
1012 self.assertEqual(expected, got)
1013 i += 1
1014
1015 # Out of bounds.
1016 base = cls(2000, 2, 29)
1017 self.assertRaises(ValueError, base.replace, year=2001)
1018
Tim Petersa98924a2003-05-17 05:55:19 +00001019 def test_subclass_date(self):
1020
1021 class C(self.theclass):
1022 theAnswer = 42
1023
1024 def __new__(cls, *args, **kws):
1025 temp = kws.copy()
1026 extra = temp.pop('extra')
1027 result = self.theclass.__new__(cls, *args, **temp)
1028 result.extra = extra
1029 return result
1030
1031 def newmeth(self, start):
1032 return start + self.year + self.month
1033
1034 args = 2003, 4, 14
1035
1036 dt1 = self.theclass(*args)
1037 dt2 = C(*args, **{'extra': 7})
1038
1039 self.assertEqual(dt2.__class__, C)
1040 self.assertEqual(dt2.theAnswer, 42)
1041 self.assertEqual(dt2.extra, 7)
1042 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1043 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1044
Tim Peters604c0132004-06-07 23:04:33 +00001045 def test_pickling_subclass_date(self):
1046
1047 args = 6, 7, 23
1048 orig = SubclassDate(*args)
1049 for pickler, unpickler, proto in pickle_choices:
1050 green = pickler.dumps(orig, proto)
1051 derived = unpickler.loads(green)
1052 self.assertEqual(orig, derived)
1053
Tim Peters3f606292004-03-21 23:38:41 +00001054 def test_backdoor_resistance(self):
1055 # For fast unpickling, the constructor accepts a pickle string.
1056 # This is a low-overhead backdoor. A user can (by intent or
1057 # mistake) pass a string directly, which (if it's the right length)
1058 # will get treated like a pickle, and bypass the normal sanity
1059 # checks in the constructor. This can create insane objects.
1060 # The constructor doesn't want to burn the time to validate all
1061 # fields, but does check the month field. This stops, e.g.,
1062 # datetime.datetime('1995-03-25') from yielding an insane object.
1063 base = '1995-03-25'
1064 if not issubclass(self.theclass, datetime):
1065 base = base[:4]
1066 for month_byte in '9', chr(0), chr(13), '\xff':
1067 self.assertRaises(TypeError, self.theclass,
1068 base[:2] + month_byte + base[3:])
1069 for ord_byte in range(1, 13):
1070 # This shouldn't blow up because of the month byte alone. If
1071 # the implementation changes to do more-careful checking, it may
1072 # blow up because other fields are insane.
1073 self.theclass(base[:2] + chr(ord_byte) + base[3:])
Tim Peterseb1a4962003-05-17 02:25:20 +00001074
Tim Peters2a799bf2002-12-16 20:18:38 +00001075#############################################################################
1076# datetime tests
1077
Tim Peters604c0132004-06-07 23:04:33 +00001078class SubclassDatetime(datetime):
1079 sub_var = 1
1080
Tim Peters2a799bf2002-12-16 20:18:38 +00001081class TestDateTime(TestDate):
1082
1083 theclass = datetime
1084
1085 def test_basic_attributes(self):
1086 dt = self.theclass(2002, 3, 1, 12, 0)
1087 self.assertEqual(dt.year, 2002)
1088 self.assertEqual(dt.month, 3)
1089 self.assertEqual(dt.day, 1)
1090 self.assertEqual(dt.hour, 12)
1091 self.assertEqual(dt.minute, 0)
1092 self.assertEqual(dt.second, 0)
1093 self.assertEqual(dt.microsecond, 0)
1094
1095 def test_basic_attributes_nonzero(self):
1096 # Make sure all attributes are non-zero so bugs in
1097 # bit-shifting access show up.
1098 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1099 self.assertEqual(dt.year, 2002)
1100 self.assertEqual(dt.month, 3)
1101 self.assertEqual(dt.day, 1)
1102 self.assertEqual(dt.hour, 12)
1103 self.assertEqual(dt.minute, 59)
1104 self.assertEqual(dt.second, 59)
1105 self.assertEqual(dt.microsecond, 8000)
1106
1107 def test_roundtrip(self):
1108 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1109 self.theclass.now()):
1110 # Verify dt -> string -> datetime identity.
1111 s = repr(dt)
1112 self.failUnless(s.startswith('datetime.'))
1113 s = s[9:]
1114 dt2 = eval(s)
1115 self.assertEqual(dt, dt2)
1116
1117 # Verify identity via reconstructing from pieces.
1118 dt2 = self.theclass(dt.year, dt.month, dt.day,
1119 dt.hour, dt.minute, dt.second,
1120 dt.microsecond)
1121 self.assertEqual(dt, dt2)
1122
1123 def test_isoformat(self):
1124 t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1125 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123")
1126 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1127 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1128 # str is ISO format with the separator forced to a blank.
1129 self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1130
1131 t = self.theclass(2, 3, 2)
1132 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00")
1133 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1134 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1135 # str is ISO format with the separator forced to a blank.
1136 self.assertEqual(str(t), "0002-03-02 00:00:00")
1137
1138 def test_more_ctime(self):
1139 # Test fields that TestDate doesn't touch.
1140 import time
1141
1142 t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1143 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002")
1144 # Oops! The next line fails on Win2K under MSVC 6, so it's commented
1145 # out. The difference is that t.ctime() produces " 2" for the day,
1146 # but platform ctime() produces "02" for the day. According to
1147 # C99, t.ctime() is correct here.
1148 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1149
1150 # So test a case where that difference doesn't matter.
1151 t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1152 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1153
1154 def test_tz_independent_comparing(self):
1155 dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1156 dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1157 dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1158 self.assertEqual(dt1, dt3)
1159 self.assert_(dt2 > dt3)
1160
1161 # Make sure comparison doesn't forget microseconds, and isn't done
1162 # via comparing a float timestamp (an IEEE double doesn't have enough
1163 # precision to span microsecond resolution across years 1 thru 9999,
1164 # so comparing via timestamp necessarily calls some distinct values
1165 # equal).
1166 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1167 us = timedelta(microseconds=1)
1168 dt2 = dt1 + us
1169 self.assertEqual(dt2 - dt1, us)
1170 self.assert_(dt1 < dt2)
1171
Neal Norwitzd5b0c9b2006-03-20 01:58:39 +00001172 def test_strftime_with_bad_tzname_replace(self):
1173 # verify ok if tzinfo.tzname().replace() returns a non-string
1174 class MyTzInfo(FixedOffset):
1175 def tzname(self, dt):
1176 class MyStr(str):
1177 def replace(self, *args):
1178 return None
1179 return MyStr('name')
1180 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1181 self.assertRaises(TypeError, t.strftime, '%Z')
1182
Tim Peters2a799bf2002-12-16 20:18:38 +00001183 def test_bad_constructor_arguments(self):
1184 # bad years
1185 self.theclass(MINYEAR, 1, 1) # no exception
1186 self.theclass(MAXYEAR, 1, 1) # no exception
1187 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1188 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1189 # bad months
1190 self.theclass(2000, 1, 1) # no exception
1191 self.theclass(2000, 12, 1) # no exception
1192 self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1193 self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1194 # bad days
1195 self.theclass(2000, 2, 29) # no exception
1196 self.theclass(2004, 2, 29) # no exception
1197 self.theclass(2400, 2, 29) # no exception
1198 self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1199 self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1200 self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1201 self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1202 self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1203 self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1204 # bad hours
1205 self.theclass(2000, 1, 31, 0) # no exception
1206 self.theclass(2000, 1, 31, 23) # no exception
1207 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1208 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1209 # bad minutes
1210 self.theclass(2000, 1, 31, 23, 0) # no exception
1211 self.theclass(2000, 1, 31, 23, 59) # no exception
1212 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1213 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1214 # bad seconds
1215 self.theclass(2000, 1, 31, 23, 59, 0) # no exception
1216 self.theclass(2000, 1, 31, 23, 59, 59) # no exception
1217 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1218 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1219 # bad microseconds
1220 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception
1221 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception
1222 self.assertRaises(ValueError, self.theclass,
1223 2000, 1, 31, 23, 59, 59, -1)
1224 self.assertRaises(ValueError, self.theclass,
1225 2000, 1, 31, 23, 59, 59,
1226 1000000)
1227
1228 def test_hash_equality(self):
1229 d = self.theclass(2000, 12, 31, 23, 30, 17)
1230 e = self.theclass(2000, 12, 31, 23, 30, 17)
1231 self.assertEqual(d, e)
1232 self.assertEqual(hash(d), hash(e))
1233
1234 dic = {d: 1}
1235 dic[e] = 2
1236 self.assertEqual(len(dic), 1)
1237 self.assertEqual(dic[d], 2)
1238 self.assertEqual(dic[e], 2)
1239
1240 d = self.theclass(2001, 1, 1, 0, 5, 17)
1241 e = self.theclass(2001, 1, 1, 0, 5, 17)
1242 self.assertEqual(d, e)
1243 self.assertEqual(hash(d), hash(e))
1244
1245 dic = {d: 1}
1246 dic[e] = 2
1247 self.assertEqual(len(dic), 1)
1248 self.assertEqual(dic[d], 2)
1249 self.assertEqual(dic[e], 2)
1250
1251 def test_computations(self):
1252 a = self.theclass(2002, 1, 31)
1253 b = self.theclass(1956, 1, 31)
1254 diff = a-b
1255 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1256 self.assertEqual(diff.seconds, 0)
1257 self.assertEqual(diff.microseconds, 0)
1258 a = self.theclass(2002, 3, 2, 17, 6)
1259 millisec = timedelta(0, 0, 1000)
1260 hour = timedelta(0, 3600)
1261 day = timedelta(1)
1262 week = timedelta(7)
1263 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1264 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1265 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1266 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1267 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1268 self.assertEqual(a - hour, a + -hour)
1269 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1270 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1271 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1272 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1273 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1274 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1275 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1276 self.assertEqual((a + week) - a, week)
1277 self.assertEqual((a + day) - a, day)
1278 self.assertEqual((a + hour) - a, hour)
1279 self.assertEqual((a + millisec) - a, millisec)
1280 self.assertEqual((a - week) - a, -week)
1281 self.assertEqual((a - day) - a, -day)
1282 self.assertEqual((a - hour) - a, -hour)
1283 self.assertEqual((a - millisec) - a, -millisec)
1284 self.assertEqual(a - (a + week), -week)
1285 self.assertEqual(a - (a + day), -day)
1286 self.assertEqual(a - (a + hour), -hour)
1287 self.assertEqual(a - (a + millisec), -millisec)
1288 self.assertEqual(a - (a - week), week)
1289 self.assertEqual(a - (a - day), day)
1290 self.assertEqual(a - (a - hour), hour)
1291 self.assertEqual(a - (a - millisec), millisec)
1292 self.assertEqual(a + (week + day + hour + millisec),
1293 self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1294 self.assertEqual(a + (week + day + hour + millisec),
1295 (((a + week) + day) + hour) + millisec)
1296 self.assertEqual(a - (week + day + hour + millisec),
1297 self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1298 self.assertEqual(a - (week + day + hour + millisec),
1299 (((a - week) - day) - hour) - millisec)
1300 # Add/sub ints, longs, floats should be illegal
1301 for i in 1, 1L, 1.0:
1302 self.assertRaises(TypeError, lambda: a+i)
1303 self.assertRaises(TypeError, lambda: a-i)
1304 self.assertRaises(TypeError, lambda: i+a)
1305 self.assertRaises(TypeError, lambda: i-a)
1306
1307 # delta - datetime is senseless.
1308 self.assertRaises(TypeError, lambda: day - a)
1309 # mixing datetime and (delta or datetime) via * or // is senseless
1310 self.assertRaises(TypeError, lambda: day * a)
1311 self.assertRaises(TypeError, lambda: a * day)
1312 self.assertRaises(TypeError, lambda: day // a)
1313 self.assertRaises(TypeError, lambda: a // day)
1314 self.assertRaises(TypeError, lambda: a * a)
1315 self.assertRaises(TypeError, lambda: a // a)
1316 # datetime + datetime is senseless
1317 self.assertRaises(TypeError, lambda: a + a)
1318
1319 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001320 args = 6, 7, 23, 20, 59, 1, 64**2
1321 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001322 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001323 green = pickler.dumps(orig, proto)
1324 derived = unpickler.loads(green)
1325 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001326
Guido van Rossum275666f2003-02-07 21:49:01 +00001327 def test_more_pickling(self):
1328 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1329 s = pickle.dumps(a)
1330 b = pickle.loads(s)
1331 self.assertEqual(b.year, 2003)
1332 self.assertEqual(b.month, 2)
1333 self.assertEqual(b.day, 7)
1334
Tim Peters604c0132004-06-07 23:04:33 +00001335 def test_pickling_subclass_datetime(self):
1336 args = 6, 7, 23, 20, 59, 1, 64**2
1337 orig = SubclassDatetime(*args)
1338 for pickler, unpickler, proto in pickle_choices:
1339 green = pickler.dumps(orig, proto)
1340 derived = unpickler.loads(green)
1341 self.assertEqual(orig, derived)
1342
Tim Peters2a799bf2002-12-16 20:18:38 +00001343 def test_more_compare(self):
1344 # The test_compare() inherited from TestDate covers the error cases.
1345 # We just want to test lexicographic ordering on the members datetime
1346 # has that date lacks.
1347 args = [2000, 11, 29, 20, 58, 16, 999998]
1348 t1 = self.theclass(*args)
1349 t2 = self.theclass(*args)
1350 self.failUnless(t1 == t2)
1351 self.failUnless(t1 <= t2)
1352 self.failUnless(t1 >= t2)
1353 self.failUnless(not t1 != t2)
1354 self.failUnless(not t1 < t2)
1355 self.failUnless(not t1 > t2)
1356 self.assertEqual(cmp(t1, t2), 0)
1357 self.assertEqual(cmp(t2, t1), 0)
1358
1359 for i in range(len(args)):
1360 newargs = args[:]
1361 newargs[i] = args[i] + 1
1362 t2 = self.theclass(*newargs) # this is larger than t1
1363 self.failUnless(t1 < t2)
1364 self.failUnless(t2 > t1)
1365 self.failUnless(t1 <= t2)
1366 self.failUnless(t2 >= t1)
1367 self.failUnless(t1 != t2)
1368 self.failUnless(t2 != t1)
1369 self.failUnless(not t1 == t2)
1370 self.failUnless(not t2 == t1)
1371 self.failUnless(not t1 > t2)
1372 self.failUnless(not t2 < t1)
1373 self.failUnless(not t1 >= t2)
1374 self.failUnless(not t2 <= t1)
1375 self.assertEqual(cmp(t1, t2), -1)
1376 self.assertEqual(cmp(t2, t1), 1)
1377
1378
1379 # A helper for timestamp constructor tests.
1380 def verify_field_equality(self, expected, got):
1381 self.assertEqual(expected.tm_year, got.year)
1382 self.assertEqual(expected.tm_mon, got.month)
1383 self.assertEqual(expected.tm_mday, got.day)
1384 self.assertEqual(expected.tm_hour, got.hour)
1385 self.assertEqual(expected.tm_min, got.minute)
1386 self.assertEqual(expected.tm_sec, got.second)
1387
1388 def test_fromtimestamp(self):
1389 import time
1390
1391 ts = time.time()
1392 expected = time.localtime(ts)
1393 got = self.theclass.fromtimestamp(ts)
1394 self.verify_field_equality(expected, got)
1395
1396 def test_utcfromtimestamp(self):
1397 import time
1398
1399 ts = time.time()
1400 expected = time.gmtime(ts)
1401 got = self.theclass.utcfromtimestamp(ts)
1402 self.verify_field_equality(expected, got)
1403
Georg Brandl6d78a582006-04-28 19:09:24 +00001404 def test_microsecond_rounding(self):
1405 # Test whether fromtimestamp "rounds up" floats that are less
1406 # than one microsecond smaller than an integer.
1407 self.assertEquals(self.theclass.fromtimestamp(0.9999999),
1408 self.theclass.fromtimestamp(1))
1409
Tim Peters1b6f7a92004-06-20 02:50:16 +00001410 def test_insane_fromtimestamp(self):
1411 # It's possible that some platform maps time_t to double,
1412 # and that this test will fail there. This test should
1413 # exempt such platforms (provided they return reasonable
1414 # results!).
1415 for insane in -1e200, 1e200:
1416 self.assertRaises(ValueError, self.theclass.fromtimestamp,
1417 insane)
1418
1419 def test_insane_utcfromtimestamp(self):
1420 # It's possible that some platform maps time_t to double,
1421 # and that this test will fail there. This test should
1422 # exempt such platforms (provided they return reasonable
1423 # results!).
1424 for insane in -1e200, 1e200:
1425 self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1426 insane)
1427
Guido van Rossum2054ee92007-03-06 15:50:01 +00001428 def test_negative_float_fromtimestamp(self):
1429 # The result is tz-dependent; at least test that this doesn't
1430 # fail (like it did before bug 1646728 was fixed).
1431 self.theclass.fromtimestamp(-1.05)
1432
1433 def test_negative_float_utcfromtimestamp(self):
1434 d = self.theclass.utcfromtimestamp(-1.05)
1435 self.assertEquals(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1436
Tim Peters2a799bf2002-12-16 20:18:38 +00001437 def test_utcnow(self):
1438 import time
1439
1440 # Call it a success if utcnow() and utcfromtimestamp() are within
1441 # a second of each other.
1442 tolerance = timedelta(seconds=1)
1443 for dummy in range(3):
1444 from_now = self.theclass.utcnow()
1445 from_timestamp = self.theclass.utcfromtimestamp(time.time())
1446 if abs(from_timestamp - from_now) <= tolerance:
1447 break
1448 # Else try again a few times.
1449 self.failUnless(abs(from_timestamp - from_now) <= tolerance)
1450
Skip Montanaro0af3ade2005-01-13 04:12:31 +00001451 def test_strptime(self):
1452 import time
1453
1454 string = '2004-12-01 13:02:47'
1455 format = '%Y-%m-%d %H:%M:%S'
1456 expected = self.theclass(*(time.strptime(string, format)[0:6]))
1457 got = self.theclass.strptime(string, format)
1458 self.assertEqual(expected, got)
1459
Tim Peters2a799bf2002-12-16 20:18:38 +00001460 def test_more_timetuple(self):
1461 # This tests fields beyond those tested by the TestDate.test_timetuple.
1462 t = self.theclass(2004, 12, 31, 6, 22, 33)
1463 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1464 self.assertEqual(t.timetuple(),
1465 (t.year, t.month, t.day,
1466 t.hour, t.minute, t.second,
1467 t.weekday(),
1468 t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1469 -1))
1470 tt = t.timetuple()
1471 self.assertEqual(tt.tm_year, t.year)
1472 self.assertEqual(tt.tm_mon, t.month)
1473 self.assertEqual(tt.tm_mday, t.day)
1474 self.assertEqual(tt.tm_hour, t.hour)
1475 self.assertEqual(tt.tm_min, t.minute)
1476 self.assertEqual(tt.tm_sec, t.second)
1477 self.assertEqual(tt.tm_wday, t.weekday())
1478 self.assertEqual(tt.tm_yday, t.toordinal() -
1479 date(t.year, 1, 1).toordinal() + 1)
1480 self.assertEqual(tt.tm_isdst, -1)
1481
1482 def test_more_strftime(self):
1483 # This tests fields beyond those tested by the TestDate.test_strftime.
1484 t = self.theclass(2004, 12, 31, 6, 22, 33)
1485 self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
1486 "12 31 04 33 22 06 366")
1487
1488 def test_extract(self):
1489 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1490 self.assertEqual(dt.date(), date(2002, 3, 4))
1491 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1492
1493 def test_combine(self):
1494 d = date(2002, 3, 4)
1495 t = time(18, 45, 3, 1234)
1496 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1497 combine = self.theclass.combine
1498 dt = combine(d, t)
1499 self.assertEqual(dt, expected)
1500
1501 dt = combine(time=t, date=d)
1502 self.assertEqual(dt, expected)
1503
1504 self.assertEqual(d, dt.date())
1505 self.assertEqual(t, dt.time())
1506 self.assertEqual(dt, combine(dt.date(), dt.time()))
1507
1508 self.assertRaises(TypeError, combine) # need an arg
1509 self.assertRaises(TypeError, combine, d) # need two args
1510 self.assertRaises(TypeError, combine, t, d) # args reversed
1511 self.assertRaises(TypeError, combine, d, t, 1) # too many args
1512 self.assertRaises(TypeError, combine, "date", "time") # wrong types
1513
Tim Peters12bf3392002-12-24 05:41:27 +00001514 def test_replace(self):
1515 cls = self.theclass
1516 args = [1, 2, 3, 4, 5, 6, 7]
1517 base = cls(*args)
1518 self.assertEqual(base, base.replace())
1519
1520 i = 0
1521 for name, newval in (("year", 2),
1522 ("month", 3),
1523 ("day", 4),
1524 ("hour", 5),
1525 ("minute", 6),
1526 ("second", 7),
1527 ("microsecond", 8)):
1528 newargs = args[:]
1529 newargs[i] = newval
1530 expected = cls(*newargs)
1531 got = base.replace(**{name: newval})
1532 self.assertEqual(expected, got)
1533 i += 1
1534
1535 # Out of bounds.
1536 base = cls(2000, 2, 29)
1537 self.assertRaises(ValueError, base.replace, year=2001)
1538
Tim Peters80475bb2002-12-25 07:40:55 +00001539 def test_astimezone(self):
Tim Peters52dcce22003-01-23 16:36:11 +00001540 # Pretty boring! The TZ test is more interesting here. astimezone()
1541 # simply can't be applied to a naive object.
Tim Peters80475bb2002-12-25 07:40:55 +00001542 dt = self.theclass.now()
1543 f = FixedOffset(44, "")
Tim Peters80475bb2002-12-25 07:40:55 +00001544 self.assertRaises(TypeError, dt.astimezone) # not enough args
1545 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1546 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
Tim Peters52dcce22003-01-23 16:36:11 +00001547 self.assertRaises(ValueError, dt.astimezone, f) # naive
1548 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive
Tim Peters80475bb2002-12-25 07:40:55 +00001549
Tim Peters52dcce22003-01-23 16:36:11 +00001550 class Bogus(tzinfo):
1551 def utcoffset(self, dt): return None
1552 def dst(self, dt): return timedelta(0)
1553 bog = Bogus()
1554 self.assertRaises(ValueError, dt.astimezone, bog) # naive
1555
1556 class AlsoBogus(tzinfo):
1557 def utcoffset(self, dt): return timedelta(0)
1558 def dst(self, dt): return None
1559 alsobog = AlsoBogus()
1560 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
Tim Peters12bf3392002-12-24 05:41:27 +00001561
Tim Petersa98924a2003-05-17 05:55:19 +00001562 def test_subclass_datetime(self):
1563
1564 class C(self.theclass):
1565 theAnswer = 42
1566
1567 def __new__(cls, *args, **kws):
1568 temp = kws.copy()
1569 extra = temp.pop('extra')
1570 result = self.theclass.__new__(cls, *args, **temp)
1571 result.extra = extra
1572 return result
1573
1574 def newmeth(self, start):
1575 return start + self.year + self.month + self.second
1576
1577 args = 2003, 4, 14, 12, 13, 41
1578
1579 dt1 = self.theclass(*args)
1580 dt2 = C(*args, **{'extra': 7})
1581
1582 self.assertEqual(dt2.__class__, C)
1583 self.assertEqual(dt2.theAnswer, 42)
1584 self.assertEqual(dt2.extra, 7)
1585 self.assertEqual(dt1.toordinal(), dt2.toordinal())
1586 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1587 dt1.second - 7)
1588
Tim Peters604c0132004-06-07 23:04:33 +00001589class SubclassTime(time):
1590 sub_var = 1
1591
Tim Peters07534a62003-02-07 22:50:28 +00001592class TestTime(HarmlessMixedComparison):
Tim Peters2a799bf2002-12-16 20:18:38 +00001593
1594 theclass = time
1595
1596 def test_basic_attributes(self):
1597 t = self.theclass(12, 0)
1598 self.assertEqual(t.hour, 12)
1599 self.assertEqual(t.minute, 0)
1600 self.assertEqual(t.second, 0)
1601 self.assertEqual(t.microsecond, 0)
1602
1603 def test_basic_attributes_nonzero(self):
1604 # Make sure all attributes are non-zero so bugs in
1605 # bit-shifting access show up.
1606 t = self.theclass(12, 59, 59, 8000)
1607 self.assertEqual(t.hour, 12)
1608 self.assertEqual(t.minute, 59)
1609 self.assertEqual(t.second, 59)
1610 self.assertEqual(t.microsecond, 8000)
1611
1612 def test_roundtrip(self):
1613 t = self.theclass(1, 2, 3, 4)
1614
1615 # Verify t -> string -> time identity.
1616 s = repr(t)
1617 self.failUnless(s.startswith('datetime.'))
1618 s = s[9:]
1619 t2 = eval(s)
1620 self.assertEqual(t, t2)
1621
1622 # Verify identity via reconstructing from pieces.
1623 t2 = self.theclass(t.hour, t.minute, t.second,
1624 t.microsecond)
1625 self.assertEqual(t, t2)
1626
1627 def test_comparing(self):
1628 args = [1, 2, 3, 4]
1629 t1 = self.theclass(*args)
1630 t2 = self.theclass(*args)
1631 self.failUnless(t1 == t2)
1632 self.failUnless(t1 <= t2)
1633 self.failUnless(t1 >= t2)
1634 self.failUnless(not t1 != t2)
1635 self.failUnless(not t1 < t2)
1636 self.failUnless(not t1 > t2)
1637 self.assertEqual(cmp(t1, t2), 0)
1638 self.assertEqual(cmp(t2, t1), 0)
1639
1640 for i in range(len(args)):
1641 newargs = args[:]
1642 newargs[i] = args[i] + 1
1643 t2 = self.theclass(*newargs) # this is larger than t1
1644 self.failUnless(t1 < t2)
1645 self.failUnless(t2 > t1)
1646 self.failUnless(t1 <= t2)
1647 self.failUnless(t2 >= t1)
1648 self.failUnless(t1 != t2)
1649 self.failUnless(t2 != t1)
1650 self.failUnless(not t1 == t2)
1651 self.failUnless(not t2 == t1)
1652 self.failUnless(not t1 > t2)
1653 self.failUnless(not t2 < t1)
1654 self.failUnless(not t1 >= t2)
1655 self.failUnless(not t2 <= t1)
1656 self.assertEqual(cmp(t1, t2), -1)
1657 self.assertEqual(cmp(t2, t1), 1)
1658
Tim Peters68124bb2003-02-08 03:46:31 +00001659 for badarg in OTHERSTUFF:
Tim Peters07534a62003-02-07 22:50:28 +00001660 self.assertEqual(t1 == badarg, False)
1661 self.assertEqual(t1 != badarg, True)
1662 self.assertEqual(badarg == t1, False)
1663 self.assertEqual(badarg != t1, True)
1664
Tim Peters2a799bf2002-12-16 20:18:38 +00001665 self.assertRaises(TypeError, lambda: t1 <= badarg)
1666 self.assertRaises(TypeError, lambda: t1 < badarg)
1667 self.assertRaises(TypeError, lambda: t1 > badarg)
1668 self.assertRaises(TypeError, lambda: t1 >= badarg)
Tim Peters2a799bf2002-12-16 20:18:38 +00001669 self.assertRaises(TypeError, lambda: badarg <= t1)
1670 self.assertRaises(TypeError, lambda: badarg < t1)
1671 self.assertRaises(TypeError, lambda: badarg > t1)
1672 self.assertRaises(TypeError, lambda: badarg >= t1)
1673
1674 def test_bad_constructor_arguments(self):
1675 # bad hours
1676 self.theclass(0, 0) # no exception
1677 self.theclass(23, 0) # no exception
1678 self.assertRaises(ValueError, self.theclass, -1, 0)
1679 self.assertRaises(ValueError, self.theclass, 24, 0)
1680 # bad minutes
1681 self.theclass(23, 0) # no exception
1682 self.theclass(23, 59) # no exception
1683 self.assertRaises(ValueError, self.theclass, 23, -1)
1684 self.assertRaises(ValueError, self.theclass, 23, 60)
1685 # bad seconds
1686 self.theclass(23, 59, 0) # no exception
1687 self.theclass(23, 59, 59) # no exception
1688 self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1689 self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1690 # bad microseconds
1691 self.theclass(23, 59, 59, 0) # no exception
1692 self.theclass(23, 59, 59, 999999) # no exception
1693 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1694 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1695
1696 def test_hash_equality(self):
1697 d = self.theclass(23, 30, 17)
1698 e = self.theclass(23, 30, 17)
1699 self.assertEqual(d, e)
1700 self.assertEqual(hash(d), hash(e))
1701
1702 dic = {d: 1}
1703 dic[e] = 2
1704 self.assertEqual(len(dic), 1)
1705 self.assertEqual(dic[d], 2)
1706 self.assertEqual(dic[e], 2)
1707
1708 d = self.theclass(0, 5, 17)
1709 e = self.theclass(0, 5, 17)
1710 self.assertEqual(d, e)
1711 self.assertEqual(hash(d), hash(e))
1712
1713 dic = {d: 1}
1714 dic[e] = 2
1715 self.assertEqual(len(dic), 1)
1716 self.assertEqual(dic[d], 2)
1717 self.assertEqual(dic[e], 2)
1718
1719 def test_isoformat(self):
1720 t = self.theclass(4, 5, 1, 123)
1721 self.assertEqual(t.isoformat(), "04:05:01.000123")
1722 self.assertEqual(t.isoformat(), str(t))
1723
1724 t = self.theclass()
1725 self.assertEqual(t.isoformat(), "00:00:00")
1726 self.assertEqual(t.isoformat(), str(t))
1727
1728 t = self.theclass(microsecond=1)
1729 self.assertEqual(t.isoformat(), "00:00:00.000001")
1730 self.assertEqual(t.isoformat(), str(t))
1731
1732 t = self.theclass(microsecond=10)
1733 self.assertEqual(t.isoformat(), "00:00:00.000010")
1734 self.assertEqual(t.isoformat(), str(t))
1735
1736 t = self.theclass(microsecond=100)
1737 self.assertEqual(t.isoformat(), "00:00:00.000100")
1738 self.assertEqual(t.isoformat(), str(t))
1739
1740 t = self.theclass(microsecond=1000)
1741 self.assertEqual(t.isoformat(), "00:00:00.001000")
1742 self.assertEqual(t.isoformat(), str(t))
1743
1744 t = self.theclass(microsecond=10000)
1745 self.assertEqual(t.isoformat(), "00:00:00.010000")
1746 self.assertEqual(t.isoformat(), str(t))
1747
1748 t = self.theclass(microsecond=100000)
1749 self.assertEqual(t.isoformat(), "00:00:00.100000")
1750 self.assertEqual(t.isoformat(), str(t))
1751
Martin v. Löwis4c11a922007-02-08 09:13:36 +00001752 def test_1653736(self):
1753 # verify it doesn't accept extra keyword arguments
1754 t = self.theclass(second=1)
1755 self.assertRaises(TypeError, t.isoformat, foo=3)
1756
Tim Peters2a799bf2002-12-16 20:18:38 +00001757 def test_strftime(self):
1758 t = self.theclass(1, 2, 3, 4)
1759 self.assertEqual(t.strftime('%H %M %S'), "01 02 03")
1760 # A naive object replaces %z and %Z with empty strings.
1761 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1762
1763 def test_str(self):
1764 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1765 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1766 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1767 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1768 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1769
1770 def test_repr(self):
1771 name = 'datetime.' + self.theclass.__name__
1772 self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1773 "%s(1, 2, 3, 4)" % name)
1774 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1775 "%s(10, 2, 3, 4000)" % name)
1776 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1777 "%s(0, 2, 3, 400000)" % name)
1778 self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1779 "%s(12, 2, 3)" % name)
1780 self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1781 "%s(23, 15)" % name)
1782
1783 def test_resolution_info(self):
1784 self.assert_(isinstance(self.theclass.min, self.theclass))
1785 self.assert_(isinstance(self.theclass.max, self.theclass))
1786 self.assert_(isinstance(self.theclass.resolution, timedelta))
1787 self.assert_(self.theclass.max > self.theclass.min)
1788
1789 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00001790 args = 20, 59, 16, 64**2
1791 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00001792 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00001793 green = pickler.dumps(orig, proto)
1794 derived = unpickler.loads(green)
1795 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00001796
Tim Peters604c0132004-06-07 23:04:33 +00001797 def test_pickling_subclass_time(self):
1798 args = 20, 59, 16, 64**2
1799 orig = SubclassTime(*args)
1800 for pickler, unpickler, proto in pickle_choices:
1801 green = pickler.dumps(orig, proto)
1802 derived = unpickler.loads(green)
1803 self.assertEqual(orig, derived)
1804
Tim Peters2a799bf2002-12-16 20:18:38 +00001805 def test_bool(self):
1806 cls = self.theclass
1807 self.failUnless(cls(1))
1808 self.failUnless(cls(0, 1))
1809 self.failUnless(cls(0, 0, 1))
1810 self.failUnless(cls(0, 0, 0, 1))
1811 self.failUnless(not cls(0))
1812 self.failUnless(not cls())
1813
Tim Peters12bf3392002-12-24 05:41:27 +00001814 def test_replace(self):
1815 cls = self.theclass
1816 args = [1, 2, 3, 4]
1817 base = cls(*args)
1818 self.assertEqual(base, base.replace())
1819
1820 i = 0
1821 for name, newval in (("hour", 5),
1822 ("minute", 6),
1823 ("second", 7),
1824 ("microsecond", 8)):
1825 newargs = args[:]
1826 newargs[i] = newval
1827 expected = cls(*newargs)
1828 got = base.replace(**{name: newval})
1829 self.assertEqual(expected, got)
1830 i += 1
1831
1832 # Out of bounds.
1833 base = cls(1)
1834 self.assertRaises(ValueError, base.replace, hour=24)
1835 self.assertRaises(ValueError, base.replace, minute=-1)
1836 self.assertRaises(ValueError, base.replace, second=100)
1837 self.assertRaises(ValueError, base.replace, microsecond=1000000)
1838
Tim Petersa98924a2003-05-17 05:55:19 +00001839 def test_subclass_time(self):
1840
1841 class C(self.theclass):
1842 theAnswer = 42
1843
1844 def __new__(cls, *args, **kws):
1845 temp = kws.copy()
1846 extra = temp.pop('extra')
1847 result = self.theclass.__new__(cls, *args, **temp)
1848 result.extra = extra
1849 return result
1850
1851 def newmeth(self, start):
1852 return start + self.hour + self.second
1853
1854 args = 4, 5, 6
1855
1856 dt1 = self.theclass(*args)
1857 dt2 = C(*args, **{'extra': 7})
1858
1859 self.assertEqual(dt2.__class__, C)
1860 self.assertEqual(dt2.theAnswer, 42)
1861 self.assertEqual(dt2.extra, 7)
1862 self.assertEqual(dt1.isoformat(), dt2.isoformat())
1863 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
1864
Armin Rigof4afb212005-11-07 07:15:48 +00001865 def test_backdoor_resistance(self):
1866 # see TestDate.test_backdoor_resistance().
1867 base = '2:59.0'
1868 for hour_byte in ' ', '9', chr(24), '\xff':
1869 self.assertRaises(TypeError, self.theclass,
1870 hour_byte + base[1:])
1871
Tim Peters855fe882002-12-22 03:43:39 +00001872# A mixin for classes with a tzinfo= argument. Subclasses must define
1873# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001874# must be legit (which is true for time and datetime).
Tim Peters855fe882002-12-22 03:43:39 +00001875class TZInfoBase(unittest.TestCase):
Tim Peters2a799bf2002-12-16 20:18:38 +00001876
Tim Petersbad8ff02002-12-30 20:52:32 +00001877 def test_argument_passing(self):
1878 cls = self.theclass
Tim Peters0bf60bd2003-01-08 20:40:01 +00001879 # A datetime passes itself on, a time passes None.
Tim Petersbad8ff02002-12-30 20:52:32 +00001880 class introspective(tzinfo):
1881 def tzname(self, dt): return dt and "real" or "none"
Tim Peters397301e2003-01-02 21:28:08 +00001882 def utcoffset(self, dt):
1883 return timedelta(minutes = dt and 42 or -42)
Tim Petersbad8ff02002-12-30 20:52:32 +00001884 dst = utcoffset
1885
1886 obj = cls(1, 2, 3, tzinfo=introspective())
1887
Tim Peters0bf60bd2003-01-08 20:40:01 +00001888 expected = cls is time and "none" or "real"
Tim Petersbad8ff02002-12-30 20:52:32 +00001889 self.assertEqual(obj.tzname(), expected)
1890
Tim Peters0bf60bd2003-01-08 20:40:01 +00001891 expected = timedelta(minutes=(cls is time and -42 or 42))
Tim Petersbad8ff02002-12-30 20:52:32 +00001892 self.assertEqual(obj.utcoffset(), expected)
1893 self.assertEqual(obj.dst(), expected)
1894
Tim Peters855fe882002-12-22 03:43:39 +00001895 def test_bad_tzinfo_classes(self):
1896 cls = self.theclass
1897 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
Tim Peters2a799bf2002-12-16 20:18:38 +00001898
Tim Peters855fe882002-12-22 03:43:39 +00001899 class NiceTry(object):
1900 def __init__(self): pass
1901 def utcoffset(self, dt): pass
1902 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
1903
1904 class BetterTry(tzinfo):
1905 def __init__(self): pass
1906 def utcoffset(self, dt): pass
1907 b = BetterTry()
1908 t = cls(1, 1, 1, tzinfo=b)
1909 self.failUnless(t.tzinfo is b)
1910
1911 def test_utc_offset_out_of_bounds(self):
1912 class Edgy(tzinfo):
1913 def __init__(self, offset):
Tim Peters397301e2003-01-02 21:28:08 +00001914 self.offset = timedelta(minutes=offset)
Tim Peters855fe882002-12-22 03:43:39 +00001915 def utcoffset(self, dt):
1916 return self.offset
1917
1918 cls = self.theclass
1919 for offset, legit in ((-1440, False),
1920 (-1439, True),
1921 (1439, True),
1922 (1440, False)):
Tim Peters0bf60bd2003-01-08 20:40:01 +00001923 if cls is time:
Tim Peters855fe882002-12-22 03:43:39 +00001924 t = cls(1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001925 elif cls is datetime:
Tim Peters855fe882002-12-22 03:43:39 +00001926 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
Tim Peters0bf60bd2003-01-08 20:40:01 +00001927 else:
1928 assert 0, "impossible"
Tim Peters855fe882002-12-22 03:43:39 +00001929 if legit:
1930 aofs = abs(offset)
1931 h, m = divmod(aofs, 60)
1932 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
Tim Peters0bf60bd2003-01-08 20:40:01 +00001933 if isinstance(t, datetime):
Tim Peters855fe882002-12-22 03:43:39 +00001934 t = t.timetz()
1935 self.assertEqual(str(t), "01:02:03" + tag)
1936 else:
1937 self.assertRaises(ValueError, str, t)
1938
1939 def test_tzinfo_classes(self):
1940 cls = self.theclass
1941 class C1(tzinfo):
1942 def utcoffset(self, dt): return None
1943 def dst(self, dt): return None
1944 def tzname(self, dt): return None
1945 for t in (cls(1, 1, 1),
1946 cls(1, 1, 1, tzinfo=None),
1947 cls(1, 1, 1, tzinfo=C1())):
1948 self.failUnless(t.utcoffset() is None)
1949 self.failUnless(t.dst() is None)
1950 self.failUnless(t.tzname() is None)
1951
Tim Peters855fe882002-12-22 03:43:39 +00001952 class C3(tzinfo):
1953 def utcoffset(self, dt): return timedelta(minutes=-1439)
1954 def dst(self, dt): return timedelta(minutes=1439)
1955 def tzname(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001956 t = cls(1, 1, 1, tzinfo=C3())
1957 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
1958 self.assertEqual(t.dst(), timedelta(minutes=1439))
1959 self.assertEqual(t.tzname(), "aname")
Tim Peters855fe882002-12-22 03:43:39 +00001960
1961 # Wrong types.
1962 class C4(tzinfo):
1963 def utcoffset(self, dt): return "aname"
Tim Peters397301e2003-01-02 21:28:08 +00001964 def dst(self, dt): return 7
Tim Peters855fe882002-12-22 03:43:39 +00001965 def tzname(self, dt): return 0
1966 t = cls(1, 1, 1, tzinfo=C4())
1967 self.assertRaises(TypeError, t.utcoffset)
1968 self.assertRaises(TypeError, t.dst)
1969 self.assertRaises(TypeError, t.tzname)
1970
1971 # Offset out of range.
Tim Peters855fe882002-12-22 03:43:39 +00001972 class C6(tzinfo):
1973 def utcoffset(self, dt): return timedelta(hours=-24)
1974 def dst(self, dt): return timedelta(hours=24)
Tim Peters397301e2003-01-02 21:28:08 +00001975 t = cls(1, 1, 1, tzinfo=C6())
1976 self.assertRaises(ValueError, t.utcoffset)
1977 self.assertRaises(ValueError, t.dst)
Tim Peters855fe882002-12-22 03:43:39 +00001978
1979 # Not a whole number of minutes.
1980 class C7(tzinfo):
1981 def utcoffset(self, dt): return timedelta(seconds=61)
1982 def dst(self, dt): return timedelta(microseconds=-81)
1983 t = cls(1, 1, 1, tzinfo=C7())
1984 self.assertRaises(ValueError, t.utcoffset)
1985 self.assertRaises(ValueError, t.dst)
1986
Tim Peters4c0db782002-12-26 05:01:19 +00001987 def test_aware_compare(self):
1988 cls = self.theclass
1989
Tim Peters60c76e42002-12-27 00:41:11 +00001990 # Ensure that utcoffset() gets ignored if the comparands have
1991 # the same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00001992 class OperandDependentOffset(tzinfo):
1993 def utcoffset(self, t):
1994 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00001995 # d0 and d1 equal after adjustment
1996 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00001997 else:
Tim Peters397301e2003-01-02 21:28:08 +00001998 # d2 off in the weeds
1999 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002000
2001 base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2002 d0 = base.replace(minute=3)
2003 d1 = base.replace(minute=9)
2004 d2 = base.replace(minute=11)
2005 for x in d0, d1, d2:
2006 for y in d0, d1, d2:
2007 got = cmp(x, y)
Tim Peters60c76e42002-12-27 00:41:11 +00002008 expected = cmp(x.minute, y.minute)
2009 self.assertEqual(got, expected)
2010
2011 # However, if they're different members, uctoffset is not ignored.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002012 # Note that a time can't actually have an operand-depedent offset,
2013 # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2014 # so skip this test for time.
2015 if cls is not time:
Tim Petersbad8ff02002-12-30 20:52:32 +00002016 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2017 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2018 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2019 for x in d0, d1, d2:
2020 for y in d0, d1, d2:
2021 got = cmp(x, y)
2022 if (x is d0 or x is d1) and (y is d0 or y is d1):
2023 expected = 0
2024 elif x is y is d2:
2025 expected = 0
2026 elif x is d2:
2027 expected = -1
2028 else:
2029 assert y is d2
2030 expected = 1
2031 self.assertEqual(got, expected)
Tim Peters4c0db782002-12-26 05:01:19 +00002032
Tim Peters855fe882002-12-22 03:43:39 +00002033
Tim Peters0bf60bd2003-01-08 20:40:01 +00002034# Testing time objects with a non-None tzinfo.
Tim Peters855fe882002-12-22 03:43:39 +00002035class TestTimeTZ(TestTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002036 theclass = time
Tim Peters2a799bf2002-12-16 20:18:38 +00002037
2038 def test_empty(self):
2039 t = self.theclass()
2040 self.assertEqual(t.hour, 0)
2041 self.assertEqual(t.minute, 0)
2042 self.assertEqual(t.second, 0)
2043 self.assertEqual(t.microsecond, 0)
2044 self.failUnless(t.tzinfo is None)
2045
Tim Peters2a799bf2002-12-16 20:18:38 +00002046 def test_zones(self):
2047 est = FixedOffset(-300, "EST", 1)
2048 utc = FixedOffset(0, "UTC", -2)
2049 met = FixedOffset(60, "MET", 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002050 t1 = time( 7, 47, tzinfo=est)
2051 t2 = time(12, 47, tzinfo=utc)
2052 t3 = time(13, 47, tzinfo=met)
2053 t4 = time(microsecond=40)
2054 t5 = time(microsecond=40, tzinfo=utc)
Tim Peters2a799bf2002-12-16 20:18:38 +00002055
2056 self.assertEqual(t1.tzinfo, est)
2057 self.assertEqual(t2.tzinfo, utc)
2058 self.assertEqual(t3.tzinfo, met)
2059 self.failUnless(t4.tzinfo is None)
2060 self.assertEqual(t5.tzinfo, utc)
2061
Tim Peters855fe882002-12-22 03:43:39 +00002062 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2063 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2064 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002065 self.failUnless(t4.utcoffset() is None)
2066 self.assertRaises(TypeError, t1.utcoffset, "no args")
2067
2068 self.assertEqual(t1.tzname(), "EST")
2069 self.assertEqual(t2.tzname(), "UTC")
2070 self.assertEqual(t3.tzname(), "MET")
2071 self.failUnless(t4.tzname() is None)
2072 self.assertRaises(TypeError, t1.tzname, "no args")
2073
Tim Peters855fe882002-12-22 03:43:39 +00002074 self.assertEqual(t1.dst(), timedelta(minutes=1))
2075 self.assertEqual(t2.dst(), timedelta(minutes=-2))
2076 self.assertEqual(t3.dst(), timedelta(minutes=3))
Tim Peters2a799bf2002-12-16 20:18:38 +00002077 self.failUnless(t4.dst() is None)
2078 self.assertRaises(TypeError, t1.dst, "no args")
2079
2080 self.assertEqual(hash(t1), hash(t2))
2081 self.assertEqual(hash(t1), hash(t3))
2082 self.assertEqual(hash(t2), hash(t3))
2083
2084 self.assertEqual(t1, t2)
2085 self.assertEqual(t1, t3)
2086 self.assertEqual(t2, t3)
2087 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2088 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2089 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2090
2091 self.assertEqual(str(t1), "07:47:00-05:00")
2092 self.assertEqual(str(t2), "12:47:00+00:00")
2093 self.assertEqual(str(t3), "13:47:00+01:00")
2094 self.assertEqual(str(t4), "00:00:00.000040")
2095 self.assertEqual(str(t5), "00:00:00.000040+00:00")
2096
2097 self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2098 self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2099 self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2100 self.assertEqual(t4.isoformat(), "00:00:00.000040")
2101 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2102
Tim Peters0bf60bd2003-01-08 20:40:01 +00002103 d = 'datetime.time'
Tim Peters2a799bf2002-12-16 20:18:38 +00002104 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2105 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2106 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2107 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2108 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2109
2110 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2111 "07:47:00 %Z=EST %z=-0500")
2112 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2113 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2114
2115 yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002116 t1 = time(23, 59, tzinfo=yuck)
Tim Peters2a799bf2002-12-16 20:18:38 +00002117 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2118 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2119
Tim Petersb92bb712002-12-21 17:44:07 +00002120 # Check that an invalid tzname result raises an exception.
2121 class Badtzname(tzinfo):
2122 def tzname(self, dt): return 42
Tim Peters0bf60bd2003-01-08 20:40:01 +00002123 t = time(2, 3, 4, tzinfo=Badtzname())
Tim Petersb92bb712002-12-21 17:44:07 +00002124 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2125 self.assertRaises(TypeError, t.strftime, "%Z")
Tim Peters2a799bf2002-12-16 20:18:38 +00002126
2127 def test_hash_edge_cases(self):
2128 # Offsets that overflow a basic time.
2129 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2130 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2131 self.assertEqual(hash(t1), hash(t2))
2132
2133 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2134 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2135 self.assertEqual(hash(t1), hash(t2))
2136
Tim Peters2a799bf2002-12-16 20:18:38 +00002137 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002138 # Try one without a tzinfo.
2139 args = 20, 59, 16, 64**2
2140 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002141 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002142 green = pickler.dumps(orig, proto)
2143 derived = unpickler.loads(green)
2144 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002145
2146 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002147 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002148 orig = self.theclass(5, 6, 7, tzinfo=tinfo)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002149 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002150 green = pickler.dumps(orig, proto)
2151 derived = unpickler.loads(green)
2152 self.assertEqual(orig, derived)
2153 self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
2154 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2155 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002156
2157 def test_more_bool(self):
2158 # Test cases with non-None tzinfo.
2159 cls = self.theclass
2160
2161 t = cls(0, tzinfo=FixedOffset(-300, ""))
2162 self.failUnless(t)
2163
2164 t = cls(5, tzinfo=FixedOffset(-300, ""))
2165 self.failUnless(t)
2166
2167 t = cls(5, tzinfo=FixedOffset(300, ""))
2168 self.failUnless(not t)
2169
2170 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2171 self.failUnless(not t)
2172
2173 # Mostly ensuring this doesn't overflow internally.
2174 t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2175 self.failUnless(t)
2176
2177 # But this should yield a value error -- the utcoffset is bogus.
2178 t = cls(0, tzinfo=FixedOffset(24*60, ""))
2179 self.assertRaises(ValueError, lambda: bool(t))
2180
2181 # Likewise.
2182 t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2183 self.assertRaises(ValueError, lambda: bool(t))
2184
Tim Peters12bf3392002-12-24 05:41:27 +00002185 def test_replace(self):
2186 cls = self.theclass
2187 z100 = FixedOffset(100, "+100")
2188 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2189 args = [1, 2, 3, 4, z100]
2190 base = cls(*args)
2191 self.assertEqual(base, base.replace())
2192
2193 i = 0
2194 for name, newval in (("hour", 5),
2195 ("minute", 6),
2196 ("second", 7),
2197 ("microsecond", 8),
2198 ("tzinfo", zm200)):
2199 newargs = args[:]
2200 newargs[i] = newval
2201 expected = cls(*newargs)
2202 got = base.replace(**{name: newval})
2203 self.assertEqual(expected, got)
2204 i += 1
2205
2206 # Ensure we can get rid of a tzinfo.
2207 self.assertEqual(base.tzname(), "+100")
2208 base2 = base.replace(tzinfo=None)
2209 self.failUnless(base2.tzinfo is None)
2210 self.failUnless(base2.tzname() is None)
2211
2212 # Ensure we can add one.
2213 base3 = base2.replace(tzinfo=z100)
2214 self.assertEqual(base, base3)
2215 self.failUnless(base.tzinfo is base3.tzinfo)
2216
2217 # Out of bounds.
2218 base = cls(1)
2219 self.assertRaises(ValueError, base.replace, hour=24)
2220 self.assertRaises(ValueError, base.replace, minute=-1)
2221 self.assertRaises(ValueError, base.replace, second=100)
2222 self.assertRaises(ValueError, base.replace, microsecond=1000000)
2223
Tim Peters60c76e42002-12-27 00:41:11 +00002224 def test_mixed_compare(self):
2225 t1 = time(1, 2, 3)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002226 t2 = time(1, 2, 3)
Tim Peters60c76e42002-12-27 00:41:11 +00002227 self.assertEqual(t1, t2)
2228 t2 = t2.replace(tzinfo=None)
2229 self.assertEqual(t1, t2)
2230 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2231 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002232 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2233 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002234
Tim Peters0bf60bd2003-01-08 20:40:01 +00002235 # In time w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002236 class Varies(tzinfo):
2237 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002238 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002239 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002240 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002241 return self.offset
2242
2243 v = Varies()
2244 t1 = t2.replace(tzinfo=v)
2245 t2 = t2.replace(tzinfo=v)
2246 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2247 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2248 self.assertEqual(t1, t2)
2249
2250 # But if they're not identical, it isn't ignored.
2251 t2 = t2.replace(tzinfo=Varies())
2252 self.failUnless(t1 < t2) # t1's offset counter still going up
2253
Tim Petersa98924a2003-05-17 05:55:19 +00002254 def test_subclass_timetz(self):
2255
2256 class C(self.theclass):
2257 theAnswer = 42
2258
2259 def __new__(cls, *args, **kws):
2260 temp = kws.copy()
2261 extra = temp.pop('extra')
2262 result = self.theclass.__new__(cls, *args, **temp)
2263 result.extra = extra
2264 return result
2265
2266 def newmeth(self, start):
2267 return start + self.hour + self.second
2268
2269 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2270
2271 dt1 = self.theclass(*args)
2272 dt2 = C(*args, **{'extra': 7})
2273
2274 self.assertEqual(dt2.__class__, C)
2275 self.assertEqual(dt2.theAnswer, 42)
2276 self.assertEqual(dt2.extra, 7)
2277 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2278 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2279
Tim Peters4c0db782002-12-26 05:01:19 +00002280
Tim Peters0bf60bd2003-01-08 20:40:01 +00002281# Testing datetime objects with a non-None tzinfo.
2282
Tim Peters855fe882002-12-22 03:43:39 +00002283class TestDateTimeTZ(TestDateTime, TZInfoBase):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002284 theclass = datetime
Tim Peters2a799bf2002-12-16 20:18:38 +00002285
2286 def test_trivial(self):
2287 dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2288 self.assertEqual(dt.year, 1)
2289 self.assertEqual(dt.month, 2)
2290 self.assertEqual(dt.day, 3)
2291 self.assertEqual(dt.hour, 4)
2292 self.assertEqual(dt.minute, 5)
2293 self.assertEqual(dt.second, 6)
2294 self.assertEqual(dt.microsecond, 7)
2295 self.assertEqual(dt.tzinfo, None)
2296
2297 def test_even_more_compare(self):
2298 # The test_compare() and test_more_compare() inherited from TestDate
2299 # and TestDateTime covered non-tzinfo cases.
2300
2301 # Smallest possible after UTC adjustment.
2302 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2303 # Largest possible after UTC adjustment.
2304 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2305 tzinfo=FixedOffset(-1439, ""))
2306
2307 # Make sure those compare correctly, and w/o overflow.
2308 self.failUnless(t1 < t2)
2309 self.failUnless(t1 != t2)
2310 self.failUnless(t2 > t1)
2311
2312 self.failUnless(t1 == t1)
2313 self.failUnless(t2 == t2)
2314
2315 # Equal afer adjustment.
2316 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2317 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2318 self.assertEqual(t1, t2)
2319
2320 # Change t1 not to subtract a minute, and t1 should be larger.
2321 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2322 self.failUnless(t1 > t2)
2323
2324 # Change t1 to subtract 2 minutes, and t1 should be smaller.
2325 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2326 self.failUnless(t1 < t2)
2327
2328 # Back to the original t1, but make seconds resolve it.
2329 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2330 second=1)
2331 self.failUnless(t1 > t2)
2332
2333 # Likewise, but make microseconds resolve it.
2334 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2335 microsecond=1)
2336 self.failUnless(t1 > t2)
2337
2338 # Make t2 naive and it should fail.
2339 t2 = self.theclass.min
2340 self.assertRaises(TypeError, lambda: t1 == t2)
2341 self.assertEqual(t2, t2)
2342
2343 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2344 class Naive(tzinfo):
2345 def utcoffset(self, dt): return None
2346 t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2347 self.assertRaises(TypeError, lambda: t1 == t2)
2348 self.assertEqual(t2, t2)
2349
2350 # OTOH, it's OK to compare two of these mixing the two ways of being
2351 # naive.
2352 t1 = self.theclass(5, 6, 7)
2353 self.assertEqual(t1, t2)
2354
2355 # Try a bogus uctoffset.
2356 class Bogus(tzinfo):
Tim Peters397301e2003-01-02 21:28:08 +00002357 def utcoffset(self, dt):
2358 return timedelta(minutes=1440) # out of bounds
Tim Peters2a799bf2002-12-16 20:18:38 +00002359 t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2360 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
Tim Peters60c76e42002-12-27 00:41:11 +00002361 self.assertRaises(ValueError, lambda: t1 == t2)
Tim Peters2a799bf2002-12-16 20:18:38 +00002362
Tim Peters2a799bf2002-12-16 20:18:38 +00002363 def test_pickling(self):
Tim Peters2a799bf2002-12-16 20:18:38 +00002364 # Try one without a tzinfo.
2365 args = 6, 7, 23, 20, 59, 1, 64**2
2366 orig = self.theclass(*args)
Guido van Rossum177e41a2003-01-30 22:06:23 +00002367 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002368 green = pickler.dumps(orig, proto)
2369 derived = unpickler.loads(green)
2370 self.assertEqual(orig, derived)
Tim Peters2a799bf2002-12-16 20:18:38 +00002371
2372 # Try one with a tzinfo.
Tim Petersfb8472c2002-12-21 05:04:42 +00002373 tinfo = PicklableFixedOffset(-300, 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002374 orig = self.theclass(*args, **{'tzinfo': tinfo})
Tim Petersa9bc1682003-01-11 03:39:11 +00002375 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
Guido van Rossum177e41a2003-01-30 22:06:23 +00002376 for pickler, unpickler, proto in pickle_choices:
Tim Peters96940c92003-01-31 21:55:33 +00002377 green = pickler.dumps(orig, proto)
2378 derived = unpickler.loads(green)
2379 self.assertEqual(orig, derived)
2380 self.failUnless(isinstance(derived.tzinfo,
2381 PicklableFixedOffset))
2382 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2383 self.assertEqual(derived.tzname(), 'cookie')
Tim Peters2a799bf2002-12-16 20:18:38 +00002384
2385 def test_extreme_hashes(self):
2386 # If an attempt is made to hash these via subtracting the offset
2387 # then hashing a datetime object, OverflowError results. The
2388 # Python implementation used to blow up here.
2389 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2390 hash(t)
2391 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2392 tzinfo=FixedOffset(-1439, ""))
2393 hash(t)
2394
2395 # OTOH, an OOB offset should blow up.
2396 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2397 self.assertRaises(ValueError, hash, t)
2398
2399 def test_zones(self):
2400 est = FixedOffset(-300, "EST")
2401 utc = FixedOffset(0, "UTC")
2402 met = FixedOffset(60, "MET")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002403 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est)
2404 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2405 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
Tim Peters2a799bf2002-12-16 20:18:38 +00002406 self.assertEqual(t1.tzinfo, est)
2407 self.assertEqual(t2.tzinfo, utc)
2408 self.assertEqual(t3.tzinfo, met)
Tim Peters855fe882002-12-22 03:43:39 +00002409 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2410 self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2411 self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
Tim Peters2a799bf2002-12-16 20:18:38 +00002412 self.assertEqual(t1.tzname(), "EST")
2413 self.assertEqual(t2.tzname(), "UTC")
2414 self.assertEqual(t3.tzname(), "MET")
2415 self.assertEqual(hash(t1), hash(t2))
2416 self.assertEqual(hash(t1), hash(t3))
2417 self.assertEqual(hash(t2), hash(t3))
2418 self.assertEqual(t1, t2)
2419 self.assertEqual(t1, t3)
2420 self.assertEqual(t2, t3)
2421 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2422 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2423 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002424 d = 'datetime.datetime(2002, 3, 19, '
Tim Peters2a799bf2002-12-16 20:18:38 +00002425 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2426 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2427 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2428
2429 def test_combine(self):
2430 met = FixedOffset(60, "MET")
2431 d = date(2002, 3, 4)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002432 tz = time(18, 45, 3, 1234, tzinfo=met)
2433 dt = datetime.combine(d, tz)
2434 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
Tim Peters2a799bf2002-12-16 20:18:38 +00002435 tzinfo=met))
2436
2437 def test_extract(self):
2438 met = FixedOffset(60, "MET")
2439 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2440 self.assertEqual(dt.date(), date(2002, 3, 4))
2441 self.assertEqual(dt.time(), time(18, 45, 3, 1234))
Tim Peters0bf60bd2003-01-08 20:40:01 +00002442 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
Tim Peters2a799bf2002-12-16 20:18:38 +00002443
2444 def test_tz_aware_arithmetic(self):
2445 import random
2446
2447 now = self.theclass.now()
2448 tz55 = FixedOffset(-330, "west 5:30")
Tim Peters0bf60bd2003-01-08 20:40:01 +00002449 timeaware = now.time().replace(tzinfo=tz55)
Tim Peters2a799bf2002-12-16 20:18:38 +00002450 nowaware = self.theclass.combine(now.date(), timeaware)
2451 self.failUnless(nowaware.tzinfo is tz55)
2452 self.assertEqual(nowaware.timetz(), timeaware)
2453
2454 # Can't mix aware and non-aware.
2455 self.assertRaises(TypeError, lambda: now - nowaware)
2456 self.assertRaises(TypeError, lambda: nowaware - now)
2457
Tim Peters0bf60bd2003-01-08 20:40:01 +00002458 # And adding datetime's doesn't make sense, aware or not.
Tim Peters2a799bf2002-12-16 20:18:38 +00002459 self.assertRaises(TypeError, lambda: now + nowaware)
2460 self.assertRaises(TypeError, lambda: nowaware + now)
2461 self.assertRaises(TypeError, lambda: nowaware + nowaware)
2462
2463 # Subtracting should yield 0.
2464 self.assertEqual(now - now, timedelta(0))
2465 self.assertEqual(nowaware - nowaware, timedelta(0))
2466
2467 # Adding a delta should preserve tzinfo.
2468 delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2469 nowawareplus = nowaware + delta
2470 self.failUnless(nowaware.tzinfo is tz55)
2471 nowawareplus2 = delta + nowaware
2472 self.failUnless(nowawareplus2.tzinfo is tz55)
2473 self.assertEqual(nowawareplus, nowawareplus2)
2474
2475 # that - delta should be what we started with, and that - what we
2476 # started with should be delta.
2477 diff = nowawareplus - delta
2478 self.failUnless(diff.tzinfo is tz55)
2479 self.assertEqual(nowaware, diff)
2480 self.assertRaises(TypeError, lambda: delta - nowawareplus)
2481 self.assertEqual(nowawareplus - nowaware, delta)
2482
2483 # Make up a random timezone.
2484 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
Tim Peters4c0db782002-12-26 05:01:19 +00002485 # Attach it to nowawareplus.
2486 nowawareplus = nowawareplus.replace(tzinfo=tzr)
Tim Peters2a799bf2002-12-16 20:18:38 +00002487 self.failUnless(nowawareplus.tzinfo is tzr)
2488 # Make sure the difference takes the timezone adjustments into account.
2489 got = nowaware - nowawareplus
2490 # Expected: (nowaware base - nowaware offset) -
2491 # (nowawareplus base - nowawareplus offset) =
2492 # (nowaware base - nowawareplus base) +
2493 # (nowawareplus offset - nowaware offset) =
2494 # -delta + nowawareplus offset - nowaware offset
Tim Peters855fe882002-12-22 03:43:39 +00002495 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
Tim Peters2a799bf2002-12-16 20:18:38 +00002496 self.assertEqual(got, expected)
2497
2498 # Try max possible difference.
2499 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2500 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2501 tzinfo=FixedOffset(-1439, "max"))
2502 maxdiff = max - min
2503 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2504 timedelta(minutes=2*1439))
2505
2506 def test_tzinfo_now(self):
2507 meth = self.theclass.now
2508 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2509 base = meth()
2510 # Try with and without naming the keyword.
2511 off42 = FixedOffset(42, "42")
2512 another = meth(off42)
Tim Peters10cadce2003-01-23 19:58:02 +00002513 again = meth(tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002514 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002515 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002516 # Bad argument with and w/o naming the keyword.
2517 self.assertRaises(TypeError, meth, 16)
2518 self.assertRaises(TypeError, meth, tzinfo=16)
2519 # Bad keyword name.
2520 self.assertRaises(TypeError, meth, tinfo=off42)
2521 # Too many args.
2522 self.assertRaises(TypeError, meth, off42, off42)
2523
Tim Peters10cadce2003-01-23 19:58:02 +00002524 # We don't know which time zone we're in, and don't have a tzinfo
2525 # class to represent it, so seeing whether a tz argument actually
2526 # does a conversion is tricky.
2527 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2528 utc = FixedOffset(0, "utc", 0)
2529 for dummy in range(3):
2530 now = datetime.now(weirdtz)
2531 self.failUnless(now.tzinfo is weirdtz)
2532 utcnow = datetime.utcnow().replace(tzinfo=utc)
2533 now2 = utcnow.astimezone(weirdtz)
2534 if abs(now - now2) < timedelta(seconds=30):
2535 break
2536 # Else the code is broken, or more than 30 seconds passed between
2537 # calls; assuming the latter, just try again.
2538 else:
2539 # Three strikes and we're out.
2540 self.fail("utcnow(), now(tz), or astimezone() may be broken")
2541
Tim Peters2a799bf2002-12-16 20:18:38 +00002542 def test_tzinfo_fromtimestamp(self):
2543 import time
2544 meth = self.theclass.fromtimestamp
2545 ts = time.time()
2546 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2547 base = meth(ts)
2548 # Try with and without naming the keyword.
2549 off42 = FixedOffset(42, "42")
2550 another = meth(ts, off42)
Tim Peters2a44a8d2003-01-23 20:53:10 +00002551 again = meth(ts, tz=off42)
Tim Peters2a799bf2002-12-16 20:18:38 +00002552 self.failUnless(another.tzinfo is again.tzinfo)
Tim Peters855fe882002-12-22 03:43:39 +00002553 self.assertEqual(another.utcoffset(), timedelta(minutes=42))
Tim Peters2a799bf2002-12-16 20:18:38 +00002554 # Bad argument with and w/o naming the keyword.
2555 self.assertRaises(TypeError, meth, ts, 16)
2556 self.assertRaises(TypeError, meth, ts, tzinfo=16)
2557 # Bad keyword name.
2558 self.assertRaises(TypeError, meth, ts, tinfo=off42)
2559 # Too many args.
2560 self.assertRaises(TypeError, meth, ts, off42, off42)
2561 # Too few args.
2562 self.assertRaises(TypeError, meth)
2563
Tim Peters2a44a8d2003-01-23 20:53:10 +00002564 # Try to make sure tz= actually does some conversion.
Tim Peters84407612003-02-06 16:42:14 +00002565 timestamp = 1000000000
2566 utcdatetime = datetime.utcfromtimestamp(timestamp)
2567 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2568 # But on some flavor of Mac, it's nowhere near that. So we can't have
2569 # any idea here what time that actually is, we can only test that
2570 # relative changes match.
2571 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2572 tz = FixedOffset(utcoffset, "tz", 0)
2573 expected = utcdatetime + utcoffset
2574 got = datetime.fromtimestamp(timestamp, tz)
2575 self.assertEqual(expected, got.replace(tzinfo=None))
Tim Peters2a44a8d2003-01-23 20:53:10 +00002576
Tim Peters2a799bf2002-12-16 20:18:38 +00002577 def test_tzinfo_utcnow(self):
2578 meth = self.theclass.utcnow
2579 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2580 base = meth()
2581 # Try with and without naming the keyword; for whatever reason,
2582 # utcnow() doesn't accept a tzinfo argument.
2583 off42 = FixedOffset(42, "42")
2584 self.assertRaises(TypeError, meth, off42)
2585 self.assertRaises(TypeError, meth, tzinfo=off42)
2586
2587 def test_tzinfo_utcfromtimestamp(self):
2588 import time
2589 meth = self.theclass.utcfromtimestamp
2590 ts = time.time()
2591 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2592 base = meth(ts)
2593 # Try with and without naming the keyword; for whatever reason,
2594 # utcfromtimestamp() doesn't accept a tzinfo argument.
2595 off42 = FixedOffset(42, "42")
2596 self.assertRaises(TypeError, meth, ts, off42)
2597 self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2598
2599 def test_tzinfo_timetuple(self):
Tim Peters0bf60bd2003-01-08 20:40:01 +00002600 # TestDateTime tested most of this. datetime adds a twist to the
Tim Peters2a799bf2002-12-16 20:18:38 +00002601 # DST flag.
2602 class DST(tzinfo):
2603 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002604 if isinstance(dstvalue, int):
2605 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002606 self.dstvalue = dstvalue
2607 def dst(self, dt):
2608 return self.dstvalue
2609
2610 cls = self.theclass
2611 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2612 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2613 t = d.timetuple()
2614 self.assertEqual(1, t.tm_year)
2615 self.assertEqual(1, t.tm_mon)
2616 self.assertEqual(1, t.tm_mday)
2617 self.assertEqual(10, t.tm_hour)
2618 self.assertEqual(20, t.tm_min)
2619 self.assertEqual(30, t.tm_sec)
2620 self.assertEqual(0, t.tm_wday)
2621 self.assertEqual(1, t.tm_yday)
2622 self.assertEqual(flag, t.tm_isdst)
2623
2624 # dst() returns wrong type.
2625 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2626
2627 # dst() at the edge.
2628 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2629 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2630
2631 # dst() out of range.
2632 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2633 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2634
2635 def test_utctimetuple(self):
2636 class DST(tzinfo):
2637 def __init__(self, dstvalue):
Tim Peters397301e2003-01-02 21:28:08 +00002638 if isinstance(dstvalue, int):
2639 dstvalue = timedelta(minutes=dstvalue)
Tim Peters2a799bf2002-12-16 20:18:38 +00002640 self.dstvalue = dstvalue
2641 def dst(self, dt):
2642 return self.dstvalue
2643
2644 cls = self.theclass
2645 # This can't work: DST didn't implement utcoffset.
2646 self.assertRaises(NotImplementedError,
2647 cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2648
2649 class UOFS(DST):
2650 def __init__(self, uofs, dofs=None):
2651 DST.__init__(self, dofs)
Tim Peters397301e2003-01-02 21:28:08 +00002652 self.uofs = timedelta(minutes=uofs)
Tim Peters2a799bf2002-12-16 20:18:38 +00002653 def utcoffset(self, dt):
2654 return self.uofs
2655
2656 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never
2657 # in effect for a UTC time.
2658 for dstvalue in -33, 33, 0, None:
2659 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2660 t = d.utctimetuple()
2661 self.assertEqual(d.year, t.tm_year)
2662 self.assertEqual(d.month, t.tm_mon)
2663 self.assertEqual(d.day, t.tm_mday)
2664 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2665 self.assertEqual(13, t.tm_min)
2666 self.assertEqual(d.second, t.tm_sec)
2667 self.assertEqual(d.weekday(), t.tm_wday)
2668 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2669 t.tm_yday)
2670 self.assertEqual(0, t.tm_isdst)
2671
2672 # At the edges, UTC adjustment can normalize into years out-of-range
2673 # for a datetime object. Ensure that a correct timetuple is
2674 # created anyway.
2675 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2676 # That goes back 1 minute less than a full day.
2677 t = tiny.utctimetuple()
2678 self.assertEqual(t.tm_year, MINYEAR-1)
2679 self.assertEqual(t.tm_mon, 12)
2680 self.assertEqual(t.tm_mday, 31)
2681 self.assertEqual(t.tm_hour, 0)
2682 self.assertEqual(t.tm_min, 1)
2683 self.assertEqual(t.tm_sec, 37)
2684 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
2685 self.assertEqual(t.tm_isdst, 0)
2686
2687 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2688 # That goes forward 1 minute less than a full day.
2689 t = huge.utctimetuple()
2690 self.assertEqual(t.tm_year, MAXYEAR+1)
2691 self.assertEqual(t.tm_mon, 1)
2692 self.assertEqual(t.tm_mday, 1)
2693 self.assertEqual(t.tm_hour, 23)
2694 self.assertEqual(t.tm_min, 58)
2695 self.assertEqual(t.tm_sec, 37)
2696 self.assertEqual(t.tm_yday, 1)
2697 self.assertEqual(t.tm_isdst, 0)
2698
2699 def test_tzinfo_isoformat(self):
2700 zero = FixedOffset(0, "+00:00")
2701 plus = FixedOffset(220, "+03:40")
2702 minus = FixedOffset(-231, "-03:51")
2703 unknown = FixedOffset(None, "")
2704
2705 cls = self.theclass
2706 datestr = '0001-02-03'
2707 for ofs in None, zero, plus, minus, unknown:
Tim Peters6578dc92002-12-24 18:31:27 +00002708 for us in 0, 987001:
Tim Peters2a799bf2002-12-16 20:18:38 +00002709 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2710 timestr = '04:05:59' + (us and '.987001' or '')
2711 ofsstr = ofs is not None and d.tzname() or ''
2712 tailstr = timestr + ofsstr
2713 iso = d.isoformat()
2714 self.assertEqual(iso, datestr + 'T' + tailstr)
2715 self.assertEqual(iso, d.isoformat('T'))
2716 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2717 self.assertEqual(str(d), datestr + ' ' + tailstr)
2718
Tim Peters12bf3392002-12-24 05:41:27 +00002719 def test_replace(self):
2720 cls = self.theclass
2721 z100 = FixedOffset(100, "+100")
2722 zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2723 args = [1, 2, 3, 4, 5, 6, 7, z100]
2724 base = cls(*args)
2725 self.assertEqual(base, base.replace())
2726
2727 i = 0
2728 for name, newval in (("year", 2),
2729 ("month", 3),
2730 ("day", 4),
2731 ("hour", 5),
2732 ("minute", 6),
2733 ("second", 7),
2734 ("microsecond", 8),
2735 ("tzinfo", zm200)):
2736 newargs = args[:]
2737 newargs[i] = newval
2738 expected = cls(*newargs)
2739 got = base.replace(**{name: newval})
2740 self.assertEqual(expected, got)
2741 i += 1
2742
2743 # Ensure we can get rid of a tzinfo.
2744 self.assertEqual(base.tzname(), "+100")
2745 base2 = base.replace(tzinfo=None)
2746 self.failUnless(base2.tzinfo is None)
2747 self.failUnless(base2.tzname() is None)
2748
2749 # Ensure we can add one.
2750 base3 = base2.replace(tzinfo=z100)
2751 self.assertEqual(base, base3)
2752 self.failUnless(base.tzinfo is base3.tzinfo)
2753
2754 # Out of bounds.
2755 base = cls(2000, 2, 29)
2756 self.assertRaises(ValueError, base.replace, year=2001)
Tim Peters2a799bf2002-12-16 20:18:38 +00002757
Tim Peters80475bb2002-12-25 07:40:55 +00002758 def test_more_astimezone(self):
2759 # The inherited test_astimezone covered some trivial and error cases.
2760 fnone = FixedOffset(None, "None")
2761 f44m = FixedOffset(44, "44")
2762 fm5h = FixedOffset(-timedelta(hours=5), "m300")
2763
Tim Peters10cadce2003-01-23 19:58:02 +00002764 dt = self.theclass.now(tz=f44m)
Tim Peters80475bb2002-12-25 07:40:55 +00002765 self.failUnless(dt.tzinfo is f44m)
Tim Peters52dcce22003-01-23 16:36:11 +00002766 # Replacing with degenerate tzinfo raises an exception.
2767 self.assertRaises(ValueError, dt.astimezone, fnone)
2768 # Ditto with None tz.
2769 self.assertRaises(TypeError, dt.astimezone, None)
2770 # Replacing with same tzinfo makes no change.
Tim Peters80475bb2002-12-25 07:40:55 +00002771 x = dt.astimezone(dt.tzinfo)
2772 self.failUnless(x.tzinfo is f44m)
2773 self.assertEqual(x.date(), dt.date())
2774 self.assertEqual(x.time(), dt.time())
2775
2776 # Replacing with different tzinfo does adjust.
2777 got = dt.astimezone(fm5h)
2778 self.failUnless(got.tzinfo is fm5h)
2779 self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2780 expected = dt - dt.utcoffset() # in effect, convert to UTC
2781 expected += fm5h.utcoffset(dt) # and from there to local time
2782 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2783 self.assertEqual(got.date(), expected.date())
2784 self.assertEqual(got.time(), expected.time())
2785 self.assertEqual(got.timetz(), expected.timetz())
2786 self.failUnless(got.tzinfo is expected.tzinfo)
2787 self.assertEqual(got, expected)
2788
Tim Peters4c0db782002-12-26 05:01:19 +00002789 def test_aware_subtract(self):
2790 cls = self.theclass
2791
Tim Peters60c76e42002-12-27 00:41:11 +00002792 # Ensure that utcoffset() is ignored when the operands have the
2793 # same tzinfo member.
Tim Peters4c0db782002-12-26 05:01:19 +00002794 class OperandDependentOffset(tzinfo):
2795 def utcoffset(self, t):
2796 if t.minute < 10:
Tim Peters397301e2003-01-02 21:28:08 +00002797 # d0 and d1 equal after adjustment
2798 return timedelta(minutes=t.minute)
Tim Peters4c0db782002-12-26 05:01:19 +00002799 else:
Tim Peters397301e2003-01-02 21:28:08 +00002800 # d2 off in the weeds
2801 return timedelta(minutes=59)
Tim Peters4c0db782002-12-26 05:01:19 +00002802
2803 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2804 d0 = base.replace(minute=3)
2805 d1 = base.replace(minute=9)
2806 d2 = base.replace(minute=11)
2807 for x in d0, d1, d2:
2808 for y in d0, d1, d2:
2809 got = x - y
Tim Peters60c76e42002-12-27 00:41:11 +00002810 expected = timedelta(minutes=x.minute - y.minute)
2811 self.assertEqual(got, expected)
2812
2813 # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2814 # ignored.
2815 base = cls(8, 9, 10, 11, 12, 13, 14)
2816 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2817 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2818 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2819 for x in d0, d1, d2:
2820 for y in d0, d1, d2:
2821 got = x - y
Tim Peters4c0db782002-12-26 05:01:19 +00002822 if (x is d0 or x is d1) and (y is d0 or y is d1):
2823 expected = timedelta(0)
2824 elif x is y is d2:
2825 expected = timedelta(0)
2826 elif x is d2:
2827 expected = timedelta(minutes=(11-59)-0)
2828 else:
2829 assert y is d2
2830 expected = timedelta(minutes=0-(11-59))
2831 self.assertEqual(got, expected)
2832
Tim Peters60c76e42002-12-27 00:41:11 +00002833 def test_mixed_compare(self):
2834 t1 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters0bf60bd2003-01-08 20:40:01 +00002835 t2 = datetime(1, 2, 3, 4, 5, 6, 7)
Tim Peters60c76e42002-12-27 00:41:11 +00002836 self.assertEqual(t1, t2)
2837 t2 = t2.replace(tzinfo=None)
2838 self.assertEqual(t1, t2)
2839 t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2840 self.assertEqual(t1, t2)
Tim Peters68124bb2003-02-08 03:46:31 +00002841 t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2842 self.assertRaises(TypeError, lambda: t1 == t2)
Tim Peters60c76e42002-12-27 00:41:11 +00002843
Tim Peters0bf60bd2003-01-08 20:40:01 +00002844 # In datetime w/ identical tzinfo objects, utcoffset is ignored.
Tim Peters60c76e42002-12-27 00:41:11 +00002845 class Varies(tzinfo):
2846 def __init__(self):
Tim Peters397301e2003-01-02 21:28:08 +00002847 self.offset = timedelta(minutes=22)
Tim Peters60c76e42002-12-27 00:41:11 +00002848 def utcoffset(self, t):
Tim Peters397301e2003-01-02 21:28:08 +00002849 self.offset += timedelta(minutes=1)
Tim Peters60c76e42002-12-27 00:41:11 +00002850 return self.offset
2851
2852 v = Varies()
2853 t1 = t2.replace(tzinfo=v)
2854 t2 = t2.replace(tzinfo=v)
2855 self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2856 self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2857 self.assertEqual(t1, t2)
2858
2859 # But if they're not identical, it isn't ignored.
2860 t2 = t2.replace(tzinfo=Varies())
2861 self.failUnless(t1 < t2) # t1's offset counter still going up
Tim Peters80475bb2002-12-25 07:40:55 +00002862
Tim Petersa98924a2003-05-17 05:55:19 +00002863 def test_subclass_datetimetz(self):
2864
2865 class C(self.theclass):
2866 theAnswer = 42
2867
2868 def __new__(cls, *args, **kws):
2869 temp = kws.copy()
2870 extra = temp.pop('extra')
2871 result = self.theclass.__new__(cls, *args, **temp)
2872 result.extra = extra
2873 return result
2874
2875 def newmeth(self, start):
2876 return start + self.hour + self.year
2877
2878 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2879
2880 dt1 = self.theclass(*args)
2881 dt2 = C(*args, **{'extra': 7})
2882
2883 self.assertEqual(dt2.__class__, C)
2884 self.assertEqual(dt2.theAnswer, 42)
2885 self.assertEqual(dt2.extra, 7)
2886 self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2887 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
2888
Tim Peters621818b2002-12-29 23:44:49 +00002889# Pain to set up DST-aware tzinfo classes.
2890
2891def first_sunday_on_or_after(dt):
2892 days_to_go = 6 - dt.weekday()
2893 if days_to_go:
2894 dt += timedelta(days_to_go)
2895 return dt
2896
2897ZERO = timedelta(0)
2898HOUR = timedelta(hours=1)
2899DAY = timedelta(days=1)
2900# In the US, DST starts at 2am (standard time) on the first Sunday in April.
2901DSTSTART = datetime(1, 4, 1, 2)
2902# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
Tim Peters327098a2003-01-20 22:54:38 +00002903# which is the first Sunday on or after Oct 25. Because we view 1:MM as
2904# being standard time on that day, there is no spelling in local time of
2905# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
2906DSTEND = datetime(1, 10, 25, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002907
2908class USTimeZone(tzinfo):
2909
2910 def __init__(self, hours, reprname, stdname, dstname):
2911 self.stdoffset = timedelta(hours=hours)
2912 self.reprname = reprname
2913 self.stdname = stdname
2914 self.dstname = dstname
2915
2916 def __repr__(self):
2917 return self.reprname
2918
2919 def tzname(self, dt):
2920 if self.dst(dt):
2921 return self.dstname
2922 else:
2923 return self.stdname
2924
2925 def utcoffset(self, dt):
2926 return self.stdoffset + self.dst(dt)
2927
2928 def dst(self, dt):
Tim Petersbad8ff02002-12-30 20:52:32 +00002929 if dt is None or dt.tzinfo is None:
Tim Peters621818b2002-12-29 23:44:49 +00002930 # An exception instead may be sensible here, in one or more of
2931 # the cases.
2932 return ZERO
Tim Peters521fc152002-12-31 17:36:56 +00002933 assert dt.tzinfo is self
Tim Peters621818b2002-12-29 23:44:49 +00002934
2935 # Find first Sunday in April.
2936 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
2937 assert start.weekday() == 6 and start.month == 4 and start.day <= 7
2938
2939 # Find last Sunday in October.
2940 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
2941 assert end.weekday() == 6 and end.month == 10 and end.day >= 25
2942
Tim Peters621818b2002-12-29 23:44:49 +00002943 # Can't compare naive to aware objects, so strip the timezone from
2944 # dt first.
Tim Peters52dcce22003-01-23 16:36:11 +00002945 if start <= dt.replace(tzinfo=None) < end:
Tim Peters621818b2002-12-29 23:44:49 +00002946 return HOUR
2947 else:
2948 return ZERO
2949
Tim Peters521fc152002-12-31 17:36:56 +00002950Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
2951Central = USTimeZone(-6, "Central", "CST", "CDT")
2952Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
2953Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")
Tim Peters1024bf82002-12-30 17:09:40 +00002954utc_real = FixedOffset(0, "UTC", 0)
2955# For better test coverage, we want another flavor of UTC that's west of
2956# the Eastern and Pacific timezones.
Tim Petersadf64202003-01-04 06:03:15 +00002957utc_fake = FixedOffset(-12*60, "UTCfake", 0)
Tim Peters621818b2002-12-29 23:44:49 +00002958
2959class TestTimezoneConversions(unittest.TestCase):
Tim Peters327098a2003-01-20 22:54:38 +00002960 # The DST switch times for 2002, in std time.
Tim Peters0bf60bd2003-01-08 20:40:01 +00002961 dston = datetime(2002, 4, 7, 2)
Tim Peters327098a2003-01-20 22:54:38 +00002962 dstoff = datetime(2002, 10, 27, 1)
Tim Peters621818b2002-12-29 23:44:49 +00002963
Tim Peters0bf60bd2003-01-08 20:40:01 +00002964 theclass = datetime
Tim Peters710fb152003-01-02 19:35:54 +00002965
Tim Peters521fc152002-12-31 17:36:56 +00002966 # Check a time that's inside DST.
2967 def checkinside(self, dt, tz, utc, dston, dstoff):
2968 self.assertEqual(dt.dst(), HOUR)
2969
2970 # Conversion to our own timezone is always an identity.
2971 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters521fc152002-12-31 17:36:56 +00002972
2973 asutc = dt.astimezone(utc)
2974 there_and_back = asutc.astimezone(tz)
2975
2976 # Conversion to UTC and back isn't always an identity here,
2977 # because there are redundant spellings (in local time) of
2978 # UTC time when DST begins: the clock jumps from 1:59:59
2979 # to 3:00:00, and a local time of 2:MM:SS doesn't really
2980 # make sense then. The classes above treat 2:MM:SS as
2981 # daylight time then (it's "after 2am"), really an alias
2982 # for 1:MM:SS standard time. The latter form is what
2983 # conversion back from UTC produces.
2984 if dt.date() == dston.date() and dt.hour == 2:
2985 # We're in the redundant hour, and coming back from
2986 # UTC gives the 1:MM:SS standard-time spelling.
2987 self.assertEqual(there_and_back + HOUR, dt)
2988 # Although during was considered to be in daylight
2989 # time, there_and_back is not.
2990 self.assertEqual(there_and_back.dst(), ZERO)
2991 # They're the same times in UTC.
2992 self.assertEqual(there_and_back.astimezone(utc),
2993 dt.astimezone(utc))
2994 else:
2995 # We're not in the redundant hour.
2996 self.assertEqual(dt, there_and_back)
2997
Tim Peters327098a2003-01-20 22:54:38 +00002998 # Because we have a redundant spelling when DST begins, there is
2999 # (unforunately) an hour when DST ends that can't be spelled at all in
3000 # local time. When DST ends, the clock jumps from 1:59 back to 1:00
3001 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be
3002 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be
3003 # daylight time. The hour 1:MM daylight == 0:MM standard can't be
3004 # expressed in local time. Nevertheless, we want conversion back
3005 # from UTC to mimic the local clock's "repeat an hour" behavior.
Tim Peters521fc152002-12-31 17:36:56 +00003006 nexthour_utc = asutc + HOUR
Tim Petersadf64202003-01-04 06:03:15 +00003007 nexthour_tz = nexthour_utc.astimezone(tz)
Tim Peters327098a2003-01-20 22:54:38 +00003008 if dt.date() == dstoff.date() and dt.hour == 0:
3009 # We're in the hour before the last DST hour. The last DST hour
Tim Petersadf64202003-01-04 06:03:15 +00003010 # is ineffable. We want the conversion back to repeat 1:MM.
Tim Peters327098a2003-01-20 22:54:38 +00003011 self.assertEqual(nexthour_tz, dt.replace(hour=1))
3012 nexthour_utc += HOUR
3013 nexthour_tz = nexthour_utc.astimezone(tz)
3014 self.assertEqual(nexthour_tz, dt.replace(hour=1))
Tim Peters521fc152002-12-31 17:36:56 +00003015 else:
Tim Peters327098a2003-01-20 22:54:38 +00003016 self.assertEqual(nexthour_tz - dt, HOUR)
Tim Peters521fc152002-12-31 17:36:56 +00003017
3018 # Check a time that's outside DST.
3019 def checkoutside(self, dt, tz, utc):
3020 self.assertEqual(dt.dst(), ZERO)
3021
3022 # Conversion to our own timezone is always an identity.
3023 self.assertEqual(dt.astimezone(tz), dt)
Tim Peters52dcce22003-01-23 16:36:11 +00003024
3025 # Converting to UTC and back is an identity too.
3026 asutc = dt.astimezone(utc)
3027 there_and_back = asutc.astimezone(tz)
3028 self.assertEqual(dt, there_and_back)
Tim Peters521fc152002-12-31 17:36:56 +00003029
Tim Peters1024bf82002-12-30 17:09:40 +00003030 def convert_between_tz_and_utc(self, tz, utc):
3031 dston = self.dston.replace(tzinfo=tz)
Tim Peters327098a2003-01-20 22:54:38 +00003032 # Because 1:MM on the day DST ends is taken as being standard time,
3033 # there is no spelling in tz for the last hour of daylight time.
3034 # For purposes of the test, the last hour of DST is 0:MM, which is
3035 # taken as being daylight time (and 1:MM is taken as being standard
3036 # time).
Tim Peters1024bf82002-12-30 17:09:40 +00003037 dstoff = self.dstoff.replace(tzinfo=tz)
3038 for delta in (timedelta(weeks=13),
3039 DAY,
3040 HOUR,
3041 timedelta(minutes=1),
3042 timedelta(microseconds=1)):
3043
Tim Peters521fc152002-12-31 17:36:56 +00003044 self.checkinside(dston, tz, utc, dston, dstoff)
3045 for during in dston + delta, dstoff - delta:
3046 self.checkinside(during, tz, utc, dston, dstoff)
Tim Peters31cc3152002-12-30 17:37:30 +00003047
Tim Peters521fc152002-12-31 17:36:56 +00003048 self.checkoutside(dstoff, tz, utc)
3049 for outside in dston - delta, dstoff + delta:
3050 self.checkoutside(outside, tz, utc)
Tim Peters31cc3152002-12-30 17:37:30 +00003051
Tim Peters621818b2002-12-29 23:44:49 +00003052 def test_easy(self):
3053 # Despite the name of this test, the endcases are excruciating.
Tim Peters1024bf82002-12-30 17:09:40 +00003054 self.convert_between_tz_and_utc(Eastern, utc_real)
3055 self.convert_between_tz_and_utc(Pacific, utc_real)
3056 self.convert_between_tz_and_utc(Eastern, utc_fake)
3057 self.convert_between_tz_and_utc(Pacific, utc_fake)
3058 # The next is really dancing near the edge. It works because
3059 # Pacific and Eastern are far enough apart that their "problem
3060 # hours" don't overlap.
3061 self.convert_between_tz_and_utc(Eastern, Pacific)
3062 self.convert_between_tz_and_utc(Pacific, Eastern)
Tim Peters36087ed2003-01-01 04:18:51 +00003063 # OTOH, these fail! Don't enable them. The difficulty is that
3064 # the edge case tests assume that every hour is representable in
3065 # the "utc" class. This is always true for a fixed-offset tzinfo
3066 # class (lke utc_real and utc_fake), but not for Eastern or Central.
3067 # For these adjacent DST-aware time zones, the range of time offsets
3068 # tested ends up creating hours in the one that aren't representable
3069 # in the other. For the same reason, we would see failures in the
3070 # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3071 # offset deltas in convert_between_tz_and_utc().
3072 #
3073 # self.convert_between_tz_and_utc(Eastern, Central) # can't work
3074 # self.convert_between_tz_and_utc(Central, Eastern) # can't work
Tim Peters621818b2002-12-29 23:44:49 +00003075
Tim Petersf3615152003-01-01 21:51:37 +00003076 def test_tricky(self):
3077 # 22:00 on day before daylight starts.
3078 fourback = self.dston - timedelta(hours=4)
3079 ninewest = FixedOffset(-9*60, "-0900", 0)
Tim Peters52dcce22003-01-23 16:36:11 +00003080 fourback = fourback.replace(tzinfo=ninewest)
Tim Petersf3615152003-01-01 21:51:37 +00003081 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after
3082 # 2", we should get the 3 spelling.
3083 # If we plug 22:00 the day before into Eastern, it "looks like std
3084 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4
3085 # to 22:00 lands on 2:00, which makes no sense in local time (the
3086 # local clock jumps from 1 to 3). The point here is to make sure we
3087 # get the 3 spelling.
3088 expected = self.dston.replace(hour=3)
Tim Peters52dcce22003-01-23 16:36:11 +00003089 got = fourback.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003090 self.assertEqual(expected, got)
3091
3092 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that
3093 # case we want the 1:00 spelling.
Tim Peters52dcce22003-01-23 16:36:11 +00003094 sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
Tim Petersf3615152003-01-01 21:51:37 +00003095 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3096 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST
3097 # spelling.
3098 expected = self.dston.replace(hour=1)
Tim Peters52dcce22003-01-23 16:36:11 +00003099 got = sixutc.astimezone(Eastern).replace(tzinfo=None)
Tim Petersf3615152003-01-01 21:51:37 +00003100 self.assertEqual(expected, got)
Tim Peters621818b2002-12-29 23:44:49 +00003101
Tim Petersadf64202003-01-04 06:03:15 +00003102 # Now on the day DST ends, we want "repeat an hour" behavior.
3103 # UTC 4:MM 5:MM 6:MM 7:MM checking these
3104 # EST 23:MM 0:MM 1:MM 2:MM
3105 # EDT 0:MM 1:MM 2:MM 3:MM
3106 # wall 0:MM 1:MM 1:MM 2:MM against these
3107 for utc in utc_real, utc_fake:
3108 for tz in Eastern, Pacific:
Tim Peters327098a2003-01-20 22:54:38 +00003109 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
Tim Petersadf64202003-01-04 06:03:15 +00003110 # Convert that to UTC.
3111 first_std_hour -= tz.utcoffset(None)
3112 # Adjust for possibly fake UTC.
3113 asutc = first_std_hour + utc.utcoffset(None)
3114 # First UTC hour to convert; this is 4:00 when utc=utc_real &
3115 # tz=Eastern.
3116 asutcbase = asutc.replace(tzinfo=utc)
3117 for tzhour in (0, 1, 1, 2):
3118 expectedbase = self.dstoff.replace(hour=tzhour)
3119 for minute in 0, 30, 59:
3120 expected = expectedbase.replace(minute=minute)
3121 asutc = asutcbase.replace(minute=minute)
3122 astz = asutc.astimezone(tz)
3123 self.assertEqual(astz.replace(tzinfo=None), expected)
3124 asutcbase += HOUR
3125
3126
Tim Peters710fb152003-01-02 19:35:54 +00003127 def test_bogus_dst(self):
3128 class ok(tzinfo):
3129 def utcoffset(self, dt): return HOUR
3130 def dst(self, dt): return HOUR
3131
3132 now = self.theclass.now().replace(tzinfo=utc_real)
3133 # Doesn't blow up.
3134 now.astimezone(ok())
3135
3136 # Does blow up.
3137 class notok(ok):
3138 def dst(self, dt): return None
3139 self.assertRaises(ValueError, now.astimezone, notok())
3140
Tim Peters52dcce22003-01-23 16:36:11 +00003141 def test_fromutc(self):
3142 self.assertRaises(TypeError, Eastern.fromutc) # not enough args
3143 now = datetime.utcnow().replace(tzinfo=utc_real)
3144 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3145 now = now.replace(tzinfo=Eastern) # insert correct tzinfo
3146 enow = Eastern.fromutc(now) # doesn't blow up
3147 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3148 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3149 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3150
3151 # Always converts UTC to standard time.
3152 class FauxUSTimeZone(USTimeZone):
3153 def fromutc(self, dt):
3154 return dt + self.stdoffset
3155 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT")
3156
3157 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM
3158 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM
3159 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM
3160
3161 # Check around DST start.
3162 start = self.dston.replace(hour=4, tzinfo=Eastern)
3163 fstart = start.replace(tzinfo=FEastern)
3164 for wall in 23, 0, 1, 3, 4, 5:
3165 expected = start.replace(hour=wall)
3166 if wall == 23:
3167 expected -= timedelta(days=1)
3168 got = Eastern.fromutc(start)
3169 self.assertEqual(expected, got)
3170
3171 expected = fstart + FEastern.stdoffset
3172 got = FEastern.fromutc(fstart)
3173 self.assertEqual(expected, got)
3174
3175 # Ensure astimezone() calls fromutc() too.
3176 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3177 self.assertEqual(expected, got)
3178
3179 start += HOUR
3180 fstart += HOUR
3181
3182 # Check around DST end.
3183 start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3184 fstart = start.replace(tzinfo=FEastern)
3185 for wall in 0, 1, 1, 2, 3, 4:
3186 expected = start.replace(hour=wall)
3187 got = Eastern.fromutc(start)
3188 self.assertEqual(expected, got)
3189
3190 expected = fstart + FEastern.stdoffset
3191 got = FEastern.fromutc(fstart)
3192 self.assertEqual(expected, got)
3193
3194 # Ensure astimezone() calls fromutc() too.
3195 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3196 self.assertEqual(expected, got)
3197
3198 start += HOUR
3199 fstart += HOUR
3200
Tim Peters710fb152003-01-02 19:35:54 +00003201
Tim Peters528ca532004-09-16 01:30:50 +00003202#############################################################################
3203# oddballs
3204
3205class Oddballs(unittest.TestCase):
3206
3207 def test_bug_1028306(self):
3208 # Trying to compare a date to a datetime should act like a mixed-
3209 # type comparison, despite that datetime is a subclass of date.
3210 as_date = date.today()
3211 as_datetime = datetime.combine(as_date, time())
3212 self.assert_(as_date != as_datetime)
3213 self.assert_(as_datetime != as_date)
3214 self.assert_(not as_date == as_datetime)
3215 self.assert_(not as_datetime == as_date)
3216 self.assertRaises(TypeError, lambda: as_date < as_datetime)
3217 self.assertRaises(TypeError, lambda: as_datetime < as_date)
3218 self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3219 self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3220 self.assertRaises(TypeError, lambda: as_date > as_datetime)
3221 self.assertRaises(TypeError, lambda: as_datetime > as_date)
3222 self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3223 self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3224
3225 # Neverthelss, comparison should work with the base-class (date)
3226 # projection if use of a date method is forced.
3227 self.assert_(as_date.__eq__(as_datetime))
3228 different_day = (as_date.day + 1) % 20 + 1
3229 self.assert_(not as_date.__eq__(as_datetime.replace(day=
3230 different_day)))
3231
3232 # And date should compare with other subclasses of date. If a
3233 # subclass wants to stop this, it's up to the subclass to do so.
3234 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3235 self.assertEqual(as_date, date_sc)
3236 self.assertEqual(date_sc, as_date)
3237
3238 # Ditto for datetimes.
3239 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3240 as_date.day, 0, 0, 0)
3241 self.assertEqual(as_datetime, datetime_sc)
3242 self.assertEqual(datetime_sc, as_datetime)
3243
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003244def test_suite():
Tim Peters2a799bf2002-12-16 20:18:38 +00003245 allsuites = [unittest.makeSuite(klass, 'test')
3246 for klass in (TestModule,
3247 TestTZInfo,
3248 TestTimeDelta,
3249 TestDateOnly,
3250 TestDate,
3251 TestDateTime,
3252 TestTime,
3253 TestTimeTZ,
3254 TestDateTimeTZ,
Tim Peters621818b2002-12-29 23:44:49 +00003255 TestTimezoneConversions,
Tim Peters528ca532004-09-16 01:30:50 +00003256 Oddballs,
Tim Peters2a799bf2002-12-16 20:18:38 +00003257 )
3258 ]
3259 return unittest.TestSuite(allsuites)
3260
3261def test_main():
3262 import gc
3263 import sys
3264
Tim Peterscfd4a8b2002-12-16 21:12:37 +00003265 thesuite = test_suite()
Tim Peters2a799bf2002-12-16 20:18:38 +00003266 lastrc = None
3267 while True:
3268 test_support.run_suite(thesuite)
3269 if 1: # change to 0, under a debug build, for some leak detection
3270 break
3271 gc.collect()
3272 if gc.garbage:
3273 raise SystemError("gc.garbage not empty after test run: %r" %
3274 gc.garbage)
3275 if hasattr(sys, 'gettotalrefcount'):
3276 thisrc = sys.gettotalrefcount()
3277 print >> sys.stderr, '*' * 10, 'total refs:', thisrc,
3278 if lastrc:
3279 print >> sys.stderr, 'delta:', thisrc - lastrc
3280 else:
3281 print >> sys.stderr
3282 lastrc = thisrc
3283
3284if __name__ == "__main__":
3285 test_main()