• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
5from __future__ import division
6import sys
7import pickle
8import cPickle
9import unittest
10
11from test import test_support
12
13from datetime import MINYEAR, MAXYEAR
14from datetime import timedelta
15from datetime import tzinfo
16from datetime import time
17from datetime import date, datetime
18
19pickle_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
24
25# An arbitrary collection of objects of non-datetime types, for testing
26# mixed-type comparisons.
27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ())
28
29
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):
45        if isinstance(offset, int):
46            offset = timedelta(minutes=offset)
47        if isinstance(dstoffset, int):
48            dstoffset = timedelta(minutes=dstoffset)
49        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
61class PicklableFixedOffset(FixedOffset):
62    def __init__(self, offset=None, name=None, dstoffset=None):
63        FixedOffset.__init__(self, offset, name, dstoffset)
64
65class 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.assertTrue(issubclass(NotEnough, tzinfo))
83        ne = NotEnough(3, "NotByALongShot")
84        self.assertIsInstance(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.assertIsInstance(fo, tzinfo)
94        for dt in datetime.now(), None:
95            self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
96            self.assertEqual(fo.tzname(dt), "Three")
97            self.assertEqual(fo.dst(dt), timedelta(minutes=42))
98
99    def test_pickling_base(self):
100        # 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.assertIs(type(orig), tzinfo)
105        for pickler, unpickler, proto in pickle_choices:
106            green = pickler.dumps(orig, proto)
107            derived = unpickler.loads(green)
108            self.assertIs(type(derived), tzinfo)
109
110    def test_pickling_subclass(self):
111        # Make sure we can pickle/unpickle an instance of a subclass.
112        offset = timedelta(minutes=-300)
113        orig = PicklableFixedOffset(offset, 'cookie')
114        self.assertIsInstance(orig, tzinfo)
115        self.assertTrue(type(orig) is PicklableFixedOffset)
116        self.assertEqual(orig.utcoffset(None), offset)
117        self.assertEqual(orig.tzname(None), 'cookie')
118        for pickler, unpickler, proto in pickle_choices:
119            green = pickler.dumps(orig, proto)
120            derived = unpickler.loads(green)
121            self.assertIsInstance(derived, tzinfo)
122            self.assertTrue(type(derived) is PicklableFixedOffset)
123            self.assertEqual(derived.utcoffset(None), offset)
124            self.assertEqual(derived.tzname(None), 'cookie')
125
126#############################################################################
127# Base class for testing a particular aspect of timedelta, time, date and
128# datetime comparisons.
129
130class HarmlessMixedComparison:
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.assertFalse(me == ())
140        self.assertTrue(me != ())
141        self.assertFalse(() == me)
142        self.assertTrue(() != me)
143
144        self.assertIn(me, [1, 20L, [], me])
145        self.assertIn([], [me, 1, 20L, []])
146
147    def test_harmful_mixed_comparison(self):
148        me = self.theclass(1, 1, 1)
149
150        self.assertRaises(TypeError, lambda: me < ())
151        self.assertRaises(TypeError, lambda: me <= ())
152        self.assertRaises(TypeError, lambda: me > ())
153        self.assertRaises(TypeError, lambda: me >= ())
154
155        self.assertRaises(TypeError, lambda: () < me)
156        self.assertRaises(TypeError, lambda: () <= me)
157        self.assertRaises(TypeError, lambda: () > me)
158        self.assertRaises(TypeError, lambda: () >= me)
159
160        self.assertRaises(TypeError, cmp, (), me)
161        self.assertRaises(TypeError, cmp, me, ())
162
163#############################################################################
164# timedelta tests
165
166class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
167
168    theclass = timedelta
169
170    def test_constructor(self):
171        eq = self.assertEqual
172        td = timedelta
173
174        # Check keyword args to constructor
175        eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
176                    milliseconds=0, microseconds=0))
177        eq(td(1), td(days=1))
178        eq(td(0, 1), td(seconds=1))
179        eq(td(0, 0, 1), td(microseconds=1))
180        eq(td(weeks=1), td(days=7))
181        eq(td(days=1), td(hours=24))
182        eq(td(hours=1), td(minutes=60))
183        eq(td(minutes=1), td(seconds=60))
184        eq(td(seconds=1), td(milliseconds=1000))
185        eq(td(milliseconds=1), td(microseconds=1000))
186
187        # Check float args to constructor
188        eq(td(weeks=1.0/7), td(days=1))
189        eq(td(days=1.0/24), td(hours=1))
190        eq(td(hours=1.0/60), td(minutes=1))
191        eq(td(minutes=1.0/60), td(seconds=1))
192        eq(td(seconds=0.001), td(milliseconds=1))
193        eq(td(milliseconds=0.001), td(microseconds=1))
194
195    def test_computations(self):
196        eq = self.assertEqual
197        td = timedelta
198
199        a = td(7) # One week
200        b = td(0, 60) # One minute
201        c = td(0, 0, 1000) # One millisecond
202        eq(a+b+c, td(7, 60, 1000))
203        eq(a-b, td(6, 24*3600 - 60))
204        eq(-a, td(-7))
205        eq(+a, td(7))
206        eq(-b, td(-1, 24*3600 - 60))
207        eq(-c, td(-1, 24*3600 - 1, 999000))
208        eq(abs(a), a)
209        eq(abs(-a), a)
210        eq(td(6, 24*3600), a)
211        eq(td(0, 0, 60*1000000), b)
212        eq(a*10, td(70))
213        eq(a*10, 10*a)
214        eq(a*10L, 10*a)
215        eq(b*10, td(0, 600))
216        eq(10*b, td(0, 600))
217        eq(b*10L, td(0, 600))
218        eq(c*10, td(0, 0, 10000))
219        eq(10*c, td(0, 0, 10000))
220        eq(c*10L, td(0, 0, 10000))
221        eq(a*-1, -a)
222        eq(b*-2, -b-b)
223        eq(c*-2, -c+-c)
224        eq(b*(60*24), (b*60)*24)
225        eq(b*(60*24), (60*b)*24)
226        eq(c*1000, td(0, 1))
227        eq(1000*c, td(0, 1))
228        eq(a//7, td(1))
229        eq(b//10, td(0, 6))
230        eq(c//1000, td(0, 0, 1))
231        eq(a//10, td(0, 7*24*360))
232        eq(a//3600000, td(0, 0, 7*24*1000))
233
234        # Issue #11576
235        eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
236           td(0, 0, 1))
237        eq(td(999999999, 1, 1) - td(999999999, 1, 0),
238           td(0, 0, 1))
239
240
241    def test_disallowed_computations(self):
242        a = timedelta(42)
243
244        # Add/sub ints, longs, floats should be illegal
245        for i in 1, 1L, 1.0:
246            self.assertRaises(TypeError, lambda: a+i)
247            self.assertRaises(TypeError, lambda: a-i)
248            self.assertRaises(TypeError, lambda: i+a)
249            self.assertRaises(TypeError, lambda: i-a)
250
251        # Mul/div by float isn't supported.
252        x = 2.3
253        self.assertRaises(TypeError, lambda: a*x)
254        self.assertRaises(TypeError, lambda: x*a)
255        self.assertRaises(TypeError, lambda: a/x)
256        self.assertRaises(TypeError, lambda: x/a)
257        self.assertRaises(TypeError, lambda: a // x)
258        self.assertRaises(TypeError, lambda: x // a)
259
260        # Division of int by timedelta doesn't make sense.
261        # Division by zero doesn't make sense.
262        for zero in 0, 0L:
263            self.assertRaises(TypeError, lambda: zero // a)
264            self.assertRaises(ZeroDivisionError, lambda: a // zero)
265
266    def test_basic_attributes(self):
267        days, seconds, us = 1, 7, 31
268        td = timedelta(days, seconds, us)
269        self.assertEqual(td.days, days)
270        self.assertEqual(td.seconds, seconds)
271        self.assertEqual(td.microseconds, us)
272
273    def test_total_seconds(self):
274        td = timedelta(days=365)
275        self.assertEqual(td.total_seconds(), 31536000.0)
276        for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
277            td = timedelta(seconds=total_seconds)
278            self.assertEqual(td.total_seconds(), total_seconds)
279        # Issue8644: Test that td.total_seconds() has the same
280        # accuracy as td / timedelta(seconds=1).
281        for ms in [-1, -2, -123]:
282            td = timedelta(microseconds=ms)
283            self.assertEqual(td.total_seconds(),
284                             ((24*3600*td.days + td.seconds)*10**6
285                              + td.microseconds)/10**6)
286
287    def test_carries(self):
288        t1 = timedelta(days=100,
289                       weeks=-7,
290                       hours=-24*(100-49),
291                       minutes=-3,
292                       seconds=12,
293                       microseconds=(3*60 - 12) * 1e6 + 1)
294        t2 = timedelta(microseconds=1)
295        self.assertEqual(t1, t2)
296
297    def test_hash_equality(self):
298        t1 = timedelta(days=100,
299                       weeks=-7,
300                       hours=-24*(100-49),
301                       minutes=-3,
302                       seconds=12,
303                       microseconds=(3*60 - 12) * 1000000)
304        t2 = timedelta()
305        self.assertEqual(hash(t1), hash(t2))
306
307        t1 += timedelta(weeks=7)
308        t2 += timedelta(days=7*7)
309        self.assertEqual(t1, t2)
310        self.assertEqual(hash(t1), hash(t2))
311
312        d = {t1: 1}
313        d[t2] = 2
314        self.assertEqual(len(d), 1)
315        self.assertEqual(d[t1], 2)
316
317    def test_pickling(self):
318        args = 12, 34, 56
319        orig = timedelta(*args)
320        for pickler, unpickler, proto in pickle_choices:
321            green = pickler.dumps(orig, proto)
322            derived = unpickler.loads(green)
323            self.assertEqual(orig, derived)
324
325    def test_compare(self):
326        t1 = timedelta(2, 3, 4)
327        t2 = timedelta(2, 3, 4)
328        self.assertTrue(t1 == t2)
329        self.assertTrue(t1 <= t2)
330        self.assertTrue(t1 >= t2)
331        self.assertFalse(t1 != t2)
332        self.assertFalse(t1 < t2)
333        self.assertFalse(t1 > t2)
334        self.assertEqual(cmp(t1, t2), 0)
335        self.assertEqual(cmp(t2, t1), 0)
336
337        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
338            t2 = timedelta(*args)   # this is larger than t1
339            self.assertTrue(t1 < t2)
340            self.assertTrue(t2 > t1)
341            self.assertTrue(t1 <= t2)
342            self.assertTrue(t2 >= t1)
343            self.assertTrue(t1 != t2)
344            self.assertTrue(t2 != t1)
345            self.assertFalse(t1 == t2)
346            self.assertFalse(t2 == t1)
347            self.assertFalse(t1 > t2)
348            self.assertFalse(t2 < t1)
349            self.assertFalse(t1 >= t2)
350            self.assertFalse(t2 <= t1)
351            self.assertEqual(cmp(t1, t2), -1)
352            self.assertEqual(cmp(t2, t1), 1)
353
354        for badarg in OTHERSTUFF:
355            self.assertEqual(t1 == badarg, False)
356            self.assertEqual(t1 != badarg, True)
357            self.assertEqual(badarg == t1, False)
358            self.assertEqual(badarg != t1, True)
359
360            self.assertRaises(TypeError, lambda: t1 <= badarg)
361            self.assertRaises(TypeError, lambda: t1 < badarg)
362            self.assertRaises(TypeError, lambda: t1 > badarg)
363            self.assertRaises(TypeError, lambda: t1 >= badarg)
364            self.assertRaises(TypeError, lambda: badarg <= t1)
365            self.assertRaises(TypeError, lambda: badarg < t1)
366            self.assertRaises(TypeError, lambda: badarg > t1)
367            self.assertRaises(TypeError, lambda: badarg >= t1)
368
369    def test_str(self):
370        td = timedelta
371        eq = self.assertEqual
372
373        eq(str(td(1)), "1 day, 0:00:00")
374        eq(str(td(-1)), "-1 day, 0:00:00")
375        eq(str(td(2)), "2 days, 0:00:00")
376        eq(str(td(-2)), "-2 days, 0:00:00")
377
378        eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
379        eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
380        eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
381           "-210 days, 23:12:34")
382
383        eq(str(td(milliseconds=1)), "0:00:00.001000")
384        eq(str(td(microseconds=3)), "0:00:00.000003")
385
386        eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
387                   microseconds=999999)),
388           "999999999 days, 23:59:59.999999")
389
390    def test_roundtrip(self):
391        for td in (timedelta(days=999999999, hours=23, minutes=59,
392                             seconds=59, microseconds=999999),
393                   timedelta(days=-999999999),
394                   timedelta(days=1, seconds=2, microseconds=3)):
395
396            # Verify td -> string -> td identity.
397            s = repr(td)
398            self.assertTrue(s.startswith('datetime.'))
399            s = s[9:]
400            td2 = eval(s)
401            self.assertEqual(td, td2)
402
403            # Verify identity via reconstructing from pieces.
404            td2 = timedelta(td.days, td.seconds, td.microseconds)
405            self.assertEqual(td, td2)
406
407    def test_resolution_info(self):
408        self.assertIsInstance(timedelta.min, timedelta)
409        self.assertIsInstance(timedelta.max, timedelta)
410        self.assertIsInstance(timedelta.resolution, timedelta)
411        self.assertTrue(timedelta.max > timedelta.min)
412        self.assertEqual(timedelta.min, timedelta(-999999999))
413        self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
414        self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
415
416    def test_overflow(self):
417        tiny = timedelta.resolution
418
419        td = timedelta.min + tiny
420        td -= tiny  # no problem
421        self.assertRaises(OverflowError, td.__sub__, tiny)
422        self.assertRaises(OverflowError, td.__add__, -tiny)
423
424        td = timedelta.max - tiny
425        td += tiny  # no problem
426        self.assertRaises(OverflowError, td.__add__, tiny)
427        self.assertRaises(OverflowError, td.__sub__, -tiny)
428
429        self.assertRaises(OverflowError, lambda: -timedelta.max)
430
431    def test_microsecond_rounding(self):
432        td = timedelta
433        eq = self.assertEqual
434
435        # Single-field rounding.
436        eq(td(milliseconds=0.4/1000), td(0))    # rounds to 0
437        eq(td(milliseconds=-0.4/1000), td(0))    # rounds to 0
438        eq(td(milliseconds=0.6/1000), td(microseconds=1))
439        eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
440
441        # Rounding due to contributions from more than one field.
442        us_per_hour = 3600e6
443        us_per_day = us_per_hour * 24
444        eq(td(days=.4/us_per_day), td(0))
445        eq(td(hours=.2/us_per_hour), td(0))
446        eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
447
448        eq(td(days=-.4/us_per_day), td(0))
449        eq(td(hours=-.2/us_per_hour), td(0))
450        eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
451
452    def test_massive_normalization(self):
453        td = timedelta(microseconds=-1)
454        self.assertEqual((td.days, td.seconds, td.microseconds),
455                         (-1, 24*3600-1, 999999))
456
457    def test_bool(self):
458        self.assertTrue(timedelta(1))
459        self.assertTrue(timedelta(0, 1))
460        self.assertTrue(timedelta(0, 0, 1))
461        self.assertTrue(timedelta(microseconds=1))
462        self.assertFalse(timedelta(0))
463
464    def test_subclass_timedelta(self):
465
466        class T(timedelta):
467            @staticmethod
468            def from_td(td):
469                return T(td.days, td.seconds, td.microseconds)
470
471            def as_hours(self):
472                sum = (self.days * 24 +
473                       self.seconds / 3600.0 +
474                       self.microseconds / 3600e6)
475                return round(sum)
476
477        t1 = T(days=1)
478        self.assertIs(type(t1), T)
479        self.assertEqual(t1.as_hours(), 24)
480
481        t2 = T(days=-1, seconds=-3600)
482        self.assertIs(type(t2), T)
483        self.assertEqual(t2.as_hours(), -25)
484
485        t3 = t1 + t2
486        self.assertIs(type(t3), timedelta)
487        t4 = T.from_td(t3)
488        self.assertIs(type(t4), T)
489        self.assertEqual(t3.days, t4.days)
490        self.assertEqual(t3.seconds, t4.seconds)
491        self.assertEqual(t3.microseconds, t4.microseconds)
492        self.assertEqual(str(t3), str(t4))
493        self.assertEqual(t4.as_hours(), -1)
494
495    def test_issue31752(self):
496        # The interpreter shouldn't crash because divmod() returns negative
497        # remainder.
498        class BadInt(int):
499            def __mul__(self, other):
500                return Prod()
501
502        class BadLong(long):
503            def __mul__(self, other):
504                return Prod()
505
506        class Prod:
507            def __radd__(self, other):
508                return Sum()
509
510        class Sum(int):
511            def __divmod__(self, other):
512                # negative remainder
513                return (0, -1)
514
515        timedelta(microseconds=BadInt(1))
516        timedelta(hours=BadInt(1))
517        timedelta(weeks=BadInt(1))
518        timedelta(microseconds=BadLong(1))
519        timedelta(hours=BadLong(1))
520        timedelta(weeks=BadLong(1))
521
522        class Sum(long):
523            def __divmod__(self, other):
524                # negative remainder
525                return (0, -1)
526
527        timedelta(microseconds=BadInt(1))
528        timedelta(hours=BadInt(1))
529        timedelta(weeks=BadInt(1))
530        timedelta(microseconds=BadLong(1))
531        timedelta(hours=BadLong(1))
532        timedelta(weeks=BadLong(1))
533
534
535#############################################################################
536# date tests
537
538class TestDateOnly(unittest.TestCase):
539    # Tests here won't pass if also run on datetime objects, so don't
540    # subclass this to test datetimes too.
541
542    def test_delta_non_days_ignored(self):
543        dt = date(2000, 1, 2)
544        delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
545                          microseconds=5)
546        days = timedelta(delta.days)
547        self.assertEqual(days, timedelta(1))
548
549        dt2 = dt + delta
550        self.assertEqual(dt2, dt + days)
551
552        dt2 = delta + dt
553        self.assertEqual(dt2, dt + days)
554
555        dt2 = dt - delta
556        self.assertEqual(dt2, dt - days)
557
558        delta = -delta
559        days = timedelta(delta.days)
560        self.assertEqual(days, timedelta(-2))
561
562        dt2 = dt + delta
563        self.assertEqual(dt2, dt + days)
564
565        dt2 = delta + dt
566        self.assertEqual(dt2, dt + days)
567
568        dt2 = dt - delta
569        self.assertEqual(dt2, dt - days)
570
571class SubclassDate(date):
572    sub_var = 1
573
574class TestDate(HarmlessMixedComparison, unittest.TestCase):
575    # Tests here should pass for both dates and datetimes, except for a
576    # few tests that TestDateTime overrides.
577
578    theclass = date
579
580    def test_basic_attributes(self):
581        dt = self.theclass(2002, 3, 1)
582        self.assertEqual(dt.year, 2002)
583        self.assertEqual(dt.month, 3)
584        self.assertEqual(dt.day, 1)
585
586    def test_roundtrip(self):
587        for dt in (self.theclass(1, 2, 3),
588                   self.theclass.today()):
589            # Verify dt -> string -> date identity.
590            s = repr(dt)
591            self.assertTrue(s.startswith('datetime.'))
592            s = s[9:]
593            dt2 = eval(s)
594            self.assertEqual(dt, dt2)
595
596            # Verify identity via reconstructing from pieces.
597            dt2 = self.theclass(dt.year, dt.month, dt.day)
598            self.assertEqual(dt, dt2)
599
600    def test_ordinal_conversions(self):
601        # Check some fixed values.
602        for y, m, d, n in [(1, 1, 1, 1),      # calendar origin
603                           (1, 12, 31, 365),
604                           (2, 1, 1, 366),
605                           # first example from "Calendrical Calculations"
606                           (1945, 11, 12, 710347)]:
607            d = self.theclass(y, m, d)
608            self.assertEqual(n, d.toordinal())
609            fromord = self.theclass.fromordinal(n)
610            self.assertEqual(d, fromord)
611            if hasattr(fromord, "hour"):
612            # if we're checking something fancier than a date, verify
613            # the extra fields have been zeroed out
614                self.assertEqual(fromord.hour, 0)
615                self.assertEqual(fromord.minute, 0)
616                self.assertEqual(fromord.second, 0)
617                self.assertEqual(fromord.microsecond, 0)
618
619        # Check first and last days of year spottily across the whole
620        # range of years supported.
621        for year in xrange(MINYEAR, MAXYEAR+1, 7):
622            # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
623            d = self.theclass(year, 1, 1)
624            n = d.toordinal()
625            d2 = self.theclass.fromordinal(n)
626            self.assertEqual(d, d2)
627            # Verify that moving back a day gets to the end of year-1.
628            if year > 1:
629                d = self.theclass.fromordinal(n-1)
630                d2 = self.theclass(year-1, 12, 31)
631                self.assertEqual(d, d2)
632                self.assertEqual(d2.toordinal(), n-1)
633
634        # Test every day in a leap-year and a non-leap year.
635        dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
636        for year, isleap in (2000, True), (2002, False):
637            n = self.theclass(year, 1, 1).toordinal()
638            for month, maxday in zip(range(1, 13), dim):
639                if month == 2 and isleap:
640                    maxday += 1
641                for day in range(1, maxday+1):
642                    d = self.theclass(year, month, day)
643                    self.assertEqual(d.toordinal(), n)
644                    self.assertEqual(d, self.theclass.fromordinal(n))
645                    n += 1
646
647    def test_extreme_ordinals(self):
648        a = self.theclass.min
649        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
650        aord = a.toordinal()
651        b = a.fromordinal(aord)
652        self.assertEqual(a, b)
653
654        self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
655
656        b = a + timedelta(days=1)
657        self.assertEqual(b.toordinal(), aord + 1)
658        self.assertEqual(b, self.theclass.fromordinal(aord + 1))
659
660        a = self.theclass.max
661        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
662        aord = a.toordinal()
663        b = a.fromordinal(aord)
664        self.assertEqual(a, b)
665
666        self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
667
668        b = a - timedelta(days=1)
669        self.assertEqual(b.toordinal(), aord - 1)
670        self.assertEqual(b, self.theclass.fromordinal(aord - 1))
671
672    def test_bad_constructor_arguments(self):
673        # bad years
674        self.theclass(MINYEAR, 1, 1)  # no exception
675        self.theclass(MAXYEAR, 1, 1)  # no exception
676        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
677        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
678        # bad months
679        self.theclass(2000, 1, 1)    # no exception
680        self.theclass(2000, 12, 1)   # no exception
681        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
682        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
683        # bad days
684        self.theclass(2000, 2, 29)   # no exception
685        self.theclass(2004, 2, 29)   # no exception
686        self.theclass(2400, 2, 29)   # no exception
687        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
688        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
689        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
690        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
691        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
692        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
693
694    def test_hash_equality(self):
695        d = self.theclass(2000, 12, 31)
696        # same thing
697        e = self.theclass(2000, 12, 31)
698        self.assertEqual(d, e)
699        self.assertEqual(hash(d), hash(e))
700
701        dic = {d: 1}
702        dic[e] = 2
703        self.assertEqual(len(dic), 1)
704        self.assertEqual(dic[d], 2)
705        self.assertEqual(dic[e], 2)
706
707        d = self.theclass(2001,  1,  1)
708        # same thing
709        e = self.theclass(2001,  1,  1)
710        self.assertEqual(d, e)
711        self.assertEqual(hash(d), hash(e))
712
713        dic = {d: 1}
714        dic[e] = 2
715        self.assertEqual(len(dic), 1)
716        self.assertEqual(dic[d], 2)
717        self.assertEqual(dic[e], 2)
718
719    def test_computations(self):
720        a = self.theclass(2002, 1, 31)
721        b = self.theclass(1956, 1, 31)
722
723        diff = a-b
724        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
725        self.assertEqual(diff.seconds, 0)
726        self.assertEqual(diff.microseconds, 0)
727
728        day = timedelta(1)
729        week = timedelta(7)
730        a = self.theclass(2002, 3, 2)
731        self.assertEqual(a + day, self.theclass(2002, 3, 3))
732        self.assertEqual(day + a, self.theclass(2002, 3, 3))
733        self.assertEqual(a - day, self.theclass(2002, 3, 1))
734        self.assertEqual(-day + a, self.theclass(2002, 3, 1))
735        self.assertEqual(a + week, self.theclass(2002, 3, 9))
736        self.assertEqual(a - week, self.theclass(2002, 2, 23))
737        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
738        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
739        self.assertEqual((a + week) - a, week)
740        self.assertEqual((a + day) - a, day)
741        self.assertEqual((a - week) - a, -week)
742        self.assertEqual((a - day) - a, -day)
743        self.assertEqual(a - (a + week), -week)
744        self.assertEqual(a - (a + day), -day)
745        self.assertEqual(a - (a - week), week)
746        self.assertEqual(a - (a - day), day)
747
748        # Add/sub ints, longs, floats should be illegal
749        for i in 1, 1L, 1.0:
750            self.assertRaises(TypeError, lambda: a+i)
751            self.assertRaises(TypeError, lambda: a-i)
752            self.assertRaises(TypeError, lambda: i+a)
753            self.assertRaises(TypeError, lambda: i-a)
754
755        # delta - date is senseless.
756        self.assertRaises(TypeError, lambda: day - a)
757        # mixing date and (delta or date) via * or // is senseless
758        self.assertRaises(TypeError, lambda: day * a)
759        self.assertRaises(TypeError, lambda: a * day)
760        self.assertRaises(TypeError, lambda: day // a)
761        self.assertRaises(TypeError, lambda: a // day)
762        self.assertRaises(TypeError, lambda: a * a)
763        self.assertRaises(TypeError, lambda: a // a)
764        # date + date is senseless
765        self.assertRaises(TypeError, lambda: a + a)
766
767    def test_overflow(self):
768        tiny = self.theclass.resolution
769
770        for delta in [tiny, timedelta(1), timedelta(2)]:
771            dt = self.theclass.min + delta
772            dt -= delta  # no problem
773            self.assertRaises(OverflowError, dt.__sub__, delta)
774            self.assertRaises(OverflowError, dt.__add__, -delta)
775
776            dt = self.theclass.max - delta
777            dt += delta  # no problem
778            self.assertRaises(OverflowError, dt.__add__, delta)
779            self.assertRaises(OverflowError, dt.__sub__, -delta)
780
781    def test_fromtimestamp(self):
782        import time
783
784        # Try an arbitrary fixed value.
785        year, month, day = 1999, 9, 19
786        ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
787        d = self.theclass.fromtimestamp(ts)
788        self.assertEqual(d.year, year)
789        self.assertEqual(d.month, month)
790        self.assertEqual(d.day, day)
791
792    def test_insane_fromtimestamp(self):
793        # It's possible that some platform maps time_t to double,
794        # and that this test will fail there.  This test should
795        # exempt such platforms (provided they return reasonable
796        # results!).
797        for insane in -1e200, 1e200:
798            self.assertRaises(ValueError, self.theclass.fromtimestamp,
799                              insane)
800
801    def test_today(self):
802        import time
803
804        # We claim that today() is like fromtimestamp(time.time()), so
805        # prove it.
806        for dummy in range(3):
807            today = self.theclass.today()
808            ts = time.time()
809            todayagain = self.theclass.fromtimestamp(ts)
810            if today == todayagain:
811                break
812            # There are several legit reasons that could fail:
813            # 1. It recently became midnight, between the today() and the
814            #    time() calls.
815            # 2. The platform time() has such fine resolution that we'll
816            #    never get the same value twice.
817            # 3. The platform time() has poor resolution, and we just
818            #    happened to call today() right before a resolution quantum
819            #    boundary.
820            # 4. The system clock got fiddled between calls.
821            # In any case, wait a little while and try again.
822            time.sleep(0.1)
823
824        # It worked or it didn't.  If it didn't, assume it's reason #2, and
825        # let the test pass if they're within half a second of each other.
826        if today != todayagain:
827            self.assertAlmostEqual(todayagain, today,
828                                   delta=timedelta(seconds=0.5))
829
830    def test_weekday(self):
831        for i in range(7):
832            # March 4, 2002 is a Monday
833            self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
834            self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
835            # January 2, 1956 is a Monday
836            self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
837            self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
838
839    def test_isocalendar(self):
840        # Check examples from
841        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
842        for i in range(7):
843            d = self.theclass(2003, 12, 22+i)
844            self.assertEqual(d.isocalendar(), (2003, 52, i+1))
845            d = self.theclass(2003, 12, 29) + timedelta(i)
846            self.assertEqual(d.isocalendar(), (2004, 1, i+1))
847            d = self.theclass(2004, 1, 5+i)
848            self.assertEqual(d.isocalendar(), (2004, 2, i+1))
849            d = self.theclass(2009, 12, 21+i)
850            self.assertEqual(d.isocalendar(), (2009, 52, i+1))
851            d = self.theclass(2009, 12, 28) + timedelta(i)
852            self.assertEqual(d.isocalendar(), (2009, 53, i+1))
853            d = self.theclass(2010, 1, 4+i)
854            self.assertEqual(d.isocalendar(), (2010, 1, i+1))
855
856    def test_iso_long_years(self):
857        # Calculate long ISO years and compare to table from
858        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
859        ISO_LONG_YEARS_TABLE = """
860              4   32   60   88
861              9   37   65   93
862             15   43   71   99
863             20   48   76
864             26   54   82
865
866            105  133  161  189
867            111  139  167  195
868            116  144  172
869            122  150  178
870            128  156  184
871
872            201  229  257  285
873            207  235  263  291
874            212  240  268  296
875            218  246  274
876            224  252  280
877
878            303  331  359  387
879            308  336  364  392
880            314  342  370  398
881            320  348  376
882            325  353  381
883        """
884        iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split())
885        iso_long_years.sort()
886        L = []
887        for i in range(400):
888            d = self.theclass(2000+i, 12, 31)
889            d1 = self.theclass(1600+i, 12, 31)
890            self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
891            if d.isocalendar()[1] == 53:
892                L.append(i)
893        self.assertEqual(L, iso_long_years)
894
895    def test_isoformat(self):
896        t = self.theclass(2, 3, 2)
897        self.assertEqual(t.isoformat(), "0002-03-02")
898
899    def test_ctime(self):
900        t = self.theclass(2002, 3, 2)
901        self.assertEqual(t.ctime(), "Sat Mar  2 00:00:00 2002")
902
903    def test_strftime(self):
904        t = self.theclass(2005, 3, 2)
905        self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
906        self.assertEqual(t.strftime(""), "") # SF bug #761337
907        self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
908
909        self.assertRaises(TypeError, t.strftime) # needs an arg
910        self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
911        self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
912
913        # test that unicode input is allowed (issue 2782)
914        self.assertEqual(t.strftime(u"%m"), "03")
915
916        # A naive object replaces %z and %Z w/ empty strings.
917        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
918
919        #make sure that invalid format specifiers are handled correctly
920        #self.assertRaises(ValueError, t.strftime, "%e")
921        #self.assertRaises(ValueError, t.strftime, "%")
922        #self.assertRaises(ValueError, t.strftime, "%#")
923
924        #oh well, some systems just ignore those invalid ones.
925        #at least, exercise them to make sure that no crashes
926        #are generated
927        for f in ["%e", "%", "%#"]:
928            try:
929                t.strftime(f)
930            except ValueError:
931                pass
932
933        #check that this standard extension works
934        t.strftime("%f")
935
936
937    def test_format(self):
938        dt = self.theclass(2007, 9, 10)
939        self.assertEqual(dt.__format__(''), str(dt))
940
941        # check that a derived class's __str__() gets called
942        class A(self.theclass):
943            def __str__(self):
944                return 'A'
945        a = A(2007, 9, 10)
946        self.assertEqual(a.__format__(''), 'A')
947
948        # check that a derived class's strftime gets called
949        class B(self.theclass):
950            def strftime(self, format_spec):
951                return 'B'
952        b = B(2007, 9, 10)
953        self.assertEqual(b.__format__(''), str(dt))
954
955        for fmt in ["m:%m d:%d y:%y",
956                    "m:%m d:%d y:%y H:%H M:%M S:%S",
957                    "%z %Z",
958                    ]:
959            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
960            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
961            self.assertEqual(b.__format__(fmt), 'B')
962
963    def test_resolution_info(self):
964        self.assertIsInstance(self.theclass.min, self.theclass)
965        self.assertIsInstance(self.theclass.max, self.theclass)
966        self.assertIsInstance(self.theclass.resolution, timedelta)
967        self.assertTrue(self.theclass.max > self.theclass.min)
968
969    def test_extreme_timedelta(self):
970        big = self.theclass.max - self.theclass.min
971        # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
972        n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
973        # n == 315537897599999999 ~= 2**58.13
974        justasbig = timedelta(0, 0, n)
975        self.assertEqual(big, justasbig)
976        self.assertEqual(self.theclass.min + big, self.theclass.max)
977        self.assertEqual(self.theclass.max - big, self.theclass.min)
978
979    def test_timetuple(self):
980        for i in range(7):
981            # January 2, 1956 is a Monday (0)
982            d = self.theclass(1956, 1, 2+i)
983            t = d.timetuple()
984            self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
985            # February 1, 1956 is a Wednesday (2)
986            d = self.theclass(1956, 2, 1+i)
987            t = d.timetuple()
988            self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
989            # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
990            # of the year.
991            d = self.theclass(1956, 3, 1+i)
992            t = d.timetuple()
993            self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
994            self.assertEqual(t.tm_year, 1956)
995            self.assertEqual(t.tm_mon, 3)
996            self.assertEqual(t.tm_mday, 1+i)
997            self.assertEqual(t.tm_hour, 0)
998            self.assertEqual(t.tm_min, 0)
999            self.assertEqual(t.tm_sec, 0)
1000            self.assertEqual(t.tm_wday, (3+i)%7)
1001            self.assertEqual(t.tm_yday, 61+i)
1002            self.assertEqual(t.tm_isdst, -1)
1003
1004    def test_pickling(self):
1005        args = 6, 7, 23
1006        orig = self.theclass(*args)
1007        for pickler, unpickler, proto in pickle_choices:
1008            green = pickler.dumps(orig, proto)
1009            derived = unpickler.loads(green)
1010            self.assertEqual(orig, derived)
1011
1012    def test_compare(self):
1013        t1 = self.theclass(2, 3, 4)
1014        t2 = self.theclass(2, 3, 4)
1015        self.assertTrue(t1 == t2)
1016        self.assertTrue(t1 <= t2)
1017        self.assertTrue(t1 >= t2)
1018        self.assertFalse(t1 != t2)
1019        self.assertFalse(t1 < t2)
1020        self.assertFalse(t1 > t2)
1021        self.assertEqual(cmp(t1, t2), 0)
1022        self.assertEqual(cmp(t2, t1), 0)
1023
1024        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1025            t2 = self.theclass(*args)   # this is larger than t1
1026            self.assertTrue(t1 < t2)
1027            self.assertTrue(t2 > t1)
1028            self.assertTrue(t1 <= t2)
1029            self.assertTrue(t2 >= t1)
1030            self.assertTrue(t1 != t2)
1031            self.assertTrue(t2 != t1)
1032            self.assertFalse(t1 == t2)
1033            self.assertFalse(t2 == t1)
1034            self.assertFalse(t1 > t2)
1035            self.assertFalse(t2 < t1)
1036            self.assertFalse(t1 >= t2)
1037            self.assertFalse(t2 <= t1)
1038            self.assertEqual(cmp(t1, t2), -1)
1039            self.assertEqual(cmp(t2, t1), 1)
1040
1041        for badarg in OTHERSTUFF:
1042            self.assertEqual(t1 == badarg, False)
1043            self.assertEqual(t1 != badarg, True)
1044            self.assertEqual(badarg == t1, False)
1045            self.assertEqual(badarg != t1, True)
1046
1047            self.assertRaises(TypeError, lambda: t1 < badarg)
1048            self.assertRaises(TypeError, lambda: t1 > badarg)
1049            self.assertRaises(TypeError, lambda: t1 >= badarg)
1050            self.assertRaises(TypeError, lambda: badarg <= t1)
1051            self.assertRaises(TypeError, lambda: badarg < t1)
1052            self.assertRaises(TypeError, lambda: badarg > t1)
1053            self.assertRaises(TypeError, lambda: badarg >= t1)
1054
1055    def test_mixed_compare(self):
1056        our = self.theclass(2000, 4, 5)
1057        self.assertRaises(TypeError, cmp, our, 1)
1058        self.assertRaises(TypeError, cmp, 1, our)
1059
1060        class AnotherDateTimeClass(object):
1061            def __cmp__(self, other):
1062                # Return "equal" so calling this can't be confused with
1063                # compare-by-address (which never says "equal" for distinct
1064                # objects).
1065                return 0
1066            __hash__ = None # Silence Py3k warning
1067
1068        # This still errors, because date and datetime comparison raise
1069        # TypeError instead of NotImplemented when they don't know what to
1070        # do, in order to stop comparison from falling back to the default
1071        # compare-by-address.
1072        their = AnotherDateTimeClass()
1073        self.assertRaises(TypeError, cmp, our, their)
1074        # Oops:  The next stab raises TypeError in the C implementation,
1075        # but not in the Python implementation of datetime.  The difference
1076        # is due to that the Python implementation defines __cmp__ but
1077        # the C implementation defines tp_richcompare.  This is more pain
1078        # to fix than it's worth, so commenting out the test.
1079        # self.assertEqual(cmp(their, our), 0)
1080
1081        # But date and datetime comparison return NotImplemented instead if the
1082        # other object has a timetuple attr.  This gives the other object a
1083        # chance to do the comparison.
1084        class Comparable(AnotherDateTimeClass):
1085            def timetuple(self):
1086                return ()
1087
1088        their = Comparable()
1089        self.assertEqual(cmp(our, their), 0)
1090        self.assertEqual(cmp(their, our), 0)
1091        self.assertTrue(our == their)
1092        self.assertTrue(their == our)
1093
1094    def test_bool(self):
1095        # All dates are considered true.
1096        self.assertTrue(self.theclass.min)
1097        self.assertTrue(self.theclass.max)
1098
1099    def test_strftime_out_of_range(self):
1100        # For nasty technical reasons, we can't handle years before 1900.
1101        cls = self.theclass
1102        self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900")
1103        for y in 1, 49, 51, 99, 100, 1000, 1899:
1104            self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y")
1105
1106    def test_replace(self):
1107        cls = self.theclass
1108        args = [1, 2, 3]
1109        base = cls(*args)
1110        self.assertEqual(base, base.replace())
1111
1112        i = 0
1113        for name, newval in (("year", 2),
1114                             ("month", 3),
1115                             ("day", 4)):
1116            newargs = args[:]
1117            newargs[i] = newval
1118            expected = cls(*newargs)
1119            got = base.replace(**{name: newval})
1120            self.assertEqual(expected, got)
1121            i += 1
1122
1123        # Out of bounds.
1124        base = cls(2000, 2, 29)
1125        self.assertRaises(ValueError, base.replace, year=2001)
1126
1127    def test_subclass_date(self):
1128
1129        class C(self.theclass):
1130            theAnswer = 42
1131
1132            def __new__(cls, *args, **kws):
1133                temp = kws.copy()
1134                extra = temp.pop('extra')
1135                result = self.theclass.__new__(cls, *args, **temp)
1136                result.extra = extra
1137                return result
1138
1139            def newmeth(self, start):
1140                return start + self.year + self.month
1141
1142        args = 2003, 4, 14
1143
1144        dt1 = self.theclass(*args)
1145        dt2 = C(*args, **{'extra': 7})
1146
1147        self.assertEqual(dt2.__class__, C)
1148        self.assertEqual(dt2.theAnswer, 42)
1149        self.assertEqual(dt2.extra, 7)
1150        self.assertEqual(dt1.toordinal(), dt2.toordinal())
1151        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1152
1153    def test_pickling_subclass_date(self):
1154
1155        args = 6, 7, 23
1156        orig = SubclassDate(*args)
1157        for pickler, unpickler, proto in pickle_choices:
1158            green = pickler.dumps(orig, proto)
1159            derived = unpickler.loads(green)
1160            self.assertEqual(orig, derived)
1161
1162    def test_backdoor_resistance(self):
1163        # For fast unpickling, the constructor accepts a pickle string.
1164        # This is a low-overhead backdoor.  A user can (by intent or
1165        # mistake) pass a string directly, which (if it's the right length)
1166        # will get treated like a pickle, and bypass the normal sanity
1167        # checks in the constructor.  This can create insane objects.
1168        # The constructor doesn't want to burn the time to validate all
1169        # fields, but does check the month field.  This stops, e.g.,
1170        # datetime.datetime('1995-03-25') from yielding an insane object.
1171        base = '1995-03-25'
1172        if not issubclass(self.theclass, datetime):
1173            base = base[:4]
1174        for month_byte in '9', chr(0), chr(13), '\xff':
1175            self.assertRaises(TypeError, self.theclass,
1176                                         base[:2] + month_byte + base[3:])
1177        for ord_byte in range(1, 13):
1178            # This shouldn't blow up because of the month byte alone.  If
1179            # the implementation changes to do more-careful checking, it may
1180            # blow up because other fields are insane.
1181            self.theclass(base[:2] + chr(ord_byte) + base[3:])
1182
1183#############################################################################
1184# datetime tests
1185
1186class SubclassDatetime(datetime):
1187    sub_var = 1
1188
1189class TestDateTime(TestDate):
1190
1191    theclass = datetime
1192
1193    def test_basic_attributes(self):
1194        dt = self.theclass(2002, 3, 1, 12, 0)
1195        self.assertEqual(dt.year, 2002)
1196        self.assertEqual(dt.month, 3)
1197        self.assertEqual(dt.day, 1)
1198        self.assertEqual(dt.hour, 12)
1199        self.assertEqual(dt.minute, 0)
1200        self.assertEqual(dt.second, 0)
1201        self.assertEqual(dt.microsecond, 0)
1202
1203    def test_basic_attributes_nonzero(self):
1204        # Make sure all attributes are non-zero so bugs in
1205        # bit-shifting access show up.
1206        dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1207        self.assertEqual(dt.year, 2002)
1208        self.assertEqual(dt.month, 3)
1209        self.assertEqual(dt.day, 1)
1210        self.assertEqual(dt.hour, 12)
1211        self.assertEqual(dt.minute, 59)
1212        self.assertEqual(dt.second, 59)
1213        self.assertEqual(dt.microsecond, 8000)
1214
1215    def test_roundtrip(self):
1216        for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1217                   self.theclass.now()):
1218            # Verify dt -> string -> datetime identity.
1219            s = repr(dt)
1220            self.assertTrue(s.startswith('datetime.'))
1221            s = s[9:]
1222            dt2 = eval(s)
1223            self.assertEqual(dt, dt2)
1224
1225            # Verify identity via reconstructing from pieces.
1226            dt2 = self.theclass(dt.year, dt.month, dt.day,
1227                                dt.hour, dt.minute, dt.second,
1228                                dt.microsecond)
1229            self.assertEqual(dt, dt2)
1230
1231    def test_isoformat(self):
1232        t = self.theclass(2, 3, 2, 4, 5, 1, 123)
1233        self.assertEqual(t.isoformat(),    "0002-03-02T04:05:01.000123")
1234        self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123")
1235        self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123")
1236        self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123")
1237        # str is ISO format with the separator forced to a blank.
1238        self.assertEqual(str(t), "0002-03-02 04:05:01.000123")
1239
1240        t = self.theclass(2, 3, 2)
1241        self.assertEqual(t.isoformat(),    "0002-03-02T00:00:00")
1242        self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1243        self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1244        # str is ISO format with the separator forced to a blank.
1245        self.assertEqual(str(t), "0002-03-02 00:00:00")
1246
1247    def test_format(self):
1248        dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1249        self.assertEqual(dt.__format__(''), str(dt))
1250
1251        # check that a derived class's __str__() gets called
1252        class A(self.theclass):
1253            def __str__(self):
1254                return 'A'
1255        a = A(2007, 9, 10, 4, 5, 1, 123)
1256        self.assertEqual(a.__format__(''), 'A')
1257
1258        # check that a derived class's strftime gets called
1259        class B(self.theclass):
1260            def strftime(self, format_spec):
1261                return 'B'
1262        b = B(2007, 9, 10, 4, 5, 1, 123)
1263        self.assertEqual(b.__format__(''), str(dt))
1264
1265        for fmt in ["m:%m d:%d y:%y",
1266                    "m:%m d:%d y:%y H:%H M:%M S:%S",
1267                    "%z %Z",
1268                    ]:
1269            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1270            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1271            self.assertEqual(b.__format__(fmt), 'B')
1272
1273    def test_more_ctime(self):
1274        # Test fields that TestDate doesn't touch.
1275        import time
1276
1277        t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1278        self.assertEqual(t.ctime(), "Sat Mar  2 18:03:05 2002")
1279        # Oops!  The next line fails on Win2K under MSVC 6, so it's commented
1280        # out.  The difference is that t.ctime() produces " 2" for the day,
1281        # but platform ctime() produces "02" for the day.  According to
1282        # C99, t.ctime() is correct here.
1283        # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1284
1285        # So test a case where that difference doesn't matter.
1286        t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1287        self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1288
1289    def test_tz_independent_comparing(self):
1290        dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1291        dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1292        dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1293        self.assertEqual(dt1, dt3)
1294        self.assertTrue(dt2 > dt3)
1295
1296        # Make sure comparison doesn't forget microseconds, and isn't done
1297        # via comparing a float timestamp (an IEEE double doesn't have enough
1298        # precision to span microsecond resolution across years 1 thru 9999,
1299        # so comparing via timestamp necessarily calls some distinct values
1300        # equal).
1301        dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1302        us = timedelta(microseconds=1)
1303        dt2 = dt1 + us
1304        self.assertEqual(dt2 - dt1, us)
1305        self.assertTrue(dt1 < dt2)
1306
1307    def test_strftime_with_bad_tzname_replace(self):
1308        # verify ok if tzinfo.tzname().replace() returns a non-string
1309        class MyTzInfo(FixedOffset):
1310            def tzname(self, dt):
1311                class MyStr(str):
1312                    def replace(self, *args):
1313                        return None
1314                return MyStr('name')
1315        t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1316        self.assertRaises(TypeError, t.strftime, '%Z')
1317
1318    def test_bad_constructor_arguments(self):
1319        # bad years
1320        self.theclass(MINYEAR, 1, 1)  # no exception
1321        self.theclass(MAXYEAR, 1, 1)  # no exception
1322        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1323        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1324        # bad months
1325        self.theclass(2000, 1, 1)    # no exception
1326        self.theclass(2000, 12, 1)   # no exception
1327        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1328        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1329        # bad days
1330        self.theclass(2000, 2, 29)   # no exception
1331        self.theclass(2004, 2, 29)   # no exception
1332        self.theclass(2400, 2, 29)   # no exception
1333        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1334        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1335        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1336        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1337        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1338        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1339        # bad hours
1340        self.theclass(2000, 1, 31, 0)    # no exception
1341        self.theclass(2000, 1, 31, 23)   # no exception
1342        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1343        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1344        # bad minutes
1345        self.theclass(2000, 1, 31, 23, 0)    # no exception
1346        self.theclass(2000, 1, 31, 23, 59)   # no exception
1347        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1348        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1349        # bad seconds
1350        self.theclass(2000, 1, 31, 23, 59, 0)    # no exception
1351        self.theclass(2000, 1, 31, 23, 59, 59)   # no exception
1352        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1353        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1354        # bad microseconds
1355        self.theclass(2000, 1, 31, 23, 59, 59, 0)    # no exception
1356        self.theclass(2000, 1, 31, 23, 59, 59, 999999)   # no exception
1357        self.assertRaises(ValueError, self.theclass,
1358                          2000, 1, 31, 23, 59, 59, -1)
1359        self.assertRaises(ValueError, self.theclass,
1360                          2000, 1, 31, 23, 59, 59,
1361                          1000000)
1362
1363    def test_hash_equality(self):
1364        d = self.theclass(2000, 12, 31, 23, 30, 17)
1365        e = self.theclass(2000, 12, 31, 23, 30, 17)
1366        self.assertEqual(d, e)
1367        self.assertEqual(hash(d), hash(e))
1368
1369        dic = {d: 1}
1370        dic[e] = 2
1371        self.assertEqual(len(dic), 1)
1372        self.assertEqual(dic[d], 2)
1373        self.assertEqual(dic[e], 2)
1374
1375        d = self.theclass(2001,  1,  1,  0,  5, 17)
1376        e = self.theclass(2001,  1,  1,  0,  5, 17)
1377        self.assertEqual(d, e)
1378        self.assertEqual(hash(d), hash(e))
1379
1380        dic = {d: 1}
1381        dic[e] = 2
1382        self.assertEqual(len(dic), 1)
1383        self.assertEqual(dic[d], 2)
1384        self.assertEqual(dic[e], 2)
1385
1386    def test_computations(self):
1387        a = self.theclass(2002, 1, 31)
1388        b = self.theclass(1956, 1, 31)
1389        diff = a-b
1390        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1391        self.assertEqual(diff.seconds, 0)
1392        self.assertEqual(diff.microseconds, 0)
1393        a = self.theclass(2002, 3, 2, 17, 6)
1394        millisec = timedelta(0, 0, 1000)
1395        hour = timedelta(0, 3600)
1396        day = timedelta(1)
1397        week = timedelta(7)
1398        self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1399        self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1400        self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1401        self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1402        self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1403        self.assertEqual(a - hour, a + -hour)
1404        self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1405        self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1406        self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1407        self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1408        self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1409        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1410        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1411        self.assertEqual((a + week) - a, week)
1412        self.assertEqual((a + day) - a, day)
1413        self.assertEqual((a + hour) - a, hour)
1414        self.assertEqual((a + millisec) - a, millisec)
1415        self.assertEqual((a - week) - a, -week)
1416        self.assertEqual((a - day) - a, -day)
1417        self.assertEqual((a - hour) - a, -hour)
1418        self.assertEqual((a - millisec) - a, -millisec)
1419        self.assertEqual(a - (a + week), -week)
1420        self.assertEqual(a - (a + day), -day)
1421        self.assertEqual(a - (a + hour), -hour)
1422        self.assertEqual(a - (a + millisec), -millisec)
1423        self.assertEqual(a - (a - week), week)
1424        self.assertEqual(a - (a - day), day)
1425        self.assertEqual(a - (a - hour), hour)
1426        self.assertEqual(a - (a - millisec), millisec)
1427        self.assertEqual(a + (week + day + hour + millisec),
1428                         self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1429        self.assertEqual(a + (week + day + hour + millisec),
1430                         (((a + week) + day) + hour) + millisec)
1431        self.assertEqual(a - (week + day + hour + millisec),
1432                         self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1433        self.assertEqual(a - (week + day + hour + millisec),
1434                         (((a - week) - day) - hour) - millisec)
1435        # Add/sub ints, longs, floats should be illegal
1436        for i in 1, 1L, 1.0:
1437            self.assertRaises(TypeError, lambda: a+i)
1438            self.assertRaises(TypeError, lambda: a-i)
1439            self.assertRaises(TypeError, lambda: i+a)
1440            self.assertRaises(TypeError, lambda: i-a)
1441
1442        # delta - datetime is senseless.
1443        self.assertRaises(TypeError, lambda: day - a)
1444        # mixing datetime and (delta or datetime) via * or // is senseless
1445        self.assertRaises(TypeError, lambda: day * a)
1446        self.assertRaises(TypeError, lambda: a * day)
1447        self.assertRaises(TypeError, lambda: day // a)
1448        self.assertRaises(TypeError, lambda: a // day)
1449        self.assertRaises(TypeError, lambda: a * a)
1450        self.assertRaises(TypeError, lambda: a // a)
1451        # datetime + datetime is senseless
1452        self.assertRaises(TypeError, lambda: a + a)
1453
1454    def test_pickling(self):
1455        args = 6, 7, 23, 20, 59, 1, 64**2
1456        orig = self.theclass(*args)
1457        for pickler, unpickler, proto in pickle_choices:
1458            green = pickler.dumps(orig, proto)
1459            derived = unpickler.loads(green)
1460            self.assertEqual(orig, derived)
1461
1462    def test_more_pickling(self):
1463        a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1464        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1465            s = pickle.dumps(a, proto)
1466            b = pickle.loads(s)
1467            self.assertEqual(b.year, 2003)
1468            self.assertEqual(b.month, 2)
1469            self.assertEqual(b.day, 7)
1470
1471    def test_pickling_subclass_datetime(self):
1472        args = 6, 7, 23, 20, 59, 1, 64**2
1473        orig = SubclassDatetime(*args)
1474        for pickler, unpickler, proto in pickle_choices:
1475            green = pickler.dumps(orig, proto)
1476            derived = unpickler.loads(green)
1477            self.assertEqual(orig, derived)
1478
1479    def test_more_compare(self):
1480        # The test_compare() inherited from TestDate covers the error cases.
1481        # We just want to test lexicographic ordering on the members datetime
1482        # has that date lacks.
1483        args = [2000, 11, 29, 20, 58, 16, 999998]
1484        t1 = self.theclass(*args)
1485        t2 = self.theclass(*args)
1486        self.assertTrue(t1 == t2)
1487        self.assertTrue(t1 <= t2)
1488        self.assertTrue(t1 >= t2)
1489        self.assertFalse(t1 != t2)
1490        self.assertFalse(t1 < t2)
1491        self.assertFalse(t1 > t2)
1492        self.assertEqual(cmp(t1, t2), 0)
1493        self.assertEqual(cmp(t2, t1), 0)
1494
1495        for i in range(len(args)):
1496            newargs = args[:]
1497            newargs[i] = args[i] + 1
1498            t2 = self.theclass(*newargs)   # this is larger than t1
1499            self.assertTrue(t1 < t2)
1500            self.assertTrue(t2 > t1)
1501            self.assertTrue(t1 <= t2)
1502            self.assertTrue(t2 >= t1)
1503            self.assertTrue(t1 != t2)
1504            self.assertTrue(t2 != t1)
1505            self.assertFalse(t1 == t2)
1506            self.assertFalse(t2 == t1)
1507            self.assertFalse(t1 > t2)
1508            self.assertFalse(t2 < t1)
1509            self.assertFalse(t1 >= t2)
1510            self.assertFalse(t2 <= t1)
1511            self.assertEqual(cmp(t1, t2), -1)
1512            self.assertEqual(cmp(t2, t1), 1)
1513
1514
1515    # A helper for timestamp constructor tests.
1516    def verify_field_equality(self, expected, got):
1517        self.assertEqual(expected.tm_year, got.year)
1518        self.assertEqual(expected.tm_mon, got.month)
1519        self.assertEqual(expected.tm_mday, got.day)
1520        self.assertEqual(expected.tm_hour, got.hour)
1521        self.assertEqual(expected.tm_min, got.minute)
1522        self.assertEqual(expected.tm_sec, got.second)
1523
1524    def test_fromtimestamp(self):
1525        import time
1526
1527        ts = time.time()
1528        expected = time.localtime(ts)
1529        got = self.theclass.fromtimestamp(ts)
1530        self.verify_field_equality(expected, got)
1531
1532    def test_utcfromtimestamp(self):
1533        import time
1534
1535        ts = time.time()
1536        expected = time.gmtime(ts)
1537        got = self.theclass.utcfromtimestamp(ts)
1538        self.verify_field_equality(expected, got)
1539
1540    def test_microsecond_rounding(self):
1541        # Test whether fromtimestamp "rounds up" floats that are less
1542        # than one microsecond smaller than an integer.
1543        self.assertEqual(self.theclass.fromtimestamp(0.9999999),
1544                         self.theclass.fromtimestamp(1))
1545
1546    def test_insane_fromtimestamp(self):
1547        # It's possible that some platform maps time_t to double,
1548        # and that this test will fail there.  This test should
1549        # exempt such platforms (provided they return reasonable
1550        # results!).
1551        for insane in -1e200, 1e200:
1552            self.assertRaises(ValueError, self.theclass.fromtimestamp,
1553                              insane)
1554
1555    def test_insane_utcfromtimestamp(self):
1556        # It's possible that some platform maps time_t to double,
1557        # and that this test will fail there.  This test should
1558        # exempt such platforms (provided they return reasonable
1559        # results!).
1560        for insane in -1e200, 1e200:
1561            self.assertRaises(ValueError, self.theclass.utcfromtimestamp,
1562                              insane)
1563    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1564    def test_negative_float_fromtimestamp(self):
1565        # The result is tz-dependent; at least test that this doesn't
1566        # fail (like it did before bug 1646728 was fixed).
1567        self.theclass.fromtimestamp(-1.05)
1568
1569    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
1570    def test_negative_float_utcfromtimestamp(self):
1571        d = self.theclass.utcfromtimestamp(-1.05)
1572        self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
1573
1574    def test_utcnow(self):
1575        import time
1576
1577        # Call it a success if utcnow() and utcfromtimestamp() are within
1578        # a second of each other.
1579        tolerance = timedelta(seconds=1)
1580        for dummy in range(3):
1581            from_now = self.theclass.utcnow()
1582            from_timestamp = self.theclass.utcfromtimestamp(time.time())
1583            if abs(from_timestamp - from_now) <= tolerance:
1584                break
1585            # Else try again a few times.
1586        self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
1587
1588    def test_strptime(self):
1589        import _strptime
1590
1591        string = '2004-12-01 13:02:47.197'
1592        format = '%Y-%m-%d %H:%M:%S.%f'
1593        result, frac = _strptime._strptime(string, format)
1594        expected = self.theclass(*(result[0:6]+(frac,)))
1595        got = self.theclass.strptime(string, format)
1596        self.assertEqual(expected, got)
1597
1598    def test_more_timetuple(self):
1599        # This tests fields beyond those tested by the TestDate.test_timetuple.
1600        t = self.theclass(2004, 12, 31, 6, 22, 33)
1601        self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
1602        self.assertEqual(t.timetuple(),
1603                         (t.year, t.month, t.day,
1604                          t.hour, t.minute, t.second,
1605                          t.weekday(),
1606                          t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
1607                          -1))
1608        tt = t.timetuple()
1609        self.assertEqual(tt.tm_year, t.year)
1610        self.assertEqual(tt.tm_mon, t.month)
1611        self.assertEqual(tt.tm_mday, t.day)
1612        self.assertEqual(tt.tm_hour, t.hour)
1613        self.assertEqual(tt.tm_min, t.minute)
1614        self.assertEqual(tt.tm_sec, t.second)
1615        self.assertEqual(tt.tm_wday, t.weekday())
1616        self.assertEqual(tt.tm_yday, t.toordinal() -
1617                                     date(t.year, 1, 1).toordinal() + 1)
1618        self.assertEqual(tt.tm_isdst, -1)
1619
1620    def test_more_strftime(self):
1621        # This tests fields beyond those tested by the TestDate.test_strftime.
1622        t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
1623        self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
1624                                    "12 31 04 000047 33 22 06 366")
1625
1626    def test_extract(self):
1627        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1628        self.assertEqual(dt.date(), date(2002, 3, 4))
1629        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
1630
1631    def test_combine(self):
1632        d = date(2002, 3, 4)
1633        t = time(18, 45, 3, 1234)
1634        expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
1635        combine = self.theclass.combine
1636        dt = combine(d, t)
1637        self.assertEqual(dt, expected)
1638
1639        dt = combine(time=t, date=d)
1640        self.assertEqual(dt, expected)
1641
1642        self.assertEqual(d, dt.date())
1643        self.assertEqual(t, dt.time())
1644        self.assertEqual(dt, combine(dt.date(), dt.time()))
1645
1646        self.assertRaises(TypeError, combine) # need an arg
1647        self.assertRaises(TypeError, combine, d) # need two args
1648        self.assertRaises(TypeError, combine, t, d) # args reversed
1649        self.assertRaises(TypeError, combine, d, t, 1) # too many args
1650        self.assertRaises(TypeError, combine, "date", "time") # wrong types
1651
1652    def test_replace(self):
1653        cls = self.theclass
1654        args = [1, 2, 3, 4, 5, 6, 7]
1655        base = cls(*args)
1656        self.assertEqual(base, base.replace())
1657
1658        i = 0
1659        for name, newval in (("year", 2),
1660                             ("month", 3),
1661                             ("day", 4),
1662                             ("hour", 5),
1663                             ("minute", 6),
1664                             ("second", 7),
1665                             ("microsecond", 8)):
1666            newargs = args[:]
1667            newargs[i] = newval
1668            expected = cls(*newargs)
1669            got = base.replace(**{name: newval})
1670            self.assertEqual(expected, got)
1671            i += 1
1672
1673        # Out of bounds.
1674        base = cls(2000, 2, 29)
1675        self.assertRaises(ValueError, base.replace, year=2001)
1676
1677    def test_astimezone(self):
1678        # Pretty boring!  The TZ test is more interesting here.  astimezone()
1679        # simply can't be applied to a naive object.
1680        dt = self.theclass.now()
1681        f = FixedOffset(44, "")
1682        self.assertRaises(TypeError, dt.astimezone) # not enough args
1683        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
1684        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
1685        self.assertRaises(ValueError, dt.astimezone, f) # naive
1686        self.assertRaises(ValueError, dt.astimezone, tz=f)  # naive
1687
1688        class Bogus(tzinfo):
1689            def utcoffset(self, dt): return None
1690            def dst(self, dt): return timedelta(0)
1691        bog = Bogus()
1692        self.assertRaises(ValueError, dt.astimezone, bog)   # naive
1693
1694        class AlsoBogus(tzinfo):
1695            def utcoffset(self, dt): return timedelta(0)
1696            def dst(self, dt): return None
1697        alsobog = AlsoBogus()
1698        self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
1699
1700    def test_subclass_datetime(self):
1701
1702        class C(self.theclass):
1703            theAnswer = 42
1704
1705            def __new__(cls, *args, **kws):
1706                temp = kws.copy()
1707                extra = temp.pop('extra')
1708                result = self.theclass.__new__(cls, *args, **temp)
1709                result.extra = extra
1710                return result
1711
1712            def newmeth(self, start):
1713                return start + self.year + self.month + self.second
1714
1715        args = 2003, 4, 14, 12, 13, 41
1716
1717        dt1 = self.theclass(*args)
1718        dt2 = C(*args, **{'extra': 7})
1719
1720        self.assertEqual(dt2.__class__, C)
1721        self.assertEqual(dt2.theAnswer, 42)
1722        self.assertEqual(dt2.extra, 7)
1723        self.assertEqual(dt1.toordinal(), dt2.toordinal())
1724        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
1725                                          dt1.second - 7)
1726
1727class SubclassTime(time):
1728    sub_var = 1
1729
1730class TestTime(HarmlessMixedComparison, unittest.TestCase):
1731
1732    theclass = time
1733
1734    def test_basic_attributes(self):
1735        t = self.theclass(12, 0)
1736        self.assertEqual(t.hour, 12)
1737        self.assertEqual(t.minute, 0)
1738        self.assertEqual(t.second, 0)
1739        self.assertEqual(t.microsecond, 0)
1740
1741    def test_basic_attributes_nonzero(self):
1742        # Make sure all attributes are non-zero so bugs in
1743        # bit-shifting access show up.
1744        t = self.theclass(12, 59, 59, 8000)
1745        self.assertEqual(t.hour, 12)
1746        self.assertEqual(t.minute, 59)
1747        self.assertEqual(t.second, 59)
1748        self.assertEqual(t.microsecond, 8000)
1749
1750    def test_roundtrip(self):
1751        t = self.theclass(1, 2, 3, 4)
1752
1753        # Verify t -> string -> time identity.
1754        s = repr(t)
1755        self.assertTrue(s.startswith('datetime.'))
1756        s = s[9:]
1757        t2 = eval(s)
1758        self.assertEqual(t, t2)
1759
1760        # Verify identity via reconstructing from pieces.
1761        t2 = self.theclass(t.hour, t.minute, t.second,
1762                           t.microsecond)
1763        self.assertEqual(t, t2)
1764
1765    def test_comparing(self):
1766        args = [1, 2, 3, 4]
1767        t1 = self.theclass(*args)
1768        t2 = self.theclass(*args)
1769        self.assertTrue(t1 == t2)
1770        self.assertTrue(t1 <= t2)
1771        self.assertTrue(t1 >= t2)
1772        self.assertFalse(t1 != t2)
1773        self.assertFalse(t1 < t2)
1774        self.assertFalse(t1 > t2)
1775        self.assertEqual(cmp(t1, t2), 0)
1776        self.assertEqual(cmp(t2, t1), 0)
1777
1778        for i in range(len(args)):
1779            newargs = args[:]
1780            newargs[i] = args[i] + 1
1781            t2 = self.theclass(*newargs)   # this is larger than t1
1782            self.assertTrue(t1 < t2)
1783            self.assertTrue(t2 > t1)
1784            self.assertTrue(t1 <= t2)
1785            self.assertTrue(t2 >= t1)
1786            self.assertTrue(t1 != t2)
1787            self.assertTrue(t2 != t1)
1788            self.assertFalse(t1 == t2)
1789            self.assertFalse(t2 == t1)
1790            self.assertFalse(t1 > t2)
1791            self.assertFalse(t2 < t1)
1792            self.assertFalse(t1 >= t2)
1793            self.assertFalse(t2 <= t1)
1794            self.assertEqual(cmp(t1, t2), -1)
1795            self.assertEqual(cmp(t2, t1), 1)
1796
1797        for badarg in OTHERSTUFF:
1798            self.assertEqual(t1 == badarg, False)
1799            self.assertEqual(t1 != badarg, True)
1800            self.assertEqual(badarg == t1, False)
1801            self.assertEqual(badarg != t1, True)
1802
1803            self.assertRaises(TypeError, lambda: t1 <= badarg)
1804            self.assertRaises(TypeError, lambda: t1 < badarg)
1805            self.assertRaises(TypeError, lambda: t1 > badarg)
1806            self.assertRaises(TypeError, lambda: t1 >= badarg)
1807            self.assertRaises(TypeError, lambda: badarg <= t1)
1808            self.assertRaises(TypeError, lambda: badarg < t1)
1809            self.assertRaises(TypeError, lambda: badarg > t1)
1810            self.assertRaises(TypeError, lambda: badarg >= t1)
1811
1812    def test_bad_constructor_arguments(self):
1813        # bad hours
1814        self.theclass(0, 0)    # no exception
1815        self.theclass(23, 0)   # no exception
1816        self.assertRaises(ValueError, self.theclass, -1, 0)
1817        self.assertRaises(ValueError, self.theclass, 24, 0)
1818        # bad minutes
1819        self.theclass(23, 0)    # no exception
1820        self.theclass(23, 59)   # no exception
1821        self.assertRaises(ValueError, self.theclass, 23, -1)
1822        self.assertRaises(ValueError, self.theclass, 23, 60)
1823        # bad seconds
1824        self.theclass(23, 59, 0)    # no exception
1825        self.theclass(23, 59, 59)   # no exception
1826        self.assertRaises(ValueError, self.theclass, 23, 59, -1)
1827        self.assertRaises(ValueError, self.theclass, 23, 59, 60)
1828        # bad microseconds
1829        self.theclass(23, 59, 59, 0)        # no exception
1830        self.theclass(23, 59, 59, 999999)   # no exception
1831        self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
1832        self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
1833
1834    def test_hash_equality(self):
1835        d = self.theclass(23, 30, 17)
1836        e = self.theclass(23, 30, 17)
1837        self.assertEqual(d, e)
1838        self.assertEqual(hash(d), hash(e))
1839
1840        dic = {d: 1}
1841        dic[e] = 2
1842        self.assertEqual(len(dic), 1)
1843        self.assertEqual(dic[d], 2)
1844        self.assertEqual(dic[e], 2)
1845
1846        d = self.theclass(0,  5, 17)
1847        e = self.theclass(0,  5, 17)
1848        self.assertEqual(d, e)
1849        self.assertEqual(hash(d), hash(e))
1850
1851        dic = {d: 1}
1852        dic[e] = 2
1853        self.assertEqual(len(dic), 1)
1854        self.assertEqual(dic[d], 2)
1855        self.assertEqual(dic[e], 2)
1856
1857    def test_isoformat(self):
1858        t = self.theclass(4, 5, 1, 123)
1859        self.assertEqual(t.isoformat(), "04:05:01.000123")
1860        self.assertEqual(t.isoformat(), str(t))
1861
1862        t = self.theclass()
1863        self.assertEqual(t.isoformat(), "00:00:00")
1864        self.assertEqual(t.isoformat(), str(t))
1865
1866        t = self.theclass(microsecond=1)
1867        self.assertEqual(t.isoformat(), "00:00:00.000001")
1868        self.assertEqual(t.isoformat(), str(t))
1869
1870        t = self.theclass(microsecond=10)
1871        self.assertEqual(t.isoformat(), "00:00:00.000010")
1872        self.assertEqual(t.isoformat(), str(t))
1873
1874        t = self.theclass(microsecond=100)
1875        self.assertEqual(t.isoformat(), "00:00:00.000100")
1876        self.assertEqual(t.isoformat(), str(t))
1877
1878        t = self.theclass(microsecond=1000)
1879        self.assertEqual(t.isoformat(), "00:00:00.001000")
1880        self.assertEqual(t.isoformat(), str(t))
1881
1882        t = self.theclass(microsecond=10000)
1883        self.assertEqual(t.isoformat(), "00:00:00.010000")
1884        self.assertEqual(t.isoformat(), str(t))
1885
1886        t = self.theclass(microsecond=100000)
1887        self.assertEqual(t.isoformat(), "00:00:00.100000")
1888        self.assertEqual(t.isoformat(), str(t))
1889
1890    def test_1653736(self):
1891        # verify it doesn't accept extra keyword arguments
1892        t = self.theclass(second=1)
1893        self.assertRaises(TypeError, t.isoformat, foo=3)
1894
1895    def test_strftime(self):
1896        t = self.theclass(1, 2, 3, 4)
1897        self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
1898        # A naive object replaces %z and %Z with empty strings.
1899        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1900
1901    def test_format(self):
1902        t = self.theclass(1, 2, 3, 4)
1903        self.assertEqual(t.__format__(''), str(t))
1904
1905        # check that a derived class's __str__() gets called
1906        class A(self.theclass):
1907            def __str__(self):
1908                return 'A'
1909        a = A(1, 2, 3, 4)
1910        self.assertEqual(a.__format__(''), 'A')
1911
1912        # check that a derived class's strftime gets called
1913        class B(self.theclass):
1914            def strftime(self, format_spec):
1915                return 'B'
1916        b = B(1, 2, 3, 4)
1917        self.assertEqual(b.__format__(''), str(t))
1918
1919        for fmt in ['%H %M %S',
1920                    ]:
1921            self.assertEqual(t.__format__(fmt), t.strftime(fmt))
1922            self.assertEqual(a.__format__(fmt), t.strftime(fmt))
1923            self.assertEqual(b.__format__(fmt), 'B')
1924
1925    def test_str(self):
1926        self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
1927        self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
1928        self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
1929        self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
1930        self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
1931
1932    def test_repr(self):
1933        name = 'datetime.' + self.theclass.__name__
1934        self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
1935                         "%s(1, 2, 3, 4)" % name)
1936        self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
1937                         "%s(10, 2, 3, 4000)" % name)
1938        self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
1939                         "%s(0, 2, 3, 400000)" % name)
1940        self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
1941                         "%s(12, 2, 3)" % name)
1942        self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
1943                         "%s(23, 15)" % name)
1944
1945    def test_resolution_info(self):
1946        self.assertIsInstance(self.theclass.min, self.theclass)
1947        self.assertIsInstance(self.theclass.max, self.theclass)
1948        self.assertIsInstance(self.theclass.resolution, timedelta)
1949        self.assertTrue(self.theclass.max > self.theclass.min)
1950
1951    def test_pickling(self):
1952        args = 20, 59, 16, 64**2
1953        orig = self.theclass(*args)
1954        for pickler, unpickler, proto in pickle_choices:
1955            green = pickler.dumps(orig, proto)
1956            derived = unpickler.loads(green)
1957            self.assertEqual(orig, derived)
1958
1959    def test_pickling_subclass_time(self):
1960        args = 20, 59, 16, 64**2
1961        orig = SubclassTime(*args)
1962        for pickler, unpickler, proto in pickle_choices:
1963            green = pickler.dumps(orig, proto)
1964            derived = unpickler.loads(green)
1965            self.assertEqual(orig, derived)
1966
1967    def test_bool(self):
1968        cls = self.theclass
1969        self.assertTrue(cls(1))
1970        self.assertTrue(cls(0, 1))
1971        self.assertTrue(cls(0, 0, 1))
1972        self.assertTrue(cls(0, 0, 0, 1))
1973        self.assertFalse(cls(0))
1974        self.assertFalse(cls())
1975
1976    def test_replace(self):
1977        cls = self.theclass
1978        args = [1, 2, 3, 4]
1979        base = cls(*args)
1980        self.assertEqual(base, base.replace())
1981
1982        i = 0
1983        for name, newval in (("hour", 5),
1984                             ("minute", 6),
1985                             ("second", 7),
1986                             ("microsecond", 8)):
1987            newargs = args[:]
1988            newargs[i] = newval
1989            expected = cls(*newargs)
1990            got = base.replace(**{name: newval})
1991            self.assertEqual(expected, got)
1992            i += 1
1993
1994        # Out of bounds.
1995        base = cls(1)
1996        self.assertRaises(ValueError, base.replace, hour=24)
1997        self.assertRaises(ValueError, base.replace, minute=-1)
1998        self.assertRaises(ValueError, base.replace, second=100)
1999        self.assertRaises(ValueError, base.replace, microsecond=1000000)
2000
2001    def test_subclass_time(self):
2002
2003        class C(self.theclass):
2004            theAnswer = 42
2005
2006            def __new__(cls, *args, **kws):
2007                temp = kws.copy()
2008                extra = temp.pop('extra')
2009                result = self.theclass.__new__(cls, *args, **temp)
2010                result.extra = extra
2011                return result
2012
2013            def newmeth(self, start):
2014                return start + self.hour + self.second
2015
2016        args = 4, 5, 6
2017
2018        dt1 = self.theclass(*args)
2019        dt2 = C(*args, **{'extra': 7})
2020
2021        self.assertEqual(dt2.__class__, C)
2022        self.assertEqual(dt2.theAnswer, 42)
2023        self.assertEqual(dt2.extra, 7)
2024        self.assertEqual(dt1.isoformat(), dt2.isoformat())
2025        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2026
2027    def test_backdoor_resistance(self):
2028        # see TestDate.test_backdoor_resistance().
2029        base = '2:59.0'
2030        for hour_byte in ' ', '9', chr(24), '\xff':
2031            self.assertRaises(TypeError, self.theclass,
2032                                         hour_byte + base[1:])
2033
2034# A mixin for classes with a tzinfo= argument.  Subclasses must define
2035# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
2036# must be legit (which is true for time and datetime).
2037class TZInfoBase:
2038
2039    def test_argument_passing(self):
2040        cls = self.theclass
2041        # A datetime passes itself on, a time passes None.
2042        class introspective(tzinfo):
2043            def tzname(self, dt):    return dt and "real" or "none"
2044            def utcoffset(self, dt):
2045                return timedelta(minutes = dt and 42 or -42)
2046            dst = utcoffset
2047
2048        obj = cls(1, 2, 3, tzinfo=introspective())
2049
2050        expected = cls is time and "none" or "real"
2051        self.assertEqual(obj.tzname(), expected)
2052
2053        expected = timedelta(minutes=(cls is time and -42 or 42))
2054        self.assertEqual(obj.utcoffset(), expected)
2055        self.assertEqual(obj.dst(), expected)
2056
2057    def test_bad_tzinfo_classes(self):
2058        cls = self.theclass
2059        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2060
2061        class NiceTry(object):
2062            def __init__(self): pass
2063            def utcoffset(self, dt): pass
2064        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2065
2066        class BetterTry(tzinfo):
2067            def __init__(self): pass
2068            def utcoffset(self, dt): pass
2069        b = BetterTry()
2070        t = cls(1, 1, 1, tzinfo=b)
2071        self.assertIs(t.tzinfo, b)
2072
2073    def test_utc_offset_out_of_bounds(self):
2074        class Edgy(tzinfo):
2075            def __init__(self, offset):
2076                self.offset = timedelta(minutes=offset)
2077            def utcoffset(self, dt):
2078                return self.offset
2079
2080        cls = self.theclass
2081        for offset, legit in ((-1440, False),
2082                              (-1439, True),
2083                              (1439, True),
2084                              (1440, False)):
2085            if cls is time:
2086                t = cls(1, 2, 3, tzinfo=Edgy(offset))
2087            elif cls is datetime:
2088                t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2089            else:
2090                assert 0, "impossible"
2091            if legit:
2092                aofs = abs(offset)
2093                h, m = divmod(aofs, 60)
2094                tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2095                if isinstance(t, datetime):
2096                    t = t.timetz()
2097                self.assertEqual(str(t), "01:02:03" + tag)
2098            else:
2099                self.assertRaises(ValueError, str, t)
2100
2101    def test_tzinfo_classes(self):
2102        cls = self.theclass
2103        class C1(tzinfo):
2104            def utcoffset(self, dt): return None
2105            def dst(self, dt): return None
2106            def tzname(self, dt): return None
2107        for t in (cls(1, 1, 1),
2108                  cls(1, 1, 1, tzinfo=None),
2109                  cls(1, 1, 1, tzinfo=C1())):
2110            self.assertIsNone(t.utcoffset())
2111            self.assertIsNone(t.dst())
2112            self.assertIsNone(t.tzname())
2113
2114        class C3(tzinfo):
2115            def utcoffset(self, dt): return timedelta(minutes=-1439)
2116            def dst(self, dt): return timedelta(minutes=1439)
2117            def tzname(self, dt): return "aname"
2118        t = cls(1, 1, 1, tzinfo=C3())
2119        self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2120        self.assertEqual(t.dst(), timedelta(minutes=1439))
2121        self.assertEqual(t.tzname(), "aname")
2122
2123        # Wrong types.
2124        class C4(tzinfo):
2125            def utcoffset(self, dt): return "aname"
2126            def dst(self, dt): return 7
2127            def tzname(self, dt): return 0
2128        t = cls(1, 1, 1, tzinfo=C4())
2129        self.assertRaises(TypeError, t.utcoffset)
2130        self.assertRaises(TypeError, t.dst)
2131        self.assertRaises(TypeError, t.tzname)
2132
2133        # Offset out of range.
2134        class C6(tzinfo):
2135            def utcoffset(self, dt): return timedelta(hours=-24)
2136            def dst(self, dt): return timedelta(hours=24)
2137        t = cls(1, 1, 1, tzinfo=C6())
2138        self.assertRaises(ValueError, t.utcoffset)
2139        self.assertRaises(ValueError, t.dst)
2140
2141        # Not a whole number of minutes.
2142        class C7(tzinfo):
2143            def utcoffset(self, dt): return timedelta(seconds=61)
2144            def dst(self, dt): return timedelta(microseconds=-81)
2145        t = cls(1, 1, 1, tzinfo=C7())
2146        self.assertRaises(ValueError, t.utcoffset)
2147        self.assertRaises(ValueError, t.dst)
2148
2149    def test_aware_compare(self):
2150        cls = self.theclass
2151
2152        # Ensure that utcoffset() gets ignored if the comparands have
2153        # the same tzinfo member.
2154        class OperandDependentOffset(tzinfo):
2155            def utcoffset(self, t):
2156                if t.minute < 10:
2157                    # d0 and d1 equal after adjustment
2158                    return timedelta(minutes=t.minute)
2159                else:
2160                    # d2 off in the weeds
2161                    return timedelta(minutes=59)
2162
2163        base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2164        d0 = base.replace(minute=3)
2165        d1 = base.replace(minute=9)
2166        d2 = base.replace(minute=11)
2167        for x in d0, d1, d2:
2168            for y in d0, d1, d2:
2169                got = cmp(x, y)
2170                expected = cmp(x.minute, y.minute)
2171                self.assertEqual(got, expected)
2172
2173        # However, if they're different members, uctoffset is not ignored.
2174        # Note that a time can't actually have an operand-depedent offset,
2175        # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2176        # so skip this test for time.
2177        if cls is not time:
2178            d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2179            d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2180            d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2181            for x in d0, d1, d2:
2182                for y in d0, d1, d2:
2183                    got = cmp(x, y)
2184                    if (x is d0 or x is d1) and (y is d0 or y is d1):
2185                        expected = 0
2186                    elif x is y is d2:
2187                        expected = 0
2188                    elif x is d2:
2189                        expected = -1
2190                    else:
2191                        assert y is d2
2192                        expected = 1
2193                    self.assertEqual(got, expected)
2194
2195
2196# Testing time objects with a non-None tzinfo.
2197class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2198    theclass = time
2199
2200    def test_empty(self):
2201        t = self.theclass()
2202        self.assertEqual(t.hour, 0)
2203        self.assertEqual(t.minute, 0)
2204        self.assertEqual(t.second, 0)
2205        self.assertEqual(t.microsecond, 0)
2206        self.assertIsNone(t.tzinfo)
2207
2208    def test_zones(self):
2209        est = FixedOffset(-300, "EST", 1)
2210        utc = FixedOffset(0, "UTC", -2)
2211        met = FixedOffset(60, "MET", 3)
2212        t1 = time( 7, 47, tzinfo=est)
2213        t2 = time(12, 47, tzinfo=utc)
2214        t3 = time(13, 47, tzinfo=met)
2215        t4 = time(microsecond=40)
2216        t5 = time(microsecond=40, tzinfo=utc)
2217
2218        self.assertEqual(t1.tzinfo, est)
2219        self.assertEqual(t2.tzinfo, utc)
2220        self.assertEqual(t3.tzinfo, met)
2221        self.assertIsNone(t4.tzinfo)
2222        self.assertEqual(t5.tzinfo, utc)
2223
2224        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2225        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2226        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2227        self.assertIsNone(t4.utcoffset())
2228        self.assertRaises(TypeError, t1.utcoffset, "no args")
2229
2230        self.assertEqual(t1.tzname(), "EST")
2231        self.assertEqual(t2.tzname(), "UTC")
2232        self.assertEqual(t3.tzname(), "MET")
2233        self.assertIsNone(t4.tzname())
2234        self.assertRaises(TypeError, t1.tzname, "no args")
2235
2236        self.assertEqual(t1.dst(), timedelta(minutes=1))
2237        self.assertEqual(t2.dst(), timedelta(minutes=-2))
2238        self.assertEqual(t3.dst(), timedelta(minutes=3))
2239        self.assertIsNone(t4.dst())
2240        self.assertRaises(TypeError, t1.dst, "no args")
2241
2242        self.assertEqual(hash(t1), hash(t2))
2243        self.assertEqual(hash(t1), hash(t3))
2244        self.assertEqual(hash(t2), hash(t3))
2245
2246        self.assertEqual(t1, t2)
2247        self.assertEqual(t1, t3)
2248        self.assertEqual(t2, t3)
2249        self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive
2250        self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2251        self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2252
2253        self.assertEqual(str(t1), "07:47:00-05:00")
2254        self.assertEqual(str(t2), "12:47:00+00:00")
2255        self.assertEqual(str(t3), "13:47:00+01:00")
2256        self.assertEqual(str(t4), "00:00:00.000040")
2257        self.assertEqual(str(t5), "00:00:00.000040+00:00")
2258
2259        self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2260        self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2261        self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2262        self.assertEqual(t4.isoformat(), "00:00:00.000040")
2263        self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2264
2265        d = 'datetime.time'
2266        self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2267        self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2268        self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2269        self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2270        self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2271
2272        self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2273                                     "07:47:00 %Z=EST %z=-0500")
2274        self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2275        self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2276
2277        yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2278        t1 = time(23, 59, tzinfo=yuck)
2279        self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2280                                     "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2281
2282        # Check that an invalid tzname result raises an exception.
2283        class Badtzname(tzinfo):
2284            def tzname(self, dt): return 42
2285        t = time(2, 3, 4, tzinfo=Badtzname())
2286        self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2287        self.assertRaises(TypeError, t.strftime, "%Z")
2288
2289    def test_hash_edge_cases(self):
2290        # Offsets that overflow a basic time.
2291        t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2292        t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2293        self.assertEqual(hash(t1), hash(t2))
2294
2295        t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2296        t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2297        self.assertEqual(hash(t1), hash(t2))
2298
2299    def test_pickling(self):
2300        # Try one without a tzinfo.
2301        args = 20, 59, 16, 64**2
2302        orig = self.theclass(*args)
2303        for pickler, unpickler, proto in pickle_choices:
2304            green = pickler.dumps(orig, proto)
2305            derived = unpickler.loads(green)
2306            self.assertEqual(orig, derived)
2307
2308        # Try one with a tzinfo.
2309        tinfo = PicklableFixedOffset(-300, 'cookie')
2310        orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2311        for pickler, unpickler, proto in pickle_choices:
2312            green = pickler.dumps(orig, proto)
2313            derived = unpickler.loads(green)
2314            self.assertEqual(orig, derived)
2315            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2316            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2317            self.assertEqual(derived.tzname(), 'cookie')
2318
2319    def test_more_bool(self):
2320        # Test cases with non-None tzinfo.
2321        cls = self.theclass
2322
2323        t = cls(0, tzinfo=FixedOffset(-300, ""))
2324        self.assertTrue(t)
2325
2326        t = cls(5, tzinfo=FixedOffset(-300, ""))
2327        self.assertTrue(t)
2328
2329        t = cls(5, tzinfo=FixedOffset(300, ""))
2330        self.assertFalse(t)
2331
2332        t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2333        self.assertFalse(t)
2334
2335        # Mostly ensuring this doesn't overflow internally.
2336        t = cls(0, tzinfo=FixedOffset(23*60 + 59, ""))
2337        self.assertTrue(t)
2338
2339        # But this should yield a value error -- the utcoffset is bogus.
2340        t = cls(0, tzinfo=FixedOffset(24*60, ""))
2341        self.assertRaises(ValueError, lambda: bool(t))
2342
2343        # Likewise.
2344        t = cls(0, tzinfo=FixedOffset(-24*60, ""))
2345        self.assertRaises(ValueError, lambda: bool(t))
2346
2347    def test_replace(self):
2348        cls = self.theclass
2349        z100 = FixedOffset(100, "+100")
2350        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2351        args = [1, 2, 3, 4, z100]
2352        base = cls(*args)
2353        self.assertEqual(base, base.replace())
2354
2355        i = 0
2356        for name, newval in (("hour", 5),
2357                             ("minute", 6),
2358                             ("second", 7),
2359                             ("microsecond", 8),
2360                             ("tzinfo", zm200)):
2361            newargs = args[:]
2362            newargs[i] = newval
2363            expected = cls(*newargs)
2364            got = base.replace(**{name: newval})
2365            self.assertEqual(expected, got)
2366            i += 1
2367
2368        # Ensure we can get rid of a tzinfo.
2369        self.assertEqual(base.tzname(), "+100")
2370        base2 = base.replace(tzinfo=None)
2371        self.assertIsNone(base2.tzinfo)
2372        self.assertIsNone(base2.tzname())
2373
2374        # Ensure we can add one.
2375        base3 = base2.replace(tzinfo=z100)
2376        self.assertEqual(base, base3)
2377        self.assertIs(base.tzinfo, base3.tzinfo)
2378
2379        # Out of bounds.
2380        base = cls(1)
2381        self.assertRaises(ValueError, base.replace, hour=24)
2382        self.assertRaises(ValueError, base.replace, minute=-1)
2383        self.assertRaises(ValueError, base.replace, second=100)
2384        self.assertRaises(ValueError, base.replace, microsecond=1000000)
2385
2386    def test_mixed_compare(self):
2387        t1 = time(1, 2, 3)
2388        t2 = time(1, 2, 3)
2389        self.assertEqual(t1, t2)
2390        t2 = t2.replace(tzinfo=None)
2391        self.assertEqual(t1, t2)
2392        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2393        self.assertEqual(t1, t2)
2394        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2395        self.assertRaises(TypeError, lambda: t1 == t2)
2396
2397        # In time w/ identical tzinfo objects, utcoffset is ignored.
2398        class Varies(tzinfo):
2399            def __init__(self):
2400                self.offset = timedelta(minutes=22)
2401            def utcoffset(self, t):
2402                self.offset += timedelta(minutes=1)
2403                return self.offset
2404
2405        v = Varies()
2406        t1 = t2.replace(tzinfo=v)
2407        t2 = t2.replace(tzinfo=v)
2408        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2409        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2410        self.assertEqual(t1, t2)
2411
2412        # But if they're not identical, it isn't ignored.
2413        t2 = t2.replace(tzinfo=Varies())
2414        self.assertTrue(t1 < t2)  # t1's offset counter still going up
2415
2416    def test_subclass_timetz(self):
2417
2418        class C(self.theclass):
2419            theAnswer = 42
2420
2421            def __new__(cls, *args, **kws):
2422                temp = kws.copy()
2423                extra = temp.pop('extra')
2424                result = self.theclass.__new__(cls, *args, **temp)
2425                result.extra = extra
2426                return result
2427
2428            def newmeth(self, start):
2429                return start + self.hour + self.second
2430
2431        args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2432
2433        dt1 = self.theclass(*args)
2434        dt2 = C(*args, **{'extra': 7})
2435
2436        self.assertEqual(dt2.__class__, C)
2437        self.assertEqual(dt2.theAnswer, 42)
2438        self.assertEqual(dt2.extra, 7)
2439        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2440        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2441
2442
2443# Testing datetime objects with a non-None tzinfo.
2444
2445class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
2446    theclass = datetime
2447
2448    def test_trivial(self):
2449        dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
2450        self.assertEqual(dt.year, 1)
2451        self.assertEqual(dt.month, 2)
2452        self.assertEqual(dt.day, 3)
2453        self.assertEqual(dt.hour, 4)
2454        self.assertEqual(dt.minute, 5)
2455        self.assertEqual(dt.second, 6)
2456        self.assertEqual(dt.microsecond, 7)
2457        self.assertEqual(dt.tzinfo, None)
2458
2459    def test_even_more_compare(self):
2460        # The test_compare() and test_more_compare() inherited from TestDate
2461        # and TestDateTime covered non-tzinfo cases.
2462
2463        # Smallest possible after UTC adjustment.
2464        t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2465        # Largest possible after UTC adjustment.
2466        t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2467                           tzinfo=FixedOffset(-1439, ""))
2468
2469        # Make sure those compare correctly, and w/o overflow.
2470        self.assertTrue(t1 < t2)
2471        self.assertTrue(t1 != t2)
2472        self.assertTrue(t2 > t1)
2473
2474        self.assertTrue(t1 == t1)
2475        self.assertTrue(t2 == t2)
2476
2477        # Equal afer adjustment.
2478        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
2479        t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
2480        self.assertEqual(t1, t2)
2481
2482        # Change t1 not to subtract a minute, and t1 should be larger.
2483        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
2484        self.assertTrue(t1 > t2)
2485
2486        # Change t1 to subtract 2 minutes, and t1 should be smaller.
2487        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
2488        self.assertTrue(t1 < t2)
2489
2490        # Back to the original t1, but make seconds resolve it.
2491        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2492                           second=1)
2493        self.assertTrue(t1 > t2)
2494
2495        # Likewise, but make microseconds resolve it.
2496        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
2497                           microsecond=1)
2498        self.assertTrue(t1 > t2)
2499
2500        # Make t2 naive and it should fail.
2501        t2 = self.theclass.min
2502        self.assertRaises(TypeError, lambda: t1 == t2)
2503        self.assertEqual(t2, t2)
2504
2505        # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
2506        class Naive(tzinfo):
2507            def utcoffset(self, dt): return None
2508        t2 = self.theclass(5, 6, 7, tzinfo=Naive())
2509        self.assertRaises(TypeError, lambda: t1 == t2)
2510        self.assertEqual(t2, t2)
2511
2512        # OTOH, it's OK to compare two of these mixing the two ways of being
2513        # naive.
2514        t1 = self.theclass(5, 6, 7)
2515        self.assertEqual(t1, t2)
2516
2517        # Try a bogus uctoffset.
2518        class Bogus(tzinfo):
2519            def utcoffset(self, dt):
2520                return timedelta(minutes=1440) # out of bounds
2521        t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
2522        t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
2523        self.assertRaises(ValueError, lambda: t1 == t2)
2524
2525    def test_pickling(self):
2526        # Try one without a tzinfo.
2527        args = 6, 7, 23, 20, 59, 1, 64**2
2528        orig = self.theclass(*args)
2529        for pickler, unpickler, proto in pickle_choices:
2530            green = pickler.dumps(orig, proto)
2531            derived = unpickler.loads(green)
2532            self.assertEqual(orig, derived)
2533
2534        # Try one with a tzinfo.
2535        tinfo = PicklableFixedOffset(-300, 'cookie')
2536        orig = self.theclass(*args, **{'tzinfo': tinfo})
2537        derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
2538        for pickler, unpickler, proto in pickle_choices:
2539            green = pickler.dumps(orig, proto)
2540            derived = unpickler.loads(green)
2541            self.assertEqual(orig, derived)
2542            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2543            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2544            self.assertEqual(derived.tzname(), 'cookie')
2545
2546    def test_extreme_hashes(self):
2547        # If an attempt is made to hash these via subtracting the offset
2548        # then hashing a datetime object, OverflowError results.  The
2549        # Python implementation used to blow up here.
2550        t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
2551        hash(t)
2552        t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2553                          tzinfo=FixedOffset(-1439, ""))
2554        hash(t)
2555
2556        # OTOH, an OOB offset should blow up.
2557        t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
2558        self.assertRaises(ValueError, hash, t)
2559
2560    def test_zones(self):
2561        est = FixedOffset(-300, "EST")
2562        utc = FixedOffset(0, "UTC")
2563        met = FixedOffset(60, "MET")
2564        t1 = datetime(2002, 3, 19,  7, 47, tzinfo=est)
2565        t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
2566        t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
2567        self.assertEqual(t1.tzinfo, est)
2568        self.assertEqual(t2.tzinfo, utc)
2569        self.assertEqual(t3.tzinfo, met)
2570        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2571        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2572        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2573        self.assertEqual(t1.tzname(), "EST")
2574        self.assertEqual(t2.tzname(), "UTC")
2575        self.assertEqual(t3.tzname(), "MET")
2576        self.assertEqual(hash(t1), hash(t2))
2577        self.assertEqual(hash(t1), hash(t3))
2578        self.assertEqual(hash(t2), hash(t3))
2579        self.assertEqual(t1, t2)
2580        self.assertEqual(t1, t3)
2581        self.assertEqual(t2, t3)
2582        self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
2583        self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
2584        self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
2585        d = 'datetime.datetime(2002, 3, 19, '
2586        self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
2587        self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
2588        self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
2589
2590    def test_combine(self):
2591        met = FixedOffset(60, "MET")
2592        d = date(2002, 3, 4)
2593        tz = time(18, 45, 3, 1234, tzinfo=met)
2594        dt = datetime.combine(d, tz)
2595        self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
2596                                        tzinfo=met))
2597
2598    def test_extract(self):
2599        met = FixedOffset(60, "MET")
2600        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
2601        self.assertEqual(dt.date(), date(2002, 3, 4))
2602        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2603        self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
2604
2605    def test_tz_aware_arithmetic(self):
2606        import random
2607
2608        now = self.theclass.now()
2609        tz55 = FixedOffset(-330, "west 5:30")
2610        timeaware = now.time().replace(tzinfo=tz55)
2611        nowaware = self.theclass.combine(now.date(), timeaware)
2612        self.assertIs(nowaware.tzinfo, tz55)
2613        self.assertEqual(nowaware.timetz(), timeaware)
2614
2615        # Can't mix aware and non-aware.
2616        self.assertRaises(TypeError, lambda: now - nowaware)
2617        self.assertRaises(TypeError, lambda: nowaware - now)
2618
2619        # And adding datetime's doesn't make sense, aware or not.
2620        self.assertRaises(TypeError, lambda: now + nowaware)
2621        self.assertRaises(TypeError, lambda: nowaware + now)
2622        self.assertRaises(TypeError, lambda: nowaware + nowaware)
2623
2624        # Subtracting should yield 0.
2625        self.assertEqual(now - now, timedelta(0))
2626        self.assertEqual(nowaware - nowaware, timedelta(0))
2627
2628        # Adding a delta should preserve tzinfo.
2629        delta = timedelta(weeks=1, minutes=12, microseconds=5678)
2630        nowawareplus = nowaware + delta
2631        self.assertIs(nowaware.tzinfo, tz55)
2632        nowawareplus2 = delta + nowaware
2633        self.assertIs(nowawareplus2.tzinfo, tz55)
2634        self.assertEqual(nowawareplus, nowawareplus2)
2635
2636        # that - delta should be what we started with, and that - what we
2637        # started with should be delta.
2638        diff = nowawareplus - delta
2639        self.assertIs(diff.tzinfo, tz55)
2640        self.assertEqual(nowaware, diff)
2641        self.assertRaises(TypeError, lambda: delta - nowawareplus)
2642        self.assertEqual(nowawareplus - nowaware, delta)
2643
2644        # Make up a random timezone.
2645        tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
2646        # Attach it to nowawareplus.
2647        nowawareplus = nowawareplus.replace(tzinfo=tzr)
2648        self.assertIs(nowawareplus.tzinfo, tzr)
2649        # Make sure the difference takes the timezone adjustments into account.
2650        got = nowaware - nowawareplus
2651        # Expected:  (nowaware base - nowaware offset) -
2652        #            (nowawareplus base - nowawareplus offset) =
2653        #            (nowaware base - nowawareplus base) +
2654        #            (nowawareplus offset - nowaware offset) =
2655        #            -delta + nowawareplus offset - nowaware offset
2656        expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
2657        self.assertEqual(got, expected)
2658
2659        # Try max possible difference.
2660        min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
2661        max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
2662                            tzinfo=FixedOffset(-1439, "max"))
2663        maxdiff = max - min
2664        self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
2665                                  timedelta(minutes=2*1439))
2666
2667    def test_tzinfo_now(self):
2668        meth = self.theclass.now
2669        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2670        base = meth()
2671        # Try with and without naming the keyword.
2672        off42 = FixedOffset(42, "42")
2673        another = meth(off42)
2674        again = meth(tz=off42)
2675        self.assertIs(another.tzinfo, again.tzinfo)
2676        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2677        # Bad argument with and w/o naming the keyword.
2678        self.assertRaises(TypeError, meth, 16)
2679        self.assertRaises(TypeError, meth, tzinfo=16)
2680        # Bad keyword name.
2681        self.assertRaises(TypeError, meth, tinfo=off42)
2682        # Too many args.
2683        self.assertRaises(TypeError, meth, off42, off42)
2684
2685        # We don't know which time zone we're in, and don't have a tzinfo
2686        # class to represent it, so seeing whether a tz argument actually
2687        # does a conversion is tricky.
2688        weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0)
2689        utc = FixedOffset(0, "utc", 0)
2690        for dummy in range(3):
2691            now = datetime.now(weirdtz)
2692            self.assertIs(now.tzinfo, weirdtz)
2693            utcnow = datetime.utcnow().replace(tzinfo=utc)
2694            now2 = utcnow.astimezone(weirdtz)
2695            if abs(now - now2) < timedelta(seconds=30):
2696                break
2697            # Else the code is broken, or more than 30 seconds passed between
2698            # calls; assuming the latter, just try again.
2699        else:
2700            # Three strikes and we're out.
2701            self.fail("utcnow(), now(tz), or astimezone() may be broken")
2702
2703    def test_tzinfo_fromtimestamp(self):
2704        import time
2705        meth = self.theclass.fromtimestamp
2706        ts = time.time()
2707        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2708        base = meth(ts)
2709        # Try with and without naming the keyword.
2710        off42 = FixedOffset(42, "42")
2711        another = meth(ts, off42)
2712        again = meth(ts, tz=off42)
2713        self.assertIs(another.tzinfo, again.tzinfo)
2714        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
2715        # Bad argument with and w/o naming the keyword.
2716        self.assertRaises(TypeError, meth, ts, 16)
2717        self.assertRaises(TypeError, meth, ts, tzinfo=16)
2718        # Bad keyword name.
2719        self.assertRaises(TypeError, meth, ts, tinfo=off42)
2720        # Too many args.
2721        self.assertRaises(TypeError, meth, ts, off42, off42)
2722        # Too few args.
2723        self.assertRaises(TypeError, meth)
2724
2725        # Try to make sure tz= actually does some conversion.
2726        timestamp = 1000000000
2727        utcdatetime = datetime.utcfromtimestamp(timestamp)
2728        # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
2729        # But on some flavor of Mac, it's nowhere near that.  So we can't have
2730        # any idea here what time that actually is, we can only test that
2731        # relative changes match.
2732        utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
2733        tz = FixedOffset(utcoffset, "tz", 0)
2734        expected = utcdatetime + utcoffset
2735        got = datetime.fromtimestamp(timestamp, tz)
2736        self.assertEqual(expected, got.replace(tzinfo=None))
2737
2738    def test_tzinfo_utcnow(self):
2739        meth = self.theclass.utcnow
2740        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2741        base = meth()
2742        # Try with and without naming the keyword; for whatever reason,
2743        # utcnow() doesn't accept a tzinfo argument.
2744        off42 = FixedOffset(42, "42")
2745        self.assertRaises(TypeError, meth, off42)
2746        self.assertRaises(TypeError, meth, tzinfo=off42)
2747
2748    def test_tzinfo_utcfromtimestamp(self):
2749        import time
2750        meth = self.theclass.utcfromtimestamp
2751        ts = time.time()
2752        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
2753        base = meth(ts)
2754        # Try with and without naming the keyword; for whatever reason,
2755        # utcfromtimestamp() doesn't accept a tzinfo argument.
2756        off42 = FixedOffset(42, "42")
2757        self.assertRaises(TypeError, meth, ts, off42)
2758        self.assertRaises(TypeError, meth, ts, tzinfo=off42)
2759
2760    def test_tzinfo_timetuple(self):
2761        # TestDateTime tested most of this.  datetime adds a twist to the
2762        # DST flag.
2763        class DST(tzinfo):
2764            def __init__(self, dstvalue):
2765                if isinstance(dstvalue, int):
2766                    dstvalue = timedelta(minutes=dstvalue)
2767                self.dstvalue = dstvalue
2768            def dst(self, dt):
2769                return self.dstvalue
2770
2771        cls = self.theclass
2772        for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
2773            d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
2774            t = d.timetuple()
2775            self.assertEqual(1, t.tm_year)
2776            self.assertEqual(1, t.tm_mon)
2777            self.assertEqual(1, t.tm_mday)
2778            self.assertEqual(10, t.tm_hour)
2779            self.assertEqual(20, t.tm_min)
2780            self.assertEqual(30, t.tm_sec)
2781            self.assertEqual(0, t.tm_wday)
2782            self.assertEqual(1, t.tm_yday)
2783            self.assertEqual(flag, t.tm_isdst)
2784
2785        # dst() returns wrong type.
2786        self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
2787
2788        # dst() at the edge.
2789        self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
2790        self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
2791
2792        # dst() out of range.
2793        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
2794        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
2795
2796    def test_utctimetuple(self):
2797        class DST(tzinfo):
2798            def __init__(self, dstvalue):
2799                if isinstance(dstvalue, int):
2800                    dstvalue = timedelta(minutes=dstvalue)
2801                self.dstvalue = dstvalue
2802            def dst(self, dt):
2803                return self.dstvalue
2804
2805        cls = self.theclass
2806        # This can't work:  DST didn't implement utcoffset.
2807        self.assertRaises(NotImplementedError,
2808                          cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
2809
2810        class UOFS(DST):
2811            def __init__(self, uofs, dofs=None):
2812                DST.__init__(self, dofs)
2813                self.uofs = timedelta(minutes=uofs)
2814            def utcoffset(self, dt):
2815                return self.uofs
2816
2817        # Ensure tm_isdst is 0 regardless of what dst() says:  DST is never
2818        # in effect for a UTC time.
2819        for dstvalue in -33, 33, 0, None:
2820            d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
2821            t = d.utctimetuple()
2822            self.assertEqual(d.year, t.tm_year)
2823            self.assertEqual(d.month, t.tm_mon)
2824            self.assertEqual(d.day, t.tm_mday)
2825            self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
2826            self.assertEqual(13, t.tm_min)
2827            self.assertEqual(d.second, t.tm_sec)
2828            self.assertEqual(d.weekday(), t.tm_wday)
2829            self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
2830                             t.tm_yday)
2831            self.assertEqual(0, t.tm_isdst)
2832
2833        # At the edges, UTC adjustment can normalize into years out-of-range
2834        # for a datetime object.  Ensure that a correct timetuple is
2835        # created anyway.
2836        tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
2837        # That goes back 1 minute less than a full day.
2838        t = tiny.utctimetuple()
2839        self.assertEqual(t.tm_year, MINYEAR-1)
2840        self.assertEqual(t.tm_mon, 12)
2841        self.assertEqual(t.tm_mday, 31)
2842        self.assertEqual(t.tm_hour, 0)
2843        self.assertEqual(t.tm_min, 1)
2844        self.assertEqual(t.tm_sec, 37)
2845        self.assertEqual(t.tm_yday, 366)    # "year 0" is a leap year
2846        self.assertEqual(t.tm_isdst, 0)
2847
2848        huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
2849        # That goes forward 1 minute less than a full day.
2850        t = huge.utctimetuple()
2851        self.assertEqual(t.tm_year, MAXYEAR+1)
2852        self.assertEqual(t.tm_mon, 1)
2853        self.assertEqual(t.tm_mday, 1)
2854        self.assertEqual(t.tm_hour, 23)
2855        self.assertEqual(t.tm_min, 58)
2856        self.assertEqual(t.tm_sec, 37)
2857        self.assertEqual(t.tm_yday, 1)
2858        self.assertEqual(t.tm_isdst, 0)
2859
2860    def test_tzinfo_isoformat(self):
2861        zero = FixedOffset(0, "+00:00")
2862        plus = FixedOffset(220, "+03:40")
2863        minus = FixedOffset(-231, "-03:51")
2864        unknown = FixedOffset(None, "")
2865
2866        cls = self.theclass
2867        datestr = '0001-02-03'
2868        for ofs in None, zero, plus, minus, unknown:
2869            for us in 0, 987001:
2870                d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
2871                timestr = '04:05:59' + (us and '.987001' or '')
2872                ofsstr = ofs is not None and d.tzname() or ''
2873                tailstr = timestr + ofsstr
2874                iso = d.isoformat()
2875                self.assertEqual(iso, datestr + 'T' + tailstr)
2876                self.assertEqual(iso, d.isoformat('T'))
2877                self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
2878                self.assertEqual(str(d), datestr + ' ' + tailstr)
2879
2880    def test_replace(self):
2881        cls = self.theclass
2882        z100 = FixedOffset(100, "+100")
2883        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2884        args = [1, 2, 3, 4, 5, 6, 7, z100]
2885        base = cls(*args)
2886        self.assertEqual(base, base.replace())
2887
2888        i = 0
2889        for name, newval in (("year", 2),
2890                             ("month", 3),
2891                             ("day", 4),
2892                             ("hour", 5),
2893                             ("minute", 6),
2894                             ("second", 7),
2895                             ("microsecond", 8),
2896                             ("tzinfo", zm200)):
2897            newargs = args[:]
2898            newargs[i] = newval
2899            expected = cls(*newargs)
2900            got = base.replace(**{name: newval})
2901            self.assertEqual(expected, got)
2902            i += 1
2903
2904        # Ensure we can get rid of a tzinfo.
2905        self.assertEqual(base.tzname(), "+100")
2906        base2 = base.replace(tzinfo=None)
2907        self.assertIsNone(base2.tzinfo)
2908        self.assertIsNone(base2.tzname())
2909
2910        # Ensure we can add one.
2911        base3 = base2.replace(tzinfo=z100)
2912        self.assertEqual(base, base3)
2913        self.assertIs(base.tzinfo, base3.tzinfo)
2914
2915        # Out of bounds.
2916        base = cls(2000, 2, 29)
2917        self.assertRaises(ValueError, base.replace, year=2001)
2918
2919    def test_more_astimezone(self):
2920        # The inherited test_astimezone covered some trivial and error cases.
2921        fnone = FixedOffset(None, "None")
2922        f44m = FixedOffset(44, "44")
2923        fm5h = FixedOffset(-timedelta(hours=5), "m300")
2924
2925        dt = self.theclass.now(tz=f44m)
2926        self.assertIs(dt.tzinfo, f44m)
2927        # Replacing with degenerate tzinfo raises an exception.
2928        self.assertRaises(ValueError, dt.astimezone, fnone)
2929        # Ditto with None tz.
2930        self.assertRaises(TypeError, dt.astimezone, None)
2931        # Replacing with same tzinfo makes no change.
2932        x = dt.astimezone(dt.tzinfo)
2933        self.assertIs(x.tzinfo, f44m)
2934        self.assertEqual(x.date(), dt.date())
2935        self.assertEqual(x.time(), dt.time())
2936
2937        # Replacing with different tzinfo does adjust.
2938        got = dt.astimezone(fm5h)
2939        self.assertIs(got.tzinfo, fm5h)
2940        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
2941        expected = dt - dt.utcoffset()  # in effect, convert to UTC
2942        expected += fm5h.utcoffset(dt)  # and from there to local time
2943        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
2944        self.assertEqual(got.date(), expected.date())
2945        self.assertEqual(got.time(), expected.time())
2946        self.assertEqual(got.timetz(), expected.timetz())
2947        self.assertIs(got.tzinfo, expected.tzinfo)
2948        self.assertEqual(got, expected)
2949
2950    def test_aware_subtract(self):
2951        cls = self.theclass
2952
2953        # Ensure that utcoffset() is ignored when the operands have the
2954        # same tzinfo member.
2955        class OperandDependentOffset(tzinfo):
2956            def utcoffset(self, t):
2957                if t.minute < 10:
2958                    # d0 and d1 equal after adjustment
2959                    return timedelta(minutes=t.minute)
2960                else:
2961                    # d2 off in the weeds
2962                    return timedelta(minutes=59)
2963
2964        base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
2965        d0 = base.replace(minute=3)
2966        d1 = base.replace(minute=9)
2967        d2 = base.replace(minute=11)
2968        for x in d0, d1, d2:
2969            for y in d0, d1, d2:
2970                got = x - y
2971                expected = timedelta(minutes=x.minute - y.minute)
2972                self.assertEqual(got, expected)
2973
2974        # OTOH, if the tzinfo members are distinct, utcoffsets aren't
2975        # ignored.
2976        base = cls(8, 9, 10, 11, 12, 13, 14)
2977        d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2978        d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2979        d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2980        for x in d0, d1, d2:
2981            for y in d0, d1, d2:
2982                got = x - y
2983                if (x is d0 or x is d1) and (y is d0 or y is d1):
2984                    expected = timedelta(0)
2985                elif x is y is d2:
2986                    expected = timedelta(0)
2987                elif x is d2:
2988                    expected = timedelta(minutes=(11-59)-0)
2989                else:
2990                    assert y is d2
2991                    expected = timedelta(minutes=0-(11-59))
2992                self.assertEqual(got, expected)
2993
2994    def test_mixed_compare(self):
2995        t1 = datetime(1, 2, 3, 4, 5, 6, 7)
2996        t2 = datetime(1, 2, 3, 4, 5, 6, 7)
2997        self.assertEqual(t1, t2)
2998        t2 = t2.replace(tzinfo=None)
2999        self.assertEqual(t1, t2)
3000        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3001        self.assertEqual(t1, t2)
3002        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3003        self.assertRaises(TypeError, lambda: t1 == t2)
3004
3005        # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3006        class Varies(tzinfo):
3007            def __init__(self):
3008                self.offset = timedelta(minutes=22)
3009            def utcoffset(self, t):
3010                self.offset += timedelta(minutes=1)
3011                return self.offset
3012
3013        v = Varies()
3014        t1 = t2.replace(tzinfo=v)
3015        t2 = t2.replace(tzinfo=v)
3016        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3017        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3018        self.assertEqual(t1, t2)
3019
3020        # But if they're not identical, it isn't ignored.
3021        t2 = t2.replace(tzinfo=Varies())
3022        self.assertTrue(t1 < t2)  # t1's offset counter still going up
3023
3024    def test_subclass_datetimetz(self):
3025
3026        class C(self.theclass):
3027            theAnswer = 42
3028
3029            def __new__(cls, *args, **kws):
3030                temp = kws.copy()
3031                extra = temp.pop('extra')
3032                result = self.theclass.__new__(cls, *args, **temp)
3033                result.extra = extra
3034                return result
3035
3036            def newmeth(self, start):
3037                return start + self.hour + self.year
3038
3039        args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3040
3041        dt1 = self.theclass(*args)
3042        dt2 = C(*args, **{'extra': 7})
3043
3044        self.assertEqual(dt2.__class__, C)
3045        self.assertEqual(dt2.theAnswer, 42)
3046        self.assertEqual(dt2.extra, 7)
3047        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3048        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3049
3050# Pain to set up DST-aware tzinfo classes.
3051
3052def first_sunday_on_or_after(dt):
3053    days_to_go = 6 - dt.weekday()
3054    if days_to_go:
3055        dt += timedelta(days_to_go)
3056    return dt
3057
3058ZERO = timedelta(0)
3059HOUR = timedelta(hours=1)
3060DAY = timedelta(days=1)
3061# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3062DSTSTART = datetime(1, 4, 1, 2)
3063# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3064# which is the first Sunday on or after Oct 25.  Because we view 1:MM as
3065# being standard time on that day, there is no spelling in local time of
3066# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3067DSTEND = datetime(1, 10, 25, 1)
3068
3069class USTimeZone(tzinfo):
3070
3071    def __init__(self, hours, reprname, stdname, dstname):
3072        self.stdoffset = timedelta(hours=hours)
3073        self.reprname = reprname
3074        self.stdname = stdname
3075        self.dstname = dstname
3076
3077    def __repr__(self):
3078        return self.reprname
3079
3080    def tzname(self, dt):
3081        if self.dst(dt):
3082            return self.dstname
3083        else:
3084            return self.stdname
3085
3086    def utcoffset(self, dt):
3087        return self.stdoffset + self.dst(dt)
3088
3089    def dst(self, dt):
3090        if dt is None or dt.tzinfo is None:
3091            # An exception instead may be sensible here, in one or more of
3092            # the cases.
3093            return ZERO
3094        assert dt.tzinfo is self
3095
3096        # Find first Sunday in April.
3097        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3098        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3099
3100        # Find last Sunday in October.
3101        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3102        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3103
3104        # Can't compare naive to aware objects, so strip the timezone from
3105        # dt first.
3106        if start <= dt.replace(tzinfo=None) < end:
3107            return HOUR
3108        else:
3109            return ZERO
3110
3111Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
3112Central  = USTimeZone(-6, "Central",  "CST", "CDT")
3113Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3114Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
3115utc_real = FixedOffset(0, "UTC", 0)
3116# For better test coverage, we want another flavor of UTC that's west of
3117# the Eastern and Pacific timezones.
3118utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3119
3120class TestTimezoneConversions(unittest.TestCase):
3121    # The DST switch times for 2002, in std time.
3122    dston = datetime(2002, 4, 7, 2)
3123    dstoff = datetime(2002, 10, 27, 1)
3124
3125    theclass = datetime
3126
3127    # Check a time that's inside DST.
3128    def checkinside(self, dt, tz, utc, dston, dstoff):
3129        self.assertEqual(dt.dst(), HOUR)
3130
3131        # Conversion to our own timezone is always an identity.
3132        self.assertEqual(dt.astimezone(tz), dt)
3133
3134        asutc = dt.astimezone(utc)
3135        there_and_back = asutc.astimezone(tz)
3136
3137        # Conversion to UTC and back isn't always an identity here,
3138        # because there are redundant spellings (in local time) of
3139        # UTC time when DST begins:  the clock jumps from 1:59:59
3140        # to 3:00:00, and a local time of 2:MM:SS doesn't really
3141        # make sense then.  The classes above treat 2:MM:SS as
3142        # daylight time then (it's "after 2am"), really an alias
3143        # for 1:MM:SS standard time.  The latter form is what
3144        # conversion back from UTC produces.
3145        if dt.date() == dston.date() and dt.hour == 2:
3146            # We're in the redundant hour, and coming back from
3147            # UTC gives the 1:MM:SS standard-time spelling.
3148            self.assertEqual(there_and_back + HOUR, dt)
3149            # Although during was considered to be in daylight
3150            # time, there_and_back is not.
3151            self.assertEqual(there_and_back.dst(), ZERO)
3152            # They're the same times in UTC.
3153            self.assertEqual(there_and_back.astimezone(utc),
3154                             dt.astimezone(utc))
3155        else:
3156            # We're not in the redundant hour.
3157            self.assertEqual(dt, there_and_back)
3158
3159        # Because we have a redundant spelling when DST begins, there is
3160        # (unfortunately) an hour when DST ends that can't be spelled at all in
3161        # local time.  When DST ends, the clock jumps from 1:59 back to 1:00
3162        # again.  The hour 1:MM DST has no spelling then:  1:MM is taken to be
3163        # standard time.  1:MM DST == 0:MM EST, but 0:MM is taken to be
3164        # daylight time.  The hour 1:MM daylight == 0:MM standard can't be
3165        # expressed in local time.  Nevertheless, we want conversion back
3166        # from UTC to mimic the local clock's "repeat an hour" behavior.
3167        nexthour_utc = asutc + HOUR
3168        nexthour_tz = nexthour_utc.astimezone(tz)
3169        if dt.date() == dstoff.date() and dt.hour == 0:
3170            # We're in the hour before the last DST hour.  The last DST hour
3171            # is ineffable.  We want the conversion back to repeat 1:MM.
3172            self.assertEqual(nexthour_tz, dt.replace(hour=1))
3173            nexthour_utc += HOUR
3174            nexthour_tz = nexthour_utc.astimezone(tz)
3175            self.assertEqual(nexthour_tz, dt.replace(hour=1))
3176        else:
3177            self.assertEqual(nexthour_tz - dt, HOUR)
3178
3179    # Check a time that's outside DST.
3180    def checkoutside(self, dt, tz, utc):
3181        self.assertEqual(dt.dst(), ZERO)
3182
3183        # Conversion to our own timezone is always an identity.
3184        self.assertEqual(dt.astimezone(tz), dt)
3185
3186        # Converting to UTC and back is an identity too.
3187        asutc = dt.astimezone(utc)
3188        there_and_back = asutc.astimezone(tz)
3189        self.assertEqual(dt, there_and_back)
3190
3191    def convert_between_tz_and_utc(self, tz, utc):
3192        dston = self.dston.replace(tzinfo=tz)
3193        # Because 1:MM on the day DST ends is taken as being standard time,
3194        # there is no spelling in tz for the last hour of daylight time.
3195        # For purposes of the test, the last hour of DST is 0:MM, which is
3196        # taken as being daylight time (and 1:MM is taken as being standard
3197        # time).
3198        dstoff = self.dstoff.replace(tzinfo=tz)
3199        for delta in (timedelta(weeks=13),
3200                      DAY,
3201                      HOUR,
3202                      timedelta(minutes=1),
3203                      timedelta(microseconds=1)):
3204
3205            self.checkinside(dston, tz, utc, dston, dstoff)
3206            for during in dston + delta, dstoff - delta:
3207                self.checkinside(during, tz, utc, dston, dstoff)
3208
3209            self.checkoutside(dstoff, tz, utc)
3210            for outside in dston - delta, dstoff + delta:
3211                self.checkoutside(outside, tz, utc)
3212
3213    def test_easy(self):
3214        # Despite the name of this test, the endcases are excruciating.
3215        self.convert_between_tz_and_utc(Eastern, utc_real)
3216        self.convert_between_tz_and_utc(Pacific, utc_real)
3217        self.convert_between_tz_and_utc(Eastern, utc_fake)
3218        self.convert_between_tz_and_utc(Pacific, utc_fake)
3219        # The next is really dancing near the edge.  It works because
3220        # Pacific and Eastern are far enough apart that their "problem
3221        # hours" don't overlap.
3222        self.convert_between_tz_and_utc(Eastern, Pacific)
3223        self.convert_between_tz_and_utc(Pacific, Eastern)
3224        # OTOH, these fail!  Don't enable them.  The difficulty is that
3225        # the edge case tests assume that every hour is representable in
3226        # the "utc" class.  This is always true for a fixed-offset tzinfo
3227        # class (lke utc_real and utc_fake), but not for Eastern or Central.
3228        # For these adjacent DST-aware time zones, the range of time offsets
3229        # tested ends up creating hours in the one that aren't representable
3230        # in the other.  For the same reason, we would see failures in the
3231        # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3232        # offset deltas in convert_between_tz_and_utc().
3233        #
3234        # self.convert_between_tz_and_utc(Eastern, Central)  # can't work
3235        # self.convert_between_tz_and_utc(Central, Eastern)  # can't work
3236
3237    def test_tricky(self):
3238        # 22:00 on day before daylight starts.
3239        fourback = self.dston - timedelta(hours=4)
3240        ninewest = FixedOffset(-9*60, "-0900", 0)
3241        fourback = fourback.replace(tzinfo=ninewest)
3242        # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST.  Since it's "after
3243        # 2", we should get the 3 spelling.
3244        # If we plug 22:00 the day before into Eastern, it "looks like std
3245        # time", so its offset is returned as -5, and -5 - -9 = 4.  Adding 4
3246        # to 22:00 lands on 2:00, which makes no sense in local time (the
3247        # local clock jumps from 1 to 3).  The point here is to make sure we
3248        # get the 3 spelling.
3249        expected = self.dston.replace(hour=3)
3250        got = fourback.astimezone(Eastern).replace(tzinfo=None)
3251        self.assertEqual(expected, got)
3252
3253        # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST.  In that
3254        # case we want the 1:00 spelling.
3255        sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3256        # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3257        # and adding -4-0 == -4 gives the 2:00 spelling.  We want the 1:00 EST
3258        # spelling.
3259        expected = self.dston.replace(hour=1)
3260        got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3261        self.assertEqual(expected, got)
3262
3263        # Now on the day DST ends, we want "repeat an hour" behavior.
3264        #  UTC  4:MM  5:MM  6:MM  7:MM  checking these
3265        #  EST 23:MM  0:MM  1:MM  2:MM
3266        #  EDT  0:MM  1:MM  2:MM  3:MM
3267        # wall  0:MM  1:MM  1:MM  2:MM  against these
3268        for utc in utc_real, utc_fake:
3269            for tz in Eastern, Pacific:
3270                first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3271                # Convert that to UTC.
3272                first_std_hour -= tz.utcoffset(None)
3273                # Adjust for possibly fake UTC.
3274                asutc = first_std_hour + utc.utcoffset(None)
3275                # First UTC hour to convert; this is 4:00 when utc=utc_real &
3276                # tz=Eastern.
3277                asutcbase = asutc.replace(tzinfo=utc)
3278                for tzhour in (0, 1, 1, 2):
3279                    expectedbase = self.dstoff.replace(hour=tzhour)
3280                    for minute in 0, 30, 59:
3281                        expected = expectedbase.replace(minute=minute)
3282                        asutc = asutcbase.replace(minute=minute)
3283                        astz = asutc.astimezone(tz)
3284                        self.assertEqual(astz.replace(tzinfo=None), expected)
3285                    asutcbase += HOUR
3286
3287
3288    def test_bogus_dst(self):
3289        class ok(tzinfo):
3290            def utcoffset(self, dt): return HOUR
3291            def dst(self, dt): return HOUR
3292
3293        now = self.theclass.now().replace(tzinfo=utc_real)
3294        # Doesn't blow up.
3295        now.astimezone(ok())
3296
3297        # Does blow up.
3298        class notok(ok):
3299            def dst(self, dt): return None
3300        self.assertRaises(ValueError, now.astimezone, notok())
3301
3302    def test_fromutc(self):
3303        self.assertRaises(TypeError, Eastern.fromutc)   # not enough args
3304        now = datetime.utcnow().replace(tzinfo=utc_real)
3305        self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3306        now = now.replace(tzinfo=Eastern)   # insert correct tzinfo
3307        enow = Eastern.fromutc(now)         # doesn't blow up
3308        self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3309        self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3310        self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3311
3312        # Always converts UTC to standard time.
3313        class FauxUSTimeZone(USTimeZone):
3314            def fromutc(self, dt):
3315                return dt + self.stdoffset
3316        FEastern  = FauxUSTimeZone(-5, "FEastern",  "FEST", "FEDT")
3317
3318        #  UTC  4:MM  5:MM  6:MM  7:MM  8:MM  9:MM
3319        #  EST 23:MM  0:MM  1:MM  2:MM  3:MM  4:MM
3320        #  EDT  0:MM  1:MM  2:MM  3:MM  4:MM  5:MM
3321
3322        # Check around DST start.
3323        start = self.dston.replace(hour=4, tzinfo=Eastern)
3324        fstart = start.replace(tzinfo=FEastern)
3325        for wall in 23, 0, 1, 3, 4, 5:
3326            expected = start.replace(hour=wall)
3327            if wall == 23:
3328                expected -= timedelta(days=1)
3329            got = Eastern.fromutc(start)
3330            self.assertEqual(expected, got)
3331
3332            expected = fstart + FEastern.stdoffset
3333            got = FEastern.fromutc(fstart)
3334            self.assertEqual(expected, got)
3335
3336            # Ensure astimezone() calls fromutc() too.
3337            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3338            self.assertEqual(expected, got)
3339
3340            start += HOUR
3341            fstart += HOUR
3342
3343        # Check around DST end.
3344        start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3345        fstart = start.replace(tzinfo=FEastern)
3346        for wall in 0, 1, 1, 2, 3, 4:
3347            expected = start.replace(hour=wall)
3348            got = Eastern.fromutc(start)
3349            self.assertEqual(expected, got)
3350
3351            expected = fstart + FEastern.stdoffset
3352            got = FEastern.fromutc(fstart)
3353            self.assertEqual(expected, got)
3354
3355            # Ensure astimezone() calls fromutc() too.
3356            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3357            self.assertEqual(expected, got)
3358
3359            start += HOUR
3360            fstart += HOUR
3361
3362
3363#############################################################################
3364# oddballs
3365
3366class Oddballs(unittest.TestCase):
3367
3368    def test_bug_1028306(self):
3369        # Trying to compare a date to a datetime should act like a mixed-
3370        # type comparison, despite that datetime is a subclass of date.
3371        as_date = date.today()
3372        as_datetime = datetime.combine(as_date, time())
3373        self.assertTrue(as_date != as_datetime)
3374        self.assertTrue(as_datetime != as_date)
3375        self.assertFalse(as_date == as_datetime)
3376        self.assertFalse(as_datetime == as_date)
3377        self.assertRaises(TypeError, lambda: as_date < as_datetime)
3378        self.assertRaises(TypeError, lambda: as_datetime < as_date)
3379        self.assertRaises(TypeError, lambda: as_date <= as_datetime)
3380        self.assertRaises(TypeError, lambda: as_datetime <= as_date)
3381        self.assertRaises(TypeError, lambda: as_date > as_datetime)
3382        self.assertRaises(TypeError, lambda: as_datetime > as_date)
3383        self.assertRaises(TypeError, lambda: as_date >= as_datetime)
3384        self.assertRaises(TypeError, lambda: as_datetime >= as_date)
3385
3386        # Nevertheless, comparison should work with the base-class (date)
3387        # projection if use of a date method is forced.
3388        self.assertTrue(as_date.__eq__(as_datetime))
3389        different_day = (as_date.day + 1) % 20 + 1
3390        self.assertFalse(as_date.__eq__(as_datetime.replace(day=different_day)))
3391
3392        # And date should compare with other subclasses of date.  If a
3393        # subclass wants to stop this, it's up to the subclass to do so.
3394        date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
3395        self.assertEqual(as_date, date_sc)
3396        self.assertEqual(date_sc, as_date)
3397
3398        # Ditto for datetimes.
3399        datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
3400                                       as_date.day, 0, 0, 0)
3401        self.assertEqual(as_datetime, datetime_sc)
3402        self.assertEqual(datetime_sc, as_datetime)
3403
3404def test_main():
3405    test_support.run_unittest(__name__)
3406
3407if __name__ == "__main__":
3408    test_main()
3409