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