• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
5from test.support import is_resource_enabled
6
7import itertools
8import bisect
9
10import copy
11import decimal
12import sys
13import os
14import pickle
15import random
16import struct
17import unittest
18
19from array import array
20
21from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
22
23from test import support
24
25import datetime as datetime_module
26from datetime import MINYEAR, MAXYEAR
27from datetime import timedelta
28from datetime import tzinfo
29from datetime import time
30from datetime import timezone
31from datetime import date, datetime
32import time as _time
33
34# Needed by test_datetime
35import _strptime
36#
37
38
39pickle_choices = [(pickle, pickle, proto)
40                  for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
41assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
42
43# An arbitrary collection of objects of non-datetime types, for testing
44# mixed-type comparisons.
45OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
46
47
48# XXX Copied from test_float.
49INF = float("inf")
50NAN = float("nan")
51
52
53#############################################################################
54# module tests
55
56class TestModule(unittest.TestCase):
57
58    def test_constants(self):
59        datetime = datetime_module
60        self.assertEqual(datetime.MINYEAR, 1)
61        self.assertEqual(datetime.MAXYEAR, 9999)
62
63    def test_name_cleanup(self):
64        if '_Fast' not in str(self):
65            return
66        datetime = datetime_module
67        names = set(name for name in dir(datetime)
68                    if not name.startswith('__') and not name.endswith('__'))
69        allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
70                       'datetime_CAPI', 'time', 'timedelta', 'timezone',
71                       'tzinfo'])
72        self.assertEqual(names - allowed, set([]))
73
74    def test_divide_and_round(self):
75        if '_Fast' in str(self):
76            return
77        dar = datetime_module._divide_and_round
78
79        self.assertEqual(dar(-10, -3), 3)
80        self.assertEqual(dar(5, -2), -2)
81
82        # four cases: (2 signs of a) x (2 signs of b)
83        self.assertEqual(dar(7, 3), 2)
84        self.assertEqual(dar(-7, 3), -2)
85        self.assertEqual(dar(7, -3), -2)
86        self.assertEqual(dar(-7, -3), 2)
87
88        # ties to even - eight cases:
89        # (2 signs of a) x (2 signs of b) x (even / odd quotient)
90        self.assertEqual(dar(10, 4), 2)
91        self.assertEqual(dar(-10, 4), -2)
92        self.assertEqual(dar(10, -4), -2)
93        self.assertEqual(dar(-10, -4), 2)
94
95        self.assertEqual(dar(6, 4), 2)
96        self.assertEqual(dar(-6, 4), -2)
97        self.assertEqual(dar(6, -4), -2)
98        self.assertEqual(dar(-6, -4), 2)
99
100
101#############################################################################
102# tzinfo tests
103
104class FixedOffset(tzinfo):
105
106    def __init__(self, offset, name, dstoffset=42):
107        if isinstance(offset, int):
108            offset = timedelta(minutes=offset)
109        if isinstance(dstoffset, int):
110            dstoffset = timedelta(minutes=dstoffset)
111        self.__offset = offset
112        self.__name = name
113        self.__dstoffset = dstoffset
114    def __repr__(self):
115        return self.__name.lower()
116    def utcoffset(self, dt):
117        return self.__offset
118    def tzname(self, dt):
119        return self.__name
120    def dst(self, dt):
121        return self.__dstoffset
122
123class PicklableFixedOffset(FixedOffset):
124
125    def __init__(self, offset=None, name=None, dstoffset=None):
126        FixedOffset.__init__(self, offset, name, dstoffset)
127
128    def __getstate__(self):
129        return self.__dict__
130
131class _TZInfo(tzinfo):
132    def utcoffset(self, datetime_module):
133        return random.random()
134
135class TestTZInfo(unittest.TestCase):
136
137    def test_refcnt_crash_bug_22044(self):
138        tz1 = _TZInfo()
139        dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
140        with self.assertRaises(TypeError):
141            dt1.utcoffset()
142
143    def test_non_abstractness(self):
144        # In order to allow subclasses to get pickled, the C implementation
145        # wasn't able to get away with having __init__ raise
146        # NotImplementedError.
147        useless = tzinfo()
148        dt = datetime.max
149        self.assertRaises(NotImplementedError, useless.tzname, dt)
150        self.assertRaises(NotImplementedError, useless.utcoffset, dt)
151        self.assertRaises(NotImplementedError, useless.dst, dt)
152
153    def test_subclass_must_override(self):
154        class NotEnough(tzinfo):
155            def __init__(self, offset, name):
156                self.__offset = offset
157                self.__name = name
158        self.assertTrue(issubclass(NotEnough, tzinfo))
159        ne = NotEnough(3, "NotByALongShot")
160        self.assertIsInstance(ne, tzinfo)
161
162        dt = datetime.now()
163        self.assertRaises(NotImplementedError, ne.tzname, dt)
164        self.assertRaises(NotImplementedError, ne.utcoffset, dt)
165        self.assertRaises(NotImplementedError, ne.dst, dt)
166
167    def test_normal(self):
168        fo = FixedOffset(3, "Three")
169        self.assertIsInstance(fo, tzinfo)
170        for dt in datetime.now(), None:
171            self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
172            self.assertEqual(fo.tzname(dt), "Three")
173            self.assertEqual(fo.dst(dt), timedelta(minutes=42))
174
175    def test_pickling_base(self):
176        # There's no point to pickling tzinfo objects on their own (they
177        # carry no data), but they need to be picklable anyway else
178        # concrete subclasses can't be pickled.
179        orig = tzinfo.__new__(tzinfo)
180        self.assertIs(type(orig), tzinfo)
181        for pickler, unpickler, proto in pickle_choices:
182            green = pickler.dumps(orig, proto)
183            derived = unpickler.loads(green)
184            self.assertIs(type(derived), tzinfo)
185
186    def test_pickling_subclass(self):
187        # Make sure we can pickle/unpickle an instance of a subclass.
188        offset = timedelta(minutes=-300)
189        for otype, args in [
190            (PicklableFixedOffset, (offset, 'cookie')),
191            (timezone, (offset,)),
192            (timezone, (offset, "EST"))]:
193            orig = otype(*args)
194            oname = orig.tzname(None)
195            self.assertIsInstance(orig, tzinfo)
196            self.assertIs(type(orig), otype)
197            self.assertEqual(orig.utcoffset(None), offset)
198            self.assertEqual(orig.tzname(None), oname)
199            for pickler, unpickler, proto in pickle_choices:
200                green = pickler.dumps(orig, proto)
201                derived = unpickler.loads(green)
202                self.assertIsInstance(derived, tzinfo)
203                self.assertIs(type(derived), otype)
204                self.assertEqual(derived.utcoffset(None), offset)
205                self.assertEqual(derived.tzname(None), oname)
206
207    def test_issue23600(self):
208        DSTDIFF = DSTOFFSET = timedelta(hours=1)
209
210        class UKSummerTime(tzinfo):
211            """Simple time zone which pretends to always be in summer time, since
212                that's what shows the failure.
213            """
214
215            def utcoffset(self, dt):
216                return DSTOFFSET
217
218            def dst(self, dt):
219                return DSTDIFF
220
221            def tzname(self, dt):
222                return 'UKSummerTime'
223
224        tz = UKSummerTime()
225        u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
226        t = tz.fromutc(u)
227        self.assertEqual(t - t.utcoffset(), u)
228
229
230class TestTimeZone(unittest.TestCase):
231
232    def setUp(self):
233        self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
234        self.EST = timezone(-timedelta(hours=5), 'EST')
235        self.DT = datetime(2010, 1, 1)
236
237    def test_str(self):
238        for tz in [self.ACDT, self.EST, timezone.utc,
239                   timezone.min, timezone.max]:
240            self.assertEqual(str(tz), tz.tzname(None))
241
242    def test_repr(self):
243        datetime = datetime_module
244        for tz in [self.ACDT, self.EST, timezone.utc,
245                   timezone.min, timezone.max]:
246            # test round-trip
247            tzrep = repr(tz)
248            self.assertEqual(tz, eval(tzrep))
249
250    def test_class_members(self):
251        limit = timedelta(hours=23, minutes=59)
252        self.assertEqual(timezone.utc.utcoffset(None), ZERO)
253        self.assertEqual(timezone.min.utcoffset(None), -limit)
254        self.assertEqual(timezone.max.utcoffset(None), limit)
255
256
257    def test_constructor(self):
258        self.assertIs(timezone.utc, timezone(timedelta(0)))
259        self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
260        self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
261        # invalid offsets
262        for invalid in [timedelta(microseconds=1), timedelta(1, 1),
263                        timedelta(seconds=1), timedelta(1), -timedelta(1)]:
264            self.assertRaises(ValueError, timezone, invalid)
265            self.assertRaises(ValueError, timezone, -invalid)
266
267        with self.assertRaises(TypeError): timezone(None)
268        with self.assertRaises(TypeError): timezone(42)
269        with self.assertRaises(TypeError): timezone(ZERO, None)
270        with self.assertRaises(TypeError): timezone(ZERO, 42)
271        with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
272
273    def test_inheritance(self):
274        self.assertIsInstance(timezone.utc, tzinfo)
275        self.assertIsInstance(self.EST, tzinfo)
276
277    def test_utcoffset(self):
278        dummy = self.DT
279        for h in [0, 1.5, 12]:
280            offset = h * HOUR
281            self.assertEqual(offset, timezone(offset).utcoffset(dummy))
282            self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
283
284        with self.assertRaises(TypeError): self.EST.utcoffset('')
285        with self.assertRaises(TypeError): self.EST.utcoffset(5)
286
287
288    def test_dst(self):
289        self.assertIsNone(timezone.utc.dst(self.DT))
290
291        with self.assertRaises(TypeError): self.EST.dst('')
292        with self.assertRaises(TypeError): self.EST.dst(5)
293
294    def test_tzname(self):
295        self.assertEqual('UTC', timezone.utc.tzname(None))
296        self.assertEqual('UTC', timezone(ZERO).tzname(None))
297        self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
298        self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
299        self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
300        self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
301
302        with self.assertRaises(TypeError): self.EST.tzname('')
303        with self.assertRaises(TypeError): self.EST.tzname(5)
304
305    def test_fromutc(self):
306        with self.assertRaises(ValueError):
307            timezone.utc.fromutc(self.DT)
308        with self.assertRaises(TypeError):
309            timezone.utc.fromutc('not datetime')
310        for tz in [self.EST, self.ACDT, Eastern]:
311            utctime = self.DT.replace(tzinfo=tz)
312            local = tz.fromutc(utctime)
313            self.assertEqual(local - utctime, tz.utcoffset(local))
314            self.assertEqual(local,
315                             self.DT.replace(tzinfo=timezone.utc))
316
317    def test_comparison(self):
318        self.assertNotEqual(timezone(ZERO), timezone(HOUR))
319        self.assertEqual(timezone(HOUR), timezone(HOUR))
320        self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
321        with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
322        self.assertIn(timezone(ZERO), {timezone(ZERO)})
323        self.assertTrue(timezone(ZERO) != None)
324        self.assertFalse(timezone(ZERO) ==  None)
325
326    def test_aware_datetime(self):
327        # test that timezone instances can be used by datetime
328        t = datetime(1, 1, 1)
329        for tz in [timezone.min, timezone.max, timezone.utc]:
330            self.assertEqual(tz.tzname(t),
331                             t.replace(tzinfo=tz).tzname())
332            self.assertEqual(tz.utcoffset(t),
333                             t.replace(tzinfo=tz).utcoffset())
334            self.assertEqual(tz.dst(t),
335                             t.replace(tzinfo=tz).dst())
336
337    def test_pickle(self):
338        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
339            for pickler, unpickler, proto in pickle_choices:
340                tz_copy = unpickler.loads(pickler.dumps(tz, proto))
341                self.assertEqual(tz_copy, tz)
342        tz = timezone.utc
343        for pickler, unpickler, proto in pickle_choices:
344            tz_copy = unpickler.loads(pickler.dumps(tz, proto))
345            self.assertIs(tz_copy, tz)
346
347    def test_copy(self):
348        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
349            tz_copy = copy.copy(tz)
350            self.assertEqual(tz_copy, tz)
351        tz = timezone.utc
352        tz_copy = copy.copy(tz)
353        self.assertIs(tz_copy, tz)
354
355    def test_deepcopy(self):
356        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
357            tz_copy = copy.deepcopy(tz)
358            self.assertEqual(tz_copy, tz)
359        tz = timezone.utc
360        tz_copy = copy.deepcopy(tz)
361        self.assertIs(tz_copy, tz)
362
363
364#############################################################################
365# Base class for testing a particular aspect of timedelta, time, date and
366# datetime comparisons.
367
368class HarmlessMixedComparison:
369    # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
370
371    # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
372    # legit constructor.
373
374    def test_harmless_mixed_comparison(self):
375        me = self.theclass(1, 1, 1)
376
377        self.assertFalse(me == ())
378        self.assertTrue(me != ())
379        self.assertFalse(() == me)
380        self.assertTrue(() != me)
381
382        self.assertIn(me, [1, 20, [], me])
383        self.assertIn([], [me, 1, 20, []])
384
385    def test_harmful_mixed_comparison(self):
386        me = self.theclass(1, 1, 1)
387
388        self.assertRaises(TypeError, lambda: me < ())
389        self.assertRaises(TypeError, lambda: me <= ())
390        self.assertRaises(TypeError, lambda: me > ())
391        self.assertRaises(TypeError, lambda: me >= ())
392
393        self.assertRaises(TypeError, lambda: () < me)
394        self.assertRaises(TypeError, lambda: () <= me)
395        self.assertRaises(TypeError, lambda: () > me)
396        self.assertRaises(TypeError, lambda: () >= me)
397
398#############################################################################
399# timedelta tests
400
401class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
402
403    theclass = timedelta
404
405    def test_constructor(self):
406        eq = self.assertEqual
407        td = timedelta
408
409        # Check keyword args to constructor
410        eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
411                    milliseconds=0, microseconds=0))
412        eq(td(1), td(days=1))
413        eq(td(0, 1), td(seconds=1))
414        eq(td(0, 0, 1), td(microseconds=1))
415        eq(td(weeks=1), td(days=7))
416        eq(td(days=1), td(hours=24))
417        eq(td(hours=1), td(minutes=60))
418        eq(td(minutes=1), td(seconds=60))
419        eq(td(seconds=1), td(milliseconds=1000))
420        eq(td(milliseconds=1), td(microseconds=1000))
421
422        # Check float args to constructor
423        eq(td(weeks=1.0/7), td(days=1))
424        eq(td(days=1.0/24), td(hours=1))
425        eq(td(hours=1.0/60), td(minutes=1))
426        eq(td(minutes=1.0/60), td(seconds=1))
427        eq(td(seconds=0.001), td(milliseconds=1))
428        eq(td(milliseconds=0.001), td(microseconds=1))
429
430    def test_computations(self):
431        eq = self.assertEqual
432        td = timedelta
433
434        a = td(7) # One week
435        b = td(0, 60) # One minute
436        c = td(0, 0, 1000) # One millisecond
437        eq(a+b+c, td(7, 60, 1000))
438        eq(a-b, td(6, 24*3600 - 60))
439        eq(b.__rsub__(a), td(6, 24*3600 - 60))
440        eq(-a, td(-7))
441        eq(+a, td(7))
442        eq(-b, td(-1, 24*3600 - 60))
443        eq(-c, td(-1, 24*3600 - 1, 999000))
444        eq(abs(a), a)
445        eq(abs(-a), a)
446        eq(td(6, 24*3600), a)
447        eq(td(0, 0, 60*1000000), b)
448        eq(a*10, td(70))
449        eq(a*10, 10*a)
450        eq(a*10, 10*a)
451        eq(b*10, td(0, 600))
452        eq(10*b, td(0, 600))
453        eq(b*10, td(0, 600))
454        eq(c*10, td(0, 0, 10000))
455        eq(10*c, td(0, 0, 10000))
456        eq(c*10, td(0, 0, 10000))
457        eq(a*-1, -a)
458        eq(b*-2, -b-b)
459        eq(c*-2, -c+-c)
460        eq(b*(60*24), (b*60)*24)
461        eq(b*(60*24), (60*b)*24)
462        eq(c*1000, td(0, 1))
463        eq(1000*c, td(0, 1))
464        eq(a//7, td(1))
465        eq(b//10, td(0, 6))
466        eq(c//1000, td(0, 0, 1))
467        eq(a//10, td(0, 7*24*360))
468        eq(a//3600000, td(0, 0, 7*24*1000))
469        eq(a/0.5, td(14))
470        eq(b/0.5, td(0, 120))
471        eq(a/7, td(1))
472        eq(b/10, td(0, 6))
473        eq(c/1000, td(0, 0, 1))
474        eq(a/10, td(0, 7*24*360))
475        eq(a/3600000, td(0, 0, 7*24*1000))
476
477        # Multiplication by float
478        us = td(microseconds=1)
479        eq((3*us) * 0.5, 2*us)
480        eq((5*us) * 0.5, 2*us)
481        eq(0.5 * (3*us), 2*us)
482        eq(0.5 * (5*us), 2*us)
483        eq((-3*us) * 0.5, -2*us)
484        eq((-5*us) * 0.5, -2*us)
485
486        # Issue #23521
487        eq(td(seconds=1) * 0.123456, td(microseconds=123456))
488        eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
489
490        # Division by int and float
491        eq((3*us) / 2, 2*us)
492        eq((5*us) / 2, 2*us)
493        eq((-3*us) / 2.0, -2*us)
494        eq((-5*us) / 2.0, -2*us)
495        eq((3*us) / -2, -2*us)
496        eq((5*us) / -2, -2*us)
497        eq((3*us) / -2.0, -2*us)
498        eq((5*us) / -2.0, -2*us)
499        for i in range(-10, 10):
500            eq((i*us/3)//us, round(i/3))
501        for i in range(-10, 10):
502            eq((i*us/-3)//us, round(i/-3))
503
504        # Issue #23521
505        eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
506
507        # Issue #11576
508        eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
509           td(0, 0, 1))
510        eq(td(999999999, 1, 1) - td(999999999, 1, 0),
511           td(0, 0, 1))
512
513    def test_disallowed_computations(self):
514        a = timedelta(42)
515
516        # Add/sub ints or floats should be illegal
517        for i in 1, 1.0:
518            self.assertRaises(TypeError, lambda: a+i)
519            self.assertRaises(TypeError, lambda: a-i)
520            self.assertRaises(TypeError, lambda: i+a)
521            self.assertRaises(TypeError, lambda: i-a)
522
523        # Division of int by timedelta doesn't make sense.
524        # Division by zero doesn't make sense.
525        zero = 0
526        self.assertRaises(TypeError, lambda: zero // a)
527        self.assertRaises(ZeroDivisionError, lambda: a // zero)
528        self.assertRaises(ZeroDivisionError, lambda: a / zero)
529        self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
530        self.assertRaises(TypeError, lambda: a / '')
531
532    @support.requires_IEEE_754
533    def test_disallowed_special(self):
534        a = timedelta(42)
535        self.assertRaises(ValueError, a.__mul__, NAN)
536        self.assertRaises(ValueError, a.__truediv__, NAN)
537
538    def test_basic_attributes(self):
539        days, seconds, us = 1, 7, 31
540        td = timedelta(days, seconds, us)
541        self.assertEqual(td.days, days)
542        self.assertEqual(td.seconds, seconds)
543        self.assertEqual(td.microseconds, us)
544
545    def test_total_seconds(self):
546        td = timedelta(days=365)
547        self.assertEqual(td.total_seconds(), 31536000.0)
548        for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
549            td = timedelta(seconds=total_seconds)
550            self.assertEqual(td.total_seconds(), total_seconds)
551        # Issue8644: Test that td.total_seconds() has the same
552        # accuracy as td / timedelta(seconds=1).
553        for ms in [-1, -2, -123]:
554            td = timedelta(microseconds=ms)
555            self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
556
557    def test_carries(self):
558        t1 = timedelta(days=100,
559                       weeks=-7,
560                       hours=-24*(100-49),
561                       minutes=-3,
562                       seconds=12,
563                       microseconds=(3*60 - 12) * 1e6 + 1)
564        t2 = timedelta(microseconds=1)
565        self.assertEqual(t1, t2)
566
567    def test_hash_equality(self):
568        t1 = timedelta(days=100,
569                       weeks=-7,
570                       hours=-24*(100-49),
571                       minutes=-3,
572                       seconds=12,
573                       microseconds=(3*60 - 12) * 1000000)
574        t2 = timedelta()
575        self.assertEqual(hash(t1), hash(t2))
576
577        t1 += timedelta(weeks=7)
578        t2 += timedelta(days=7*7)
579        self.assertEqual(t1, t2)
580        self.assertEqual(hash(t1), hash(t2))
581
582        d = {t1: 1}
583        d[t2] = 2
584        self.assertEqual(len(d), 1)
585        self.assertEqual(d[t1], 2)
586
587    def test_pickling(self):
588        args = 12, 34, 56
589        orig = timedelta(*args)
590        for pickler, unpickler, proto in pickle_choices:
591            green = pickler.dumps(orig, proto)
592            derived = unpickler.loads(green)
593            self.assertEqual(orig, derived)
594
595    def test_compare(self):
596        t1 = timedelta(2, 3, 4)
597        t2 = timedelta(2, 3, 4)
598        self.assertEqual(t1, t2)
599        self.assertTrue(t1 <= t2)
600        self.assertTrue(t1 >= t2)
601        self.assertFalse(t1 != t2)
602        self.assertFalse(t1 < t2)
603        self.assertFalse(t1 > t2)
604
605        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
606            t2 = timedelta(*args)   # this is larger than t1
607            self.assertTrue(t1 < t2)
608            self.assertTrue(t2 > t1)
609            self.assertTrue(t1 <= t2)
610            self.assertTrue(t2 >= t1)
611            self.assertTrue(t1 != t2)
612            self.assertTrue(t2 != t1)
613            self.assertFalse(t1 == t2)
614            self.assertFalse(t2 == t1)
615            self.assertFalse(t1 > t2)
616            self.assertFalse(t2 < t1)
617            self.assertFalse(t1 >= t2)
618            self.assertFalse(t2 <= t1)
619
620        for badarg in OTHERSTUFF:
621            self.assertEqual(t1 == badarg, False)
622            self.assertEqual(t1 != badarg, True)
623            self.assertEqual(badarg == t1, False)
624            self.assertEqual(badarg != t1, True)
625
626            self.assertRaises(TypeError, lambda: t1 <= badarg)
627            self.assertRaises(TypeError, lambda: t1 < badarg)
628            self.assertRaises(TypeError, lambda: t1 > badarg)
629            self.assertRaises(TypeError, lambda: t1 >= badarg)
630            self.assertRaises(TypeError, lambda: badarg <= t1)
631            self.assertRaises(TypeError, lambda: badarg < t1)
632            self.assertRaises(TypeError, lambda: badarg > t1)
633            self.assertRaises(TypeError, lambda: badarg >= t1)
634
635    def test_str(self):
636        td = timedelta
637        eq = self.assertEqual
638
639        eq(str(td(1)), "1 day, 0:00:00")
640        eq(str(td(-1)), "-1 day, 0:00:00")
641        eq(str(td(2)), "2 days, 0:00:00")
642        eq(str(td(-2)), "-2 days, 0:00:00")
643
644        eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
645        eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
646        eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
647           "-210 days, 23:12:34")
648
649        eq(str(td(milliseconds=1)), "0:00:00.001000")
650        eq(str(td(microseconds=3)), "0:00:00.000003")
651
652        eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
653                   microseconds=999999)),
654           "999999999 days, 23:59:59.999999")
655
656    def test_repr(self):
657        name = 'datetime.' + self.theclass.__name__
658        self.assertEqual(repr(self.theclass(1)),
659                         "%s(1)" % name)
660        self.assertEqual(repr(self.theclass(10, 2)),
661                         "%s(10, 2)" % name)
662        self.assertEqual(repr(self.theclass(-10, 2, 400000)),
663                         "%s(-10, 2, 400000)" % name)
664
665    def test_roundtrip(self):
666        for td in (timedelta(days=999999999, hours=23, minutes=59,
667                             seconds=59, microseconds=999999),
668                   timedelta(days=-999999999),
669                   timedelta(days=-999999999, seconds=1),
670                   timedelta(days=1, seconds=2, microseconds=3)):
671
672            # Verify td -> string -> td identity.
673            s = repr(td)
674            self.assertTrue(s.startswith('datetime.'))
675            s = s[9:]
676            td2 = eval(s)
677            self.assertEqual(td, td2)
678
679            # Verify identity via reconstructing from pieces.
680            td2 = timedelta(td.days, td.seconds, td.microseconds)
681            self.assertEqual(td, td2)
682
683    def test_resolution_info(self):
684        self.assertIsInstance(timedelta.min, timedelta)
685        self.assertIsInstance(timedelta.max, timedelta)
686        self.assertIsInstance(timedelta.resolution, timedelta)
687        self.assertTrue(timedelta.max > timedelta.min)
688        self.assertEqual(timedelta.min, timedelta(-999999999))
689        self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
690        self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
691
692    def test_overflow(self):
693        tiny = timedelta.resolution
694
695        td = timedelta.min + tiny
696        td -= tiny  # no problem
697        self.assertRaises(OverflowError, td.__sub__, tiny)
698        self.assertRaises(OverflowError, td.__add__, -tiny)
699
700        td = timedelta.max - tiny
701        td += tiny  # no problem
702        self.assertRaises(OverflowError, td.__add__, tiny)
703        self.assertRaises(OverflowError, td.__sub__, -tiny)
704
705        self.assertRaises(OverflowError, lambda: -timedelta.max)
706
707        day = timedelta(1)
708        self.assertRaises(OverflowError, day.__mul__, 10**9)
709        self.assertRaises(OverflowError, day.__mul__, 1e9)
710        self.assertRaises(OverflowError, day.__truediv__, 1e-20)
711        self.assertRaises(OverflowError, day.__truediv__, 1e-10)
712        self.assertRaises(OverflowError, day.__truediv__, 9e-10)
713
714    @support.requires_IEEE_754
715    def _test_overflow_special(self):
716        day = timedelta(1)
717        self.assertRaises(OverflowError, day.__mul__, INF)
718        self.assertRaises(OverflowError, day.__mul__, -INF)
719
720    def test_microsecond_rounding(self):
721        td = timedelta
722        eq = self.assertEqual
723
724        # Single-field rounding.
725        eq(td(milliseconds=0.4/1000), td(0))    # rounds to 0
726        eq(td(milliseconds=-0.4/1000), td(0))    # rounds to 0
727        eq(td(milliseconds=0.5/1000), td(microseconds=0))
728        eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
729        eq(td(milliseconds=0.6/1000), td(microseconds=1))
730        eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
731        eq(td(milliseconds=1.5/1000), td(microseconds=2))
732        eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
733        eq(td(seconds=0.5/10**6), td(microseconds=0))
734        eq(td(seconds=-0.5/10**6), td(microseconds=-0))
735        eq(td(seconds=1/2**7), td(microseconds=7812))
736        eq(td(seconds=-1/2**7), td(microseconds=-7812))
737
738        # Rounding due to contributions from more than one field.
739        us_per_hour = 3600e6
740        us_per_day = us_per_hour * 24
741        eq(td(days=.4/us_per_day), td(0))
742        eq(td(hours=.2/us_per_hour), td(0))
743        eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
744
745        eq(td(days=-.4/us_per_day), td(0))
746        eq(td(hours=-.2/us_per_hour), td(0))
747        eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
748
749        # Test for a patch in Issue 8860
750        eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
751        eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
752
753    def test_massive_normalization(self):
754        td = timedelta(microseconds=-1)
755        self.assertEqual((td.days, td.seconds, td.microseconds),
756                         (-1, 24*3600-1, 999999))
757
758    def test_bool(self):
759        self.assertTrue(timedelta(1))
760        self.assertTrue(timedelta(0, 1))
761        self.assertTrue(timedelta(0, 0, 1))
762        self.assertTrue(timedelta(microseconds=1))
763        self.assertFalse(timedelta(0))
764
765    def test_subclass_timedelta(self):
766
767        class T(timedelta):
768            @staticmethod
769            def from_td(td):
770                return T(td.days, td.seconds, td.microseconds)
771
772            def as_hours(self):
773                sum = (self.days * 24 +
774                       self.seconds / 3600.0 +
775                       self.microseconds / 3600e6)
776                return round(sum)
777
778        t1 = T(days=1)
779        self.assertIs(type(t1), T)
780        self.assertEqual(t1.as_hours(), 24)
781
782        t2 = T(days=-1, seconds=-3600)
783        self.assertIs(type(t2), T)
784        self.assertEqual(t2.as_hours(), -25)
785
786        t3 = t1 + t2
787        self.assertIs(type(t3), timedelta)
788        t4 = T.from_td(t3)
789        self.assertIs(type(t4), T)
790        self.assertEqual(t3.days, t4.days)
791        self.assertEqual(t3.seconds, t4.seconds)
792        self.assertEqual(t3.microseconds, t4.microseconds)
793        self.assertEqual(str(t3), str(t4))
794        self.assertEqual(t4.as_hours(), -1)
795
796    def test_division(self):
797        t = timedelta(hours=1, minutes=24, seconds=19)
798        second = timedelta(seconds=1)
799        self.assertEqual(t / second, 5059.0)
800        self.assertEqual(t // second, 5059)
801
802        t = timedelta(minutes=2, seconds=30)
803        minute = timedelta(minutes=1)
804        self.assertEqual(t / minute, 2.5)
805        self.assertEqual(t // minute, 2)
806
807        zerotd = timedelta(0)
808        self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
809        self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
810
811        # self.assertRaises(TypeError, truediv, t, 2)
812        # note: floor division of a timedelta by an integer *is*
813        # currently permitted.
814
815    def test_remainder(self):
816        t = timedelta(minutes=2, seconds=30)
817        minute = timedelta(minutes=1)
818        r = t % minute
819        self.assertEqual(r, timedelta(seconds=30))
820
821        t = timedelta(minutes=-2, seconds=30)
822        r = t %  minute
823        self.assertEqual(r, timedelta(seconds=30))
824
825        zerotd = timedelta(0)
826        self.assertRaises(ZeroDivisionError, mod, t, zerotd)
827
828        self.assertRaises(TypeError, mod, t, 10)
829
830    def test_divmod(self):
831        t = timedelta(minutes=2, seconds=30)
832        minute = timedelta(minutes=1)
833        q, r = divmod(t, minute)
834        self.assertEqual(q, 2)
835        self.assertEqual(r, timedelta(seconds=30))
836
837        t = timedelta(minutes=-2, seconds=30)
838        q, r = divmod(t, minute)
839        self.assertEqual(q, -2)
840        self.assertEqual(r, timedelta(seconds=30))
841
842        zerotd = timedelta(0)
843        self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
844
845        self.assertRaises(TypeError, divmod, t, 10)
846
847
848#############################################################################
849# date tests
850
851class TestDateOnly(unittest.TestCase):
852    # Tests here won't pass if also run on datetime objects, so don't
853    # subclass this to test datetimes too.
854
855    def test_delta_non_days_ignored(self):
856        dt = date(2000, 1, 2)
857        delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
858                          microseconds=5)
859        days = timedelta(delta.days)
860        self.assertEqual(days, timedelta(1))
861
862        dt2 = dt + delta
863        self.assertEqual(dt2, dt + days)
864
865        dt2 = delta + dt
866        self.assertEqual(dt2, dt + days)
867
868        dt2 = dt - delta
869        self.assertEqual(dt2, dt - days)
870
871        delta = -delta
872        days = timedelta(delta.days)
873        self.assertEqual(days, timedelta(-2))
874
875        dt2 = dt + delta
876        self.assertEqual(dt2, dt + days)
877
878        dt2 = delta + dt
879        self.assertEqual(dt2, dt + days)
880
881        dt2 = dt - delta
882        self.assertEqual(dt2, dt - days)
883
884class SubclassDate(date):
885    sub_var = 1
886
887class TestDate(HarmlessMixedComparison, unittest.TestCase):
888    # Tests here should pass for both dates and datetimes, except for a
889    # few tests that TestDateTime overrides.
890
891    theclass = date
892
893    def test_basic_attributes(self):
894        dt = self.theclass(2002, 3, 1)
895        self.assertEqual(dt.year, 2002)
896        self.assertEqual(dt.month, 3)
897        self.assertEqual(dt.day, 1)
898
899    def test_roundtrip(self):
900        for dt in (self.theclass(1, 2, 3),
901                   self.theclass.today()):
902            # Verify dt -> string -> date identity.
903            s = repr(dt)
904            self.assertTrue(s.startswith('datetime.'))
905            s = s[9:]
906            dt2 = eval(s)
907            self.assertEqual(dt, dt2)
908
909            # Verify identity via reconstructing from pieces.
910            dt2 = self.theclass(dt.year, dt.month, dt.day)
911            self.assertEqual(dt, dt2)
912
913    def test_ordinal_conversions(self):
914        # Check some fixed values.
915        for y, m, d, n in [(1, 1, 1, 1),      # calendar origin
916                           (1, 12, 31, 365),
917                           (2, 1, 1, 366),
918                           # first example from "Calendrical Calculations"
919                           (1945, 11, 12, 710347)]:
920            d = self.theclass(y, m, d)
921            self.assertEqual(n, d.toordinal())
922            fromord = self.theclass.fromordinal(n)
923            self.assertEqual(d, fromord)
924            if hasattr(fromord, "hour"):
925            # if we're checking something fancier than a date, verify
926            # the extra fields have been zeroed out
927                self.assertEqual(fromord.hour, 0)
928                self.assertEqual(fromord.minute, 0)
929                self.assertEqual(fromord.second, 0)
930                self.assertEqual(fromord.microsecond, 0)
931
932        # Check first and last days of year spottily across the whole
933        # range of years supported.
934        for year in range(MINYEAR, MAXYEAR+1, 7):
935            # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
936            d = self.theclass(year, 1, 1)
937            n = d.toordinal()
938            d2 = self.theclass.fromordinal(n)
939            self.assertEqual(d, d2)
940            # Verify that moving back a day gets to the end of year-1.
941            if year > 1:
942                d = self.theclass.fromordinal(n-1)
943                d2 = self.theclass(year-1, 12, 31)
944                self.assertEqual(d, d2)
945                self.assertEqual(d2.toordinal(), n-1)
946
947        # Test every day in a leap-year and a non-leap year.
948        dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
949        for year, isleap in (2000, True), (2002, False):
950            n = self.theclass(year, 1, 1).toordinal()
951            for month, maxday in zip(range(1, 13), dim):
952                if month == 2 and isleap:
953                    maxday += 1
954                for day in range(1, maxday+1):
955                    d = self.theclass(year, month, day)
956                    self.assertEqual(d.toordinal(), n)
957                    self.assertEqual(d, self.theclass.fromordinal(n))
958                    n += 1
959
960    def test_extreme_ordinals(self):
961        a = self.theclass.min
962        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
963        aord = a.toordinal()
964        b = a.fromordinal(aord)
965        self.assertEqual(a, b)
966
967        self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
968
969        b = a + timedelta(days=1)
970        self.assertEqual(b.toordinal(), aord + 1)
971        self.assertEqual(b, self.theclass.fromordinal(aord + 1))
972
973        a = self.theclass.max
974        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
975        aord = a.toordinal()
976        b = a.fromordinal(aord)
977        self.assertEqual(a, b)
978
979        self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
980
981        b = a - timedelta(days=1)
982        self.assertEqual(b.toordinal(), aord - 1)
983        self.assertEqual(b, self.theclass.fromordinal(aord - 1))
984
985    def test_bad_constructor_arguments(self):
986        # bad years
987        self.theclass(MINYEAR, 1, 1)  # no exception
988        self.theclass(MAXYEAR, 1, 1)  # no exception
989        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
990        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
991        # bad months
992        self.theclass(2000, 1, 1)    # no exception
993        self.theclass(2000, 12, 1)   # no exception
994        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
995        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
996        # bad days
997        self.theclass(2000, 2, 29)   # no exception
998        self.theclass(2004, 2, 29)   # no exception
999        self.theclass(2400, 2, 29)   # no exception
1000        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1001        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1002        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1003        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1004        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1005        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1006
1007    def test_hash_equality(self):
1008        d = self.theclass(2000, 12, 31)
1009        # same thing
1010        e = self.theclass(2000, 12, 31)
1011        self.assertEqual(d, e)
1012        self.assertEqual(hash(d), hash(e))
1013
1014        dic = {d: 1}
1015        dic[e] = 2
1016        self.assertEqual(len(dic), 1)
1017        self.assertEqual(dic[d], 2)
1018        self.assertEqual(dic[e], 2)
1019
1020        d = self.theclass(2001,  1,  1)
1021        # same thing
1022        e = self.theclass(2001,  1,  1)
1023        self.assertEqual(d, e)
1024        self.assertEqual(hash(d), hash(e))
1025
1026        dic = {d: 1}
1027        dic[e] = 2
1028        self.assertEqual(len(dic), 1)
1029        self.assertEqual(dic[d], 2)
1030        self.assertEqual(dic[e], 2)
1031
1032    def test_computations(self):
1033        a = self.theclass(2002, 1, 31)
1034        b = self.theclass(1956, 1, 31)
1035        c = self.theclass(2001,2,1)
1036
1037        diff = a-b
1038        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1039        self.assertEqual(diff.seconds, 0)
1040        self.assertEqual(diff.microseconds, 0)
1041
1042        day = timedelta(1)
1043        week = timedelta(7)
1044        a = self.theclass(2002, 3, 2)
1045        self.assertEqual(a + day, self.theclass(2002, 3, 3))
1046        self.assertEqual(day + a, self.theclass(2002, 3, 3))
1047        self.assertEqual(a - day, self.theclass(2002, 3, 1))
1048        self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1049        self.assertEqual(a + week, self.theclass(2002, 3, 9))
1050        self.assertEqual(a - week, self.theclass(2002, 2, 23))
1051        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1052        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1053        self.assertEqual((a + week) - a, week)
1054        self.assertEqual((a + day) - a, day)
1055        self.assertEqual((a - week) - a, -week)
1056        self.assertEqual((a - day) - a, -day)
1057        self.assertEqual(a - (a + week), -week)
1058        self.assertEqual(a - (a + day), -day)
1059        self.assertEqual(a - (a - week), week)
1060        self.assertEqual(a - (a - day), day)
1061        self.assertEqual(c - (c - day), day)
1062
1063        # Add/sub ints or floats should be illegal
1064        for i in 1, 1.0:
1065            self.assertRaises(TypeError, lambda: a+i)
1066            self.assertRaises(TypeError, lambda: a-i)
1067            self.assertRaises(TypeError, lambda: i+a)
1068            self.assertRaises(TypeError, lambda: i-a)
1069
1070        # delta - date is senseless.
1071        self.assertRaises(TypeError, lambda: day - a)
1072        # mixing date and (delta or date) via * or // is senseless
1073        self.assertRaises(TypeError, lambda: day * a)
1074        self.assertRaises(TypeError, lambda: a * day)
1075        self.assertRaises(TypeError, lambda: day // a)
1076        self.assertRaises(TypeError, lambda: a // day)
1077        self.assertRaises(TypeError, lambda: a * a)
1078        self.assertRaises(TypeError, lambda: a // a)
1079        # date + date is senseless
1080        self.assertRaises(TypeError, lambda: a + a)
1081
1082    def test_overflow(self):
1083        tiny = self.theclass.resolution
1084
1085        for delta in [tiny, timedelta(1), timedelta(2)]:
1086            dt = self.theclass.min + delta
1087            dt -= delta  # no problem
1088            self.assertRaises(OverflowError, dt.__sub__, delta)
1089            self.assertRaises(OverflowError, dt.__add__, -delta)
1090
1091            dt = self.theclass.max - delta
1092            dt += delta  # no problem
1093            self.assertRaises(OverflowError, dt.__add__, delta)
1094            self.assertRaises(OverflowError, dt.__sub__, -delta)
1095
1096    def test_fromtimestamp(self):
1097        import time
1098
1099        # Try an arbitrary fixed value.
1100        year, month, day = 1999, 9, 19
1101        ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1102        d = self.theclass.fromtimestamp(ts)
1103        self.assertEqual(d.year, year)
1104        self.assertEqual(d.month, month)
1105        self.assertEqual(d.day, day)
1106
1107    def test_insane_fromtimestamp(self):
1108        # It's possible that some platform maps time_t to double,
1109        # and that this test will fail there.  This test should
1110        # exempt such platforms (provided they return reasonable
1111        # results!).
1112        for insane in -1e200, 1e200:
1113            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
1114                              insane)
1115
1116    def test_today(self):
1117        import time
1118
1119        # We claim that today() is like fromtimestamp(time.time()), so
1120        # prove it.
1121        for dummy in range(3):
1122            today = self.theclass.today()
1123            ts = time.time()
1124            todayagain = self.theclass.fromtimestamp(ts)
1125            if today == todayagain:
1126                break
1127            # There are several legit reasons that could fail:
1128            # 1. It recently became midnight, between the today() and the
1129            #    time() calls.
1130            # 2. The platform time() has such fine resolution that we'll
1131            #    never get the same value twice.
1132            # 3. The platform time() has poor resolution, and we just
1133            #    happened to call today() right before a resolution quantum
1134            #    boundary.
1135            # 4. The system clock got fiddled between calls.
1136            # In any case, wait a little while and try again.
1137            time.sleep(0.1)
1138
1139        # It worked or it didn't.  If it didn't, assume it's reason #2, and
1140        # let the test pass if they're within half a second of each other.
1141        if today != todayagain:
1142            self.assertAlmostEqual(todayagain, today,
1143                                   delta=timedelta(seconds=0.5))
1144
1145    def test_weekday(self):
1146        for i in range(7):
1147            # March 4, 2002 is a Monday
1148            self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1149            self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1150            # January 2, 1956 is a Monday
1151            self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1152            self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1153
1154    def test_isocalendar(self):
1155        # Check examples from
1156        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1157        for i in range(7):
1158            d = self.theclass(2003, 12, 22+i)
1159            self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1160            d = self.theclass(2003, 12, 29) + timedelta(i)
1161            self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1162            d = self.theclass(2004, 1, 5+i)
1163            self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1164            d = self.theclass(2009, 12, 21+i)
1165            self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1166            d = self.theclass(2009, 12, 28) + timedelta(i)
1167            self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1168            d = self.theclass(2010, 1, 4+i)
1169            self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1170
1171    def test_iso_long_years(self):
1172        # Calculate long ISO years and compare to table from
1173        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1174        ISO_LONG_YEARS_TABLE = """
1175              4   32   60   88
1176              9   37   65   93
1177             15   43   71   99
1178             20   48   76
1179             26   54   82
1180
1181            105  133  161  189
1182            111  139  167  195
1183            116  144  172
1184            122  150  178
1185            128  156  184
1186
1187            201  229  257  285
1188            207  235  263  291
1189            212  240  268  296
1190            218  246  274
1191            224  252  280
1192
1193            303  331  359  387
1194            308  336  364  392
1195            314  342  370  398
1196            320  348  376
1197            325  353  381
1198        """
1199        iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1200        L = []
1201        for i in range(400):
1202            d = self.theclass(2000+i, 12, 31)
1203            d1 = self.theclass(1600+i, 12, 31)
1204            self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1205            if d.isocalendar()[1] == 53:
1206                L.append(i)
1207        self.assertEqual(L, iso_long_years)
1208
1209    def test_isoformat(self):
1210        t = self.theclass(2, 3, 2)
1211        self.assertEqual(t.isoformat(), "0002-03-02")
1212
1213    def test_ctime(self):
1214        t = self.theclass(2002, 3, 2)
1215        self.assertEqual(t.ctime(), "Sat Mar  2 00:00:00 2002")
1216
1217    def test_strftime(self):
1218        t = self.theclass(2005, 3, 2)
1219        self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1220        self.assertEqual(t.strftime(""), "") # SF bug #761337
1221        self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1222
1223        self.assertRaises(TypeError, t.strftime) # needs an arg
1224        self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1225        self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1226
1227        # test that unicode input is allowed (issue 2782)
1228        self.assertEqual(t.strftime("%m"), "03")
1229
1230        # A naive object replaces %z and %Z w/ empty strings.
1231        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1232
1233        #make sure that invalid format specifiers are handled correctly
1234        #self.assertRaises(ValueError, t.strftime, "%e")
1235        #self.assertRaises(ValueError, t.strftime, "%")
1236        #self.assertRaises(ValueError, t.strftime, "%#")
1237
1238        #oh well, some systems just ignore those invalid ones.
1239        #at least, exercise them to make sure that no crashes
1240        #are generated
1241        for f in ["%e", "%", "%#"]:
1242            try:
1243                t.strftime(f)
1244            except ValueError:
1245                pass
1246
1247        #check that this standard extension works
1248        t.strftime("%f")
1249
1250    def test_format(self):
1251        dt = self.theclass(2007, 9, 10)
1252        self.assertEqual(dt.__format__(''), str(dt))
1253
1254        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
1255            dt.__format__(123)
1256
1257        # check that a derived class's __str__() gets called
1258        class A(self.theclass):
1259            def __str__(self):
1260                return 'A'
1261        a = A(2007, 9, 10)
1262        self.assertEqual(a.__format__(''), 'A')
1263
1264        # check that a derived class's strftime gets called
1265        class B(self.theclass):
1266            def strftime(self, format_spec):
1267                return 'B'
1268        b = B(2007, 9, 10)
1269        self.assertEqual(b.__format__(''), str(dt))
1270
1271        for fmt in ["m:%m d:%d y:%y",
1272                    "m:%m d:%d y:%y H:%H M:%M S:%S",
1273                    "%z %Z",
1274                    ]:
1275            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1276            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1277            self.assertEqual(b.__format__(fmt), 'B')
1278
1279    def test_resolution_info(self):
1280        # XXX: Should min and max respect subclassing?
1281        if issubclass(self.theclass, datetime):
1282            expected_class = datetime
1283        else:
1284            expected_class = date
1285        self.assertIsInstance(self.theclass.min, expected_class)
1286        self.assertIsInstance(self.theclass.max, expected_class)
1287        self.assertIsInstance(self.theclass.resolution, timedelta)
1288        self.assertTrue(self.theclass.max > self.theclass.min)
1289
1290    def test_extreme_timedelta(self):
1291        big = self.theclass.max - self.theclass.min
1292        # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1293        n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1294        # n == 315537897599999999 ~= 2**58.13
1295        justasbig = timedelta(0, 0, n)
1296        self.assertEqual(big, justasbig)
1297        self.assertEqual(self.theclass.min + big, self.theclass.max)
1298        self.assertEqual(self.theclass.max - big, self.theclass.min)
1299
1300    def test_timetuple(self):
1301        for i in range(7):
1302            # January 2, 1956 is a Monday (0)
1303            d = self.theclass(1956, 1, 2+i)
1304            t = d.timetuple()
1305            self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1306            # February 1, 1956 is a Wednesday (2)
1307            d = self.theclass(1956, 2, 1+i)
1308            t = d.timetuple()
1309            self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1310            # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1311            # of the year.
1312            d = self.theclass(1956, 3, 1+i)
1313            t = d.timetuple()
1314            self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1315            self.assertEqual(t.tm_year, 1956)
1316            self.assertEqual(t.tm_mon, 3)
1317            self.assertEqual(t.tm_mday, 1+i)
1318            self.assertEqual(t.tm_hour, 0)
1319            self.assertEqual(t.tm_min, 0)
1320            self.assertEqual(t.tm_sec, 0)
1321            self.assertEqual(t.tm_wday, (3+i)%7)
1322            self.assertEqual(t.tm_yday, 61+i)
1323            self.assertEqual(t.tm_isdst, -1)
1324
1325    def test_pickling(self):
1326        args = 6, 7, 23
1327        orig = self.theclass(*args)
1328        for pickler, unpickler, proto in pickle_choices:
1329            green = pickler.dumps(orig, proto)
1330            derived = unpickler.loads(green)
1331            self.assertEqual(orig, derived)
1332        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
1333
1334    def test_compare(self):
1335        t1 = self.theclass(2, 3, 4)
1336        t2 = self.theclass(2, 3, 4)
1337        self.assertEqual(t1, t2)
1338        self.assertTrue(t1 <= t2)
1339        self.assertTrue(t1 >= t2)
1340        self.assertFalse(t1 != t2)
1341        self.assertFalse(t1 < t2)
1342        self.assertFalse(t1 > t2)
1343
1344        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1345            t2 = self.theclass(*args)   # this is larger than t1
1346            self.assertTrue(t1 < t2)
1347            self.assertTrue(t2 > t1)
1348            self.assertTrue(t1 <= t2)
1349            self.assertTrue(t2 >= t1)
1350            self.assertTrue(t1 != t2)
1351            self.assertTrue(t2 != t1)
1352            self.assertFalse(t1 == t2)
1353            self.assertFalse(t2 == t1)
1354            self.assertFalse(t1 > t2)
1355            self.assertFalse(t2 < t1)
1356            self.assertFalse(t1 >= t2)
1357            self.assertFalse(t2 <= t1)
1358
1359        for badarg in OTHERSTUFF:
1360            self.assertEqual(t1 == badarg, False)
1361            self.assertEqual(t1 != badarg, True)
1362            self.assertEqual(badarg == t1, False)
1363            self.assertEqual(badarg != t1, True)
1364
1365            self.assertRaises(TypeError, lambda: t1 < badarg)
1366            self.assertRaises(TypeError, lambda: t1 > badarg)
1367            self.assertRaises(TypeError, lambda: t1 >= badarg)
1368            self.assertRaises(TypeError, lambda: badarg <= t1)
1369            self.assertRaises(TypeError, lambda: badarg < t1)
1370            self.assertRaises(TypeError, lambda: badarg > t1)
1371            self.assertRaises(TypeError, lambda: badarg >= t1)
1372
1373    def test_mixed_compare(self):
1374        our = self.theclass(2000, 4, 5)
1375
1376        # Our class can be compared for equality to other classes
1377        self.assertEqual(our == 1, False)
1378        self.assertEqual(1 == our, False)
1379        self.assertEqual(our != 1, True)
1380        self.assertEqual(1 != our, True)
1381
1382        # But the ordering is undefined
1383        self.assertRaises(TypeError, lambda: our < 1)
1384        self.assertRaises(TypeError, lambda: 1 < our)
1385
1386        # Repeat those tests with a different class
1387
1388        class SomeClass:
1389            pass
1390
1391        their = SomeClass()
1392        self.assertEqual(our == their, False)
1393        self.assertEqual(their == our, False)
1394        self.assertEqual(our != their, True)
1395        self.assertEqual(their != our, True)
1396        self.assertRaises(TypeError, lambda: our < their)
1397        self.assertRaises(TypeError, lambda: their < our)
1398
1399        # However, if the other class explicitly defines ordering
1400        # relative to our class, it is allowed to do so
1401
1402        class LargerThanAnything:
1403            def __lt__(self, other):
1404                return False
1405            def __le__(self, other):
1406                return isinstance(other, LargerThanAnything)
1407            def __eq__(self, other):
1408                return isinstance(other, LargerThanAnything)
1409            def __gt__(self, other):
1410                return not isinstance(other, LargerThanAnything)
1411            def __ge__(self, other):
1412                return True
1413
1414        their = LargerThanAnything()
1415        self.assertEqual(our == their, False)
1416        self.assertEqual(their == our, False)
1417        self.assertEqual(our != their, True)
1418        self.assertEqual(their != our, True)
1419        self.assertEqual(our < their, True)
1420        self.assertEqual(their < our, False)
1421
1422    def test_bool(self):
1423        # All dates are considered true.
1424        self.assertTrue(self.theclass.min)
1425        self.assertTrue(self.theclass.max)
1426
1427    def test_strftime_y2k(self):
1428        for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
1429            d = self.theclass(y, 1, 1)
1430            # Issue 13305:  For years < 1000, the value is not always
1431            # padded to 4 digits across platforms.  The C standard
1432            # assumes year >= 1900, so it does not specify the number
1433            # of digits.
1434            if d.strftime("%Y") != '%04d' % y:
1435                # Year 42 returns '42', not padded
1436                self.assertEqual(d.strftime("%Y"), '%d' % y)
1437                # '0042' is obtained anyway
1438                self.assertEqual(d.strftime("%4Y"), '%04d' % y)
1439
1440    def test_replace(self):
1441        cls = self.theclass
1442        args = [1, 2, 3]
1443        base = cls(*args)
1444        self.assertEqual(base, base.replace())
1445
1446        i = 0
1447        for name, newval in (("year", 2),
1448                             ("month", 3),
1449                             ("day", 4)):
1450            newargs = args[:]
1451            newargs[i] = newval
1452            expected = cls(*newargs)
1453            got = base.replace(**{name: newval})
1454            self.assertEqual(expected, got)
1455            i += 1
1456
1457        # Out of bounds.
1458        base = cls(2000, 2, 29)
1459        self.assertRaises(ValueError, base.replace, year=2001)
1460
1461    def test_subclass_date(self):
1462
1463        class C(self.theclass):
1464            theAnswer = 42
1465
1466            def __new__(cls, *args, **kws):
1467                temp = kws.copy()
1468                extra = temp.pop('extra')
1469                result = self.theclass.__new__(cls, *args, **temp)
1470                result.extra = extra
1471                return result
1472
1473            def newmeth(self, start):
1474                return start + self.year + self.month
1475
1476        args = 2003, 4, 14
1477
1478        dt1 = self.theclass(*args)
1479        dt2 = C(*args, **{'extra': 7})
1480
1481        self.assertEqual(dt2.__class__, C)
1482        self.assertEqual(dt2.theAnswer, 42)
1483        self.assertEqual(dt2.extra, 7)
1484        self.assertEqual(dt1.toordinal(), dt2.toordinal())
1485        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1486
1487    def test_pickling_subclass_date(self):
1488
1489        args = 6, 7, 23
1490        orig = SubclassDate(*args)
1491        for pickler, unpickler, proto in pickle_choices:
1492            green = pickler.dumps(orig, proto)
1493            derived = unpickler.loads(green)
1494            self.assertEqual(orig, derived)
1495
1496    def test_backdoor_resistance(self):
1497        # For fast unpickling, the constructor accepts a pickle byte string.
1498        # This is a low-overhead backdoor.  A user can (by intent or
1499        # mistake) pass a string directly, which (if it's the right length)
1500        # will get treated like a pickle, and bypass the normal sanity
1501        # checks in the constructor.  This can create insane objects.
1502        # The constructor doesn't want to burn the time to validate all
1503        # fields, but does check the month field.  This stops, e.g.,
1504        # datetime.datetime('1995-03-25') from yielding an insane object.
1505        base = b'1995-03-25'
1506        if not issubclass(self.theclass, datetime):
1507            base = base[:4]
1508        for month_byte in b'9', b'\0', b'\r', b'\xff':
1509            self.assertRaises(TypeError, self.theclass,
1510                                         base[:2] + month_byte + base[3:])
1511        if issubclass(self.theclass, datetime):
1512            # Good bytes, but bad tzinfo:
1513            with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1514                self.theclass(bytes([1] * len(base)), 'EST')
1515
1516        for ord_byte in range(1, 13):
1517            # This shouldn't blow up because of the month byte alone.  If
1518            # the implementation changes to do more-careful checking, it may
1519            # blow up because other fields are insane.
1520            self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1521
1522#############################################################################
1523# datetime tests
1524
1525class SubclassDatetime(datetime):
1526    sub_var = 1
1527
1528class TestDateTime(TestDate):
1529
1530    theclass = datetime
1531
1532    def test_basic_attributes(self):
1533        dt = self.theclass(2002, 3, 1, 12, 0)
1534        self.assertEqual(dt.year, 2002)
1535        self.assertEqual(dt.month, 3)
1536        self.assertEqual(dt.day, 1)
1537        self.assertEqual(dt.hour, 12)
1538        self.assertEqual(dt.minute, 0)
1539        self.assertEqual(dt.second, 0)
1540        self.assertEqual(dt.microsecond, 0)
1541
1542    def test_basic_attributes_nonzero(self):
1543        # Make sure all attributes are non-zero so bugs in
1544        # bit-shifting access show up.
1545        dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1546        self.assertEqual(dt.year, 2002)
1547        self.assertEqual(dt.month, 3)
1548        self.assertEqual(dt.day, 1)
1549        self.assertEqual(dt.hour, 12)
1550        self.assertEqual(dt.minute, 59)
1551        self.assertEqual(dt.second, 59)
1552        self.assertEqual(dt.microsecond, 8000)
1553
1554    def test_roundtrip(self):
1555        for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1556                   self.theclass.now()):
1557            # Verify dt -> string -> datetime identity.
1558            s = repr(dt)
1559            self.assertTrue(s.startswith('datetime.'))
1560            s = s[9:]
1561            dt2 = eval(s)
1562            self.assertEqual(dt, dt2)
1563
1564            # Verify identity via reconstructing from pieces.
1565            dt2 = self.theclass(dt.year, dt.month, dt.day,
1566                                dt.hour, dt.minute, dt.second,
1567                                dt.microsecond)
1568            self.assertEqual(dt, dt2)
1569
1570    def test_isoformat(self):
1571        t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1572        self.assertEqual(t.isoformat(),    "0001-02-03T04:05:01.000123")
1573        self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1574        self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1575        self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1576        self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1577        self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1578        self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1579        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1580        self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1581        self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1582        self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1583        self.assertRaises(ValueError, t.isoformat, timespec='foo')
1584        # str is ISO format with the separator forced to a blank.
1585        self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1586
1587        t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1588        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1589
1590        t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1591        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1592
1593        t = self.theclass(1, 2, 3, 4, 5, 1)
1594        self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1595        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1596        self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
1597
1598        t = self.theclass(2, 3, 2)
1599        self.assertEqual(t.isoformat(),    "0002-03-02T00:00:00")
1600        self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1601        self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
1602        # str is ISO format with the separator forced to a blank.
1603        self.assertEqual(str(t), "0002-03-02 00:00:00")
1604        # ISO format with timezone
1605        tz = FixedOffset(timedelta(seconds=16), 'XXX')
1606        t = self.theclass(2, 3, 2, tzinfo=tz)
1607        self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
1608
1609    def test_format(self):
1610        dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
1611        self.assertEqual(dt.__format__(''), str(dt))
1612
1613        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
1614            dt.__format__(123)
1615
1616        # check that a derived class's __str__() gets called
1617        class A(self.theclass):
1618            def __str__(self):
1619                return 'A'
1620        a = A(2007, 9, 10, 4, 5, 1, 123)
1621        self.assertEqual(a.__format__(''), 'A')
1622
1623        # check that a derived class's strftime gets called
1624        class B(self.theclass):
1625            def strftime(self, format_spec):
1626                return 'B'
1627        b = B(2007, 9, 10, 4, 5, 1, 123)
1628        self.assertEqual(b.__format__(''), str(dt))
1629
1630        for fmt in ["m:%m d:%d y:%y",
1631                    "m:%m d:%d y:%y H:%H M:%M S:%S",
1632                    "%z %Z",
1633                    ]:
1634            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1635            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1636            self.assertEqual(b.__format__(fmt), 'B')
1637
1638    def test_more_ctime(self):
1639        # Test fields that TestDate doesn't touch.
1640        import time
1641
1642        t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
1643        self.assertEqual(t.ctime(), "Sat Mar  2 18:03:05 2002")
1644        # Oops!  The next line fails on Win2K under MSVC 6, so it's commented
1645        # out.  The difference is that t.ctime() produces " 2" for the day,
1646        # but platform ctime() produces "02" for the day.  According to
1647        # C99, t.ctime() is correct here.
1648        # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1649
1650        # So test a case where that difference doesn't matter.
1651        t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
1652        self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
1653
1654    def test_tz_independent_comparing(self):
1655        dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
1656        dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
1657        dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
1658        self.assertEqual(dt1, dt3)
1659        self.assertTrue(dt2 > dt3)
1660
1661        # Make sure comparison doesn't forget microseconds, and isn't done
1662        # via comparing a float timestamp (an IEEE double doesn't have enough
1663        # precision to span microsecond resolution across years 1 thru 9999,
1664        # so comparing via timestamp necessarily calls some distinct values
1665        # equal).
1666        dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
1667        us = timedelta(microseconds=1)
1668        dt2 = dt1 + us
1669        self.assertEqual(dt2 - dt1, us)
1670        self.assertTrue(dt1 < dt2)
1671
1672    def test_strftime_with_bad_tzname_replace(self):
1673        # verify ok if tzinfo.tzname().replace() returns a non-string
1674        class MyTzInfo(FixedOffset):
1675            def tzname(self, dt):
1676                class MyStr(str):
1677                    def replace(self, *args):
1678                        return None
1679                return MyStr('name')
1680        t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
1681        self.assertRaises(TypeError, t.strftime, '%Z')
1682
1683    def test_bad_constructor_arguments(self):
1684        # bad years
1685        self.theclass(MINYEAR, 1, 1)  # no exception
1686        self.theclass(MAXYEAR, 1, 1)  # no exception
1687        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1688        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1689        # bad months
1690        self.theclass(2000, 1, 1)    # no exception
1691        self.theclass(2000, 12, 1)   # no exception
1692        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1693        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1694        # bad days
1695        self.theclass(2000, 2, 29)   # no exception
1696        self.theclass(2004, 2, 29)   # no exception
1697        self.theclass(2400, 2, 29)   # no exception
1698        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1699        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1700        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1701        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1702        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1703        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1704        # bad hours
1705        self.theclass(2000, 1, 31, 0)    # no exception
1706        self.theclass(2000, 1, 31, 23)   # no exception
1707        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
1708        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
1709        # bad minutes
1710        self.theclass(2000, 1, 31, 23, 0)    # no exception
1711        self.theclass(2000, 1, 31, 23, 59)   # no exception
1712        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
1713        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
1714        # bad seconds
1715        self.theclass(2000, 1, 31, 23, 59, 0)    # no exception
1716        self.theclass(2000, 1, 31, 23, 59, 59)   # no exception
1717        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
1718        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
1719        # bad microseconds
1720        self.theclass(2000, 1, 31, 23, 59, 59, 0)    # no exception
1721        self.theclass(2000, 1, 31, 23, 59, 59, 999999)   # no exception
1722        self.assertRaises(ValueError, self.theclass,
1723                          2000, 1, 31, 23, 59, 59, -1)
1724        self.assertRaises(ValueError, self.theclass,
1725                          2000, 1, 31, 23, 59, 59,
1726                          1000000)
1727        # bad fold
1728        self.assertRaises(ValueError, self.theclass,
1729                          2000, 1, 31, fold=-1)
1730        self.assertRaises(ValueError, self.theclass,
1731                          2000, 1, 31, fold=2)
1732        # Positional fold:
1733        self.assertRaises(TypeError, self.theclass,
1734                          2000, 1, 31, 23, 59, 59, 0, None, 1)
1735
1736    def test_hash_equality(self):
1737        d = self.theclass(2000, 12, 31, 23, 30, 17)
1738        e = self.theclass(2000, 12, 31, 23, 30, 17)
1739        self.assertEqual(d, e)
1740        self.assertEqual(hash(d), hash(e))
1741
1742        dic = {d: 1}
1743        dic[e] = 2
1744        self.assertEqual(len(dic), 1)
1745        self.assertEqual(dic[d], 2)
1746        self.assertEqual(dic[e], 2)
1747
1748        d = self.theclass(2001,  1,  1,  0,  5, 17)
1749        e = self.theclass(2001,  1,  1,  0,  5, 17)
1750        self.assertEqual(d, e)
1751        self.assertEqual(hash(d), hash(e))
1752
1753        dic = {d: 1}
1754        dic[e] = 2
1755        self.assertEqual(len(dic), 1)
1756        self.assertEqual(dic[d], 2)
1757        self.assertEqual(dic[e], 2)
1758
1759    def test_computations(self):
1760        a = self.theclass(2002, 1, 31)
1761        b = self.theclass(1956, 1, 31)
1762        diff = a-b
1763        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1764        self.assertEqual(diff.seconds, 0)
1765        self.assertEqual(diff.microseconds, 0)
1766        a = self.theclass(2002, 3, 2, 17, 6)
1767        millisec = timedelta(0, 0, 1000)
1768        hour = timedelta(0, 3600)
1769        day = timedelta(1)
1770        week = timedelta(7)
1771        self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
1772        self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
1773        self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
1774        self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
1775        self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
1776        self.assertEqual(a - hour, a + -hour)
1777        self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
1778        self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
1779        self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
1780        self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
1781        self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
1782        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
1783        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
1784        self.assertEqual((a + week) - a, week)
1785        self.assertEqual((a + day) - a, day)
1786        self.assertEqual((a + hour) - a, hour)
1787        self.assertEqual((a + millisec) - a, millisec)
1788        self.assertEqual((a - week) - a, -week)
1789        self.assertEqual((a - day) - a, -day)
1790        self.assertEqual((a - hour) - a, -hour)
1791        self.assertEqual((a - millisec) - a, -millisec)
1792        self.assertEqual(a - (a + week), -week)
1793        self.assertEqual(a - (a + day), -day)
1794        self.assertEqual(a - (a + hour), -hour)
1795        self.assertEqual(a - (a + millisec), -millisec)
1796        self.assertEqual(a - (a - week), week)
1797        self.assertEqual(a - (a - day), day)
1798        self.assertEqual(a - (a - hour), hour)
1799        self.assertEqual(a - (a - millisec), millisec)
1800        self.assertEqual(a + (week + day + hour + millisec),
1801                         self.theclass(2002, 3, 10, 18, 6, 0, 1000))
1802        self.assertEqual(a + (week + day + hour + millisec),
1803                         (((a + week) + day) + hour) + millisec)
1804        self.assertEqual(a - (week + day + hour + millisec),
1805                         self.theclass(2002, 2, 22, 16, 5, 59, 999000))
1806        self.assertEqual(a - (week + day + hour + millisec),
1807                         (((a - week) - day) - hour) - millisec)
1808        # Add/sub ints or floats should be illegal
1809        for i in 1, 1.0:
1810            self.assertRaises(TypeError, lambda: a+i)
1811            self.assertRaises(TypeError, lambda: a-i)
1812            self.assertRaises(TypeError, lambda: i+a)
1813            self.assertRaises(TypeError, lambda: i-a)
1814
1815        # delta - datetime is senseless.
1816        self.assertRaises(TypeError, lambda: day - a)
1817        # mixing datetime and (delta or datetime) via * or // is senseless
1818        self.assertRaises(TypeError, lambda: day * a)
1819        self.assertRaises(TypeError, lambda: a * day)
1820        self.assertRaises(TypeError, lambda: day // a)
1821        self.assertRaises(TypeError, lambda: a // day)
1822        self.assertRaises(TypeError, lambda: a * a)
1823        self.assertRaises(TypeError, lambda: a // a)
1824        # datetime + datetime is senseless
1825        self.assertRaises(TypeError, lambda: a + a)
1826
1827    def test_pickling(self):
1828        args = 6, 7, 23, 20, 59, 1, 64**2
1829        orig = self.theclass(*args)
1830        for pickler, unpickler, proto in pickle_choices:
1831            green = pickler.dumps(orig, proto)
1832            derived = unpickler.loads(green)
1833            self.assertEqual(orig, derived)
1834        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
1835
1836    def test_more_pickling(self):
1837        a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
1838        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1839            s = pickle.dumps(a, proto)
1840            b = pickle.loads(s)
1841            self.assertEqual(b.year, 2003)
1842            self.assertEqual(b.month, 2)
1843            self.assertEqual(b.day, 7)
1844
1845    def test_pickling_subclass_datetime(self):
1846        args = 6, 7, 23, 20, 59, 1, 64**2
1847        orig = SubclassDatetime(*args)
1848        for pickler, unpickler, proto in pickle_choices:
1849            green = pickler.dumps(orig, proto)
1850            derived = unpickler.loads(green)
1851            self.assertEqual(orig, derived)
1852
1853    def test_more_compare(self):
1854        # The test_compare() inherited from TestDate covers the error cases.
1855        # We just want to test lexicographic ordering on the members datetime
1856        # has that date lacks.
1857        args = [2000, 11, 29, 20, 58, 16, 999998]
1858        t1 = self.theclass(*args)
1859        t2 = self.theclass(*args)
1860        self.assertEqual(t1, t2)
1861        self.assertTrue(t1 <= t2)
1862        self.assertTrue(t1 >= t2)
1863        self.assertFalse(t1 != t2)
1864        self.assertFalse(t1 < t2)
1865        self.assertFalse(t1 > t2)
1866
1867        for i in range(len(args)):
1868            newargs = args[:]
1869            newargs[i] = args[i] + 1
1870            t2 = self.theclass(*newargs)   # this is larger than t1
1871            self.assertTrue(t1 < t2)
1872            self.assertTrue(t2 > t1)
1873            self.assertTrue(t1 <= t2)
1874            self.assertTrue(t2 >= t1)
1875            self.assertTrue(t1 != t2)
1876            self.assertTrue(t2 != t1)
1877            self.assertFalse(t1 == t2)
1878            self.assertFalse(t2 == t1)
1879            self.assertFalse(t1 > t2)
1880            self.assertFalse(t2 < t1)
1881            self.assertFalse(t1 >= t2)
1882            self.assertFalse(t2 <= t1)
1883
1884
1885    # A helper for timestamp constructor tests.
1886    def verify_field_equality(self, expected, got):
1887        self.assertEqual(expected.tm_year, got.year)
1888        self.assertEqual(expected.tm_mon, got.month)
1889        self.assertEqual(expected.tm_mday, got.day)
1890        self.assertEqual(expected.tm_hour, got.hour)
1891        self.assertEqual(expected.tm_min, got.minute)
1892        self.assertEqual(expected.tm_sec, got.second)
1893
1894    def test_fromtimestamp(self):
1895        import time
1896
1897        ts = time.time()
1898        expected = time.localtime(ts)
1899        got = self.theclass.fromtimestamp(ts)
1900        self.verify_field_equality(expected, got)
1901
1902    def test_utcfromtimestamp(self):
1903        import time
1904
1905        ts = time.time()
1906        expected = time.gmtime(ts)
1907        got = self.theclass.utcfromtimestamp(ts)
1908        self.verify_field_equality(expected, got)
1909
1910    # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
1911    # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
1912    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
1913    def test_timestamp_naive(self):
1914        t = self.theclass(1970, 1, 1)
1915        self.assertEqual(t.timestamp(), 18000.0)
1916        t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
1917        self.assertEqual(t.timestamp(),
1918                         18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
1919        # Missing hour
1920        t0 = self.theclass(2012, 3, 11, 2, 30)
1921        t1 = t0.replace(fold=1)
1922        self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
1923                         t0 - timedelta(hours=1))
1924        self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
1925                         t1 + timedelta(hours=1))
1926        # Ambiguous hour defaults to DST
1927        t = self.theclass(2012, 11, 4, 1, 30)
1928        self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
1929
1930        # Timestamp may raise an overflow error on some platforms
1931        # XXX: Do we care to support the first and last year?
1932        for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
1933            try:
1934                s = t.timestamp()
1935            except OverflowError:
1936                pass
1937            else:
1938                self.assertEqual(self.theclass.fromtimestamp(s), t)
1939
1940    def test_timestamp_aware(self):
1941        t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
1942        self.assertEqual(t.timestamp(), 0.0)
1943        t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
1944        self.assertEqual(t.timestamp(),
1945                         3600 + 2*60 + 3 + 4*1e-6)
1946        t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
1947                          tzinfo=timezone(timedelta(hours=-5), 'EST'))
1948        self.assertEqual(t.timestamp(),
1949                         18000 + 3600 + 2*60 + 3 + 4*1e-6)
1950
1951    @support.run_with_tz('MSK-03')  # Something east of Greenwich
1952    def test_microsecond_rounding(self):
1953        for fts in [self.theclass.fromtimestamp,
1954                    self.theclass.utcfromtimestamp]:
1955            zero = fts(0)
1956            self.assertEqual(zero.second, 0)
1957            self.assertEqual(zero.microsecond, 0)
1958            one = fts(1e-6)
1959            try:
1960                minus_one = fts(-1e-6)
1961            except OSError:
1962                # localtime(-1) and gmtime(-1) is not supported on Windows
1963                pass
1964            else:
1965                self.assertEqual(minus_one.second, 59)
1966                self.assertEqual(minus_one.microsecond, 999999)
1967
1968                t = fts(-1e-8)
1969                self.assertEqual(t, zero)
1970                t = fts(-9e-7)
1971                self.assertEqual(t, minus_one)
1972                t = fts(-1e-7)
1973                self.assertEqual(t, zero)
1974                t = fts(-1/2**7)
1975                self.assertEqual(t.second, 59)
1976                self.assertEqual(t.microsecond, 992188)
1977
1978            t = fts(1e-7)
1979            self.assertEqual(t, zero)
1980            t = fts(9e-7)
1981            self.assertEqual(t, one)
1982            t = fts(0.99999949)
1983            self.assertEqual(t.second, 0)
1984            self.assertEqual(t.microsecond, 999999)
1985            t = fts(0.9999999)
1986            self.assertEqual(t.second, 1)
1987            self.assertEqual(t.microsecond, 0)
1988            t = fts(1/2**7)
1989            self.assertEqual(t.second, 0)
1990            self.assertEqual(t.microsecond, 7812)
1991
1992    def test_timestamp_limits(self):
1993        # minimum timestamp
1994        min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
1995        min_ts = min_dt.timestamp()
1996        try:
1997            # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
1998            self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
1999                             min_dt)
2000        except (OverflowError, OSError) as exc:
2001            # the date 0001-01-01 doesn't fit into 32-bit time_t,
2002            # or platform doesn't support such very old date
2003            self.skipTest(str(exc))
2004
2005        # maximum timestamp: set seconds to zero to avoid rounding issues
2006        max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2007                                           second=0, microsecond=0)
2008        max_ts = max_dt.timestamp()
2009        # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2010        self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2011                         max_dt)
2012
2013        # number of seconds greater than 1 year: make sure that the new date
2014        # is not valid in datetime.datetime limits
2015        delta = 3600 * 24 * 400
2016
2017        # too small
2018        ts = min_ts - delta
2019        # converting a Python int to C time_t can raise a OverflowError,
2020        # especially on 32-bit platforms.
2021        with self.assertRaises((ValueError, OverflowError)):
2022            self.theclass.fromtimestamp(ts)
2023        with self.assertRaises((ValueError, OverflowError)):
2024            self.theclass.utcfromtimestamp(ts)
2025
2026        # too big
2027        ts = max_dt.timestamp() + delta
2028        with self.assertRaises((ValueError, OverflowError)):
2029            self.theclass.fromtimestamp(ts)
2030        with self.assertRaises((ValueError, OverflowError)):
2031            self.theclass.utcfromtimestamp(ts)
2032
2033    def test_insane_fromtimestamp(self):
2034        # It's possible that some platform maps time_t to double,
2035        # and that this test will fail there.  This test should
2036        # exempt such platforms (provided they return reasonable
2037        # results!).
2038        for insane in -1e200, 1e200:
2039            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
2040                              insane)
2041
2042    def test_insane_utcfromtimestamp(self):
2043        # It's possible that some platform maps time_t to double,
2044        # and that this test will fail there.  This test should
2045        # exempt such platforms (provided they return reasonable
2046        # results!).
2047        for insane in -1e200, 1e200:
2048            self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
2049                              insane)
2050
2051    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2052    def test_negative_float_fromtimestamp(self):
2053        # The result is tz-dependent; at least test that this doesn't
2054        # fail (like it did before bug 1646728 was fixed).
2055        self.theclass.fromtimestamp(-1.05)
2056
2057    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2058    def test_negative_float_utcfromtimestamp(self):
2059        d = self.theclass.utcfromtimestamp(-1.05)
2060        self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2061
2062    def test_utcnow(self):
2063        import time
2064
2065        # Call it a success if utcnow() and utcfromtimestamp() are within
2066        # a second of each other.
2067        tolerance = timedelta(seconds=1)
2068        for dummy in range(3):
2069            from_now = self.theclass.utcnow()
2070            from_timestamp = self.theclass.utcfromtimestamp(time.time())
2071            if abs(from_timestamp - from_now) <= tolerance:
2072                break
2073            # Else try again a few times.
2074        self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
2075
2076    def test_strptime(self):
2077        string = '2004-12-01 13:02:47.197'
2078        format = '%Y-%m-%d %H:%M:%S.%f'
2079        expected = _strptime._strptime_datetime(self.theclass, string, format)
2080        got = self.theclass.strptime(string, format)
2081        self.assertEqual(expected, got)
2082        self.assertIs(type(expected), self.theclass)
2083        self.assertIs(type(got), self.theclass)
2084
2085        strptime = self.theclass.strptime
2086        self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2087        self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2088        # Only local timezone and UTC are supported
2089        for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2090                                 (-_time.timezone, _time.tzname[0])):
2091            if tzseconds < 0:
2092                sign = '-'
2093                seconds = -tzseconds
2094            else:
2095                sign ='+'
2096                seconds = tzseconds
2097            hours, minutes = divmod(seconds//60, 60)
2098            dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
2099            dt = strptime(dtstr, "%z %Z")
2100            self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2101            self.assertEqual(dt.tzname(), tzname)
2102        # Can produce inconsistent datetime
2103        dtstr, fmt = "+1234 UTC", "%z %Z"
2104        dt = strptime(dtstr, fmt)
2105        self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2106        self.assertEqual(dt.tzname(), 'UTC')
2107        # yet will roundtrip
2108        self.assertEqual(dt.strftime(fmt), dtstr)
2109
2110        # Produce naive datetime if no %z is provided
2111        self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2112
2113        with self.assertRaises(ValueError): strptime("-2400", "%z")
2114        with self.assertRaises(ValueError): strptime("-000", "%z")
2115
2116    def test_more_timetuple(self):
2117        # This tests fields beyond those tested by the TestDate.test_timetuple.
2118        t = self.theclass(2004, 12, 31, 6, 22, 33)
2119        self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2120        self.assertEqual(t.timetuple(),
2121                         (t.year, t.month, t.day,
2122                          t.hour, t.minute, t.second,
2123                          t.weekday(),
2124                          t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2125                          -1))
2126        tt = t.timetuple()
2127        self.assertEqual(tt.tm_year, t.year)
2128        self.assertEqual(tt.tm_mon, t.month)
2129        self.assertEqual(tt.tm_mday, t.day)
2130        self.assertEqual(tt.tm_hour, t.hour)
2131        self.assertEqual(tt.tm_min, t.minute)
2132        self.assertEqual(tt.tm_sec, t.second)
2133        self.assertEqual(tt.tm_wday, t.weekday())
2134        self.assertEqual(tt.tm_yday, t.toordinal() -
2135                                     date(t.year, 1, 1).toordinal() + 1)
2136        self.assertEqual(tt.tm_isdst, -1)
2137
2138    def test_more_strftime(self):
2139        # This tests fields beyond those tested by the TestDate.test_strftime.
2140        t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2141        self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2142                                    "12 31 04 000047 33 22 06 366")
2143
2144    def test_extract(self):
2145        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2146        self.assertEqual(dt.date(), date(2002, 3, 4))
2147        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2148
2149    def test_combine(self):
2150        d = date(2002, 3, 4)
2151        t = time(18, 45, 3, 1234)
2152        expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2153        combine = self.theclass.combine
2154        dt = combine(d, t)
2155        self.assertEqual(dt, expected)
2156
2157        dt = combine(time=t, date=d)
2158        self.assertEqual(dt, expected)
2159
2160        self.assertEqual(d, dt.date())
2161        self.assertEqual(t, dt.time())
2162        self.assertEqual(dt, combine(dt.date(), dt.time()))
2163
2164        self.assertRaises(TypeError, combine) # need an arg
2165        self.assertRaises(TypeError, combine, d) # need two args
2166        self.assertRaises(TypeError, combine, t, d) # args reversed
2167        self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2168        self.assertRaises(TypeError, combine, d, t, 1, 2)  # too many args
2169        self.assertRaises(TypeError, combine, "date", "time") # wrong types
2170        self.assertRaises(TypeError, combine, d, "time") # wrong type
2171        self.assertRaises(TypeError, combine, "date", t) # wrong type
2172
2173        # tzinfo= argument
2174        dt = combine(d, t, timezone.utc)
2175        self.assertIs(dt.tzinfo, timezone.utc)
2176        dt = combine(d, t, tzinfo=timezone.utc)
2177        self.assertIs(dt.tzinfo, timezone.utc)
2178        t = time()
2179        dt = combine(dt, t)
2180        self.assertEqual(dt.date(), d)
2181        self.assertEqual(dt.time(), t)
2182
2183    def test_replace(self):
2184        cls = self.theclass
2185        args = [1, 2, 3, 4, 5, 6, 7]
2186        base = cls(*args)
2187        self.assertEqual(base, base.replace())
2188
2189        i = 0
2190        for name, newval in (("year", 2),
2191                             ("month", 3),
2192                             ("day", 4),
2193                             ("hour", 5),
2194                             ("minute", 6),
2195                             ("second", 7),
2196                             ("microsecond", 8)):
2197            newargs = args[:]
2198            newargs[i] = newval
2199            expected = cls(*newargs)
2200            got = base.replace(**{name: newval})
2201            self.assertEqual(expected, got)
2202            i += 1
2203
2204        # Out of bounds.
2205        base = cls(2000, 2, 29)
2206        self.assertRaises(ValueError, base.replace, year=2001)
2207
2208    def test_astimezone(self):
2209        return  # The rest is no longer applicable
2210        # Pretty boring!  The TZ test is more interesting here.  astimezone()
2211        # simply can't be applied to a naive object.
2212        dt = self.theclass.now()
2213        f = FixedOffset(44, "")
2214        self.assertRaises(ValueError, dt.astimezone) # naive
2215        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2216        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2217        self.assertRaises(ValueError, dt.astimezone, f) # naive
2218        self.assertRaises(ValueError, dt.astimezone, tz=f)  # naive
2219
2220        class Bogus(tzinfo):
2221            def utcoffset(self, dt): return None
2222            def dst(self, dt): return timedelta(0)
2223        bog = Bogus()
2224        self.assertRaises(ValueError, dt.astimezone, bog)   # naive
2225        self.assertRaises(ValueError,
2226                          dt.replace(tzinfo=bog).astimezone, f)
2227
2228        class AlsoBogus(tzinfo):
2229            def utcoffset(self, dt): return timedelta(0)
2230            def dst(self, dt): return None
2231        alsobog = AlsoBogus()
2232        self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2233
2234    def test_subclass_datetime(self):
2235
2236        class C(self.theclass):
2237            theAnswer = 42
2238
2239            def __new__(cls, *args, **kws):
2240                temp = kws.copy()
2241                extra = temp.pop('extra')
2242                result = self.theclass.__new__(cls, *args, **temp)
2243                result.extra = extra
2244                return result
2245
2246            def newmeth(self, start):
2247                return start + self.year + self.month + self.second
2248
2249        args = 2003, 4, 14, 12, 13, 41
2250
2251        dt1 = self.theclass(*args)
2252        dt2 = C(*args, **{'extra': 7})
2253
2254        self.assertEqual(dt2.__class__, C)
2255        self.assertEqual(dt2.theAnswer, 42)
2256        self.assertEqual(dt2.extra, 7)
2257        self.assertEqual(dt1.toordinal(), dt2.toordinal())
2258        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2259                                          dt1.second - 7)
2260
2261class TestSubclassDateTime(TestDateTime):
2262    theclass = SubclassDatetime
2263    # Override tests not designed for subclass
2264    @unittest.skip('not appropriate for subclasses')
2265    def test_roundtrip(self):
2266        pass
2267
2268class SubclassTime(time):
2269    sub_var = 1
2270
2271class TestTime(HarmlessMixedComparison, unittest.TestCase):
2272
2273    theclass = time
2274
2275    def test_basic_attributes(self):
2276        t = self.theclass(12, 0)
2277        self.assertEqual(t.hour, 12)
2278        self.assertEqual(t.minute, 0)
2279        self.assertEqual(t.second, 0)
2280        self.assertEqual(t.microsecond, 0)
2281
2282    def test_basic_attributes_nonzero(self):
2283        # Make sure all attributes are non-zero so bugs in
2284        # bit-shifting access show up.
2285        t = self.theclass(12, 59, 59, 8000)
2286        self.assertEqual(t.hour, 12)
2287        self.assertEqual(t.minute, 59)
2288        self.assertEqual(t.second, 59)
2289        self.assertEqual(t.microsecond, 8000)
2290
2291    def test_roundtrip(self):
2292        t = self.theclass(1, 2, 3, 4)
2293
2294        # Verify t -> string -> time identity.
2295        s = repr(t)
2296        self.assertTrue(s.startswith('datetime.'))
2297        s = s[9:]
2298        t2 = eval(s)
2299        self.assertEqual(t, t2)
2300
2301        # Verify identity via reconstructing from pieces.
2302        t2 = self.theclass(t.hour, t.minute, t.second,
2303                           t.microsecond)
2304        self.assertEqual(t, t2)
2305
2306    def test_comparing(self):
2307        args = [1, 2, 3, 4]
2308        t1 = self.theclass(*args)
2309        t2 = self.theclass(*args)
2310        self.assertEqual(t1, t2)
2311        self.assertTrue(t1 <= t2)
2312        self.assertTrue(t1 >= t2)
2313        self.assertFalse(t1 != t2)
2314        self.assertFalse(t1 < t2)
2315        self.assertFalse(t1 > t2)
2316
2317        for i in range(len(args)):
2318            newargs = args[:]
2319            newargs[i] = args[i] + 1
2320            t2 = self.theclass(*newargs)   # this is larger than t1
2321            self.assertTrue(t1 < t2)
2322            self.assertTrue(t2 > t1)
2323            self.assertTrue(t1 <= t2)
2324            self.assertTrue(t2 >= t1)
2325            self.assertTrue(t1 != t2)
2326            self.assertTrue(t2 != t1)
2327            self.assertFalse(t1 == t2)
2328            self.assertFalse(t2 == t1)
2329            self.assertFalse(t1 > t2)
2330            self.assertFalse(t2 < t1)
2331            self.assertFalse(t1 >= t2)
2332            self.assertFalse(t2 <= t1)
2333
2334        for badarg in OTHERSTUFF:
2335            self.assertEqual(t1 == badarg, False)
2336            self.assertEqual(t1 != badarg, True)
2337            self.assertEqual(badarg == t1, False)
2338            self.assertEqual(badarg != t1, True)
2339
2340            self.assertRaises(TypeError, lambda: t1 <= badarg)
2341            self.assertRaises(TypeError, lambda: t1 < badarg)
2342            self.assertRaises(TypeError, lambda: t1 > badarg)
2343            self.assertRaises(TypeError, lambda: t1 >= badarg)
2344            self.assertRaises(TypeError, lambda: badarg <= t1)
2345            self.assertRaises(TypeError, lambda: badarg < t1)
2346            self.assertRaises(TypeError, lambda: badarg > t1)
2347            self.assertRaises(TypeError, lambda: badarg >= t1)
2348
2349    def test_bad_constructor_arguments(self):
2350        # bad hours
2351        self.theclass(0, 0)    # no exception
2352        self.theclass(23, 0)   # no exception
2353        self.assertRaises(ValueError, self.theclass, -1, 0)
2354        self.assertRaises(ValueError, self.theclass, 24, 0)
2355        # bad minutes
2356        self.theclass(23, 0)    # no exception
2357        self.theclass(23, 59)   # no exception
2358        self.assertRaises(ValueError, self.theclass, 23, -1)
2359        self.assertRaises(ValueError, self.theclass, 23, 60)
2360        # bad seconds
2361        self.theclass(23, 59, 0)    # no exception
2362        self.theclass(23, 59, 59)   # no exception
2363        self.assertRaises(ValueError, self.theclass, 23, 59, -1)
2364        self.assertRaises(ValueError, self.theclass, 23, 59, 60)
2365        # bad microseconds
2366        self.theclass(23, 59, 59, 0)        # no exception
2367        self.theclass(23, 59, 59, 999999)   # no exception
2368        self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
2369        self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
2370
2371    def test_hash_equality(self):
2372        d = self.theclass(23, 30, 17)
2373        e = self.theclass(23, 30, 17)
2374        self.assertEqual(d, e)
2375        self.assertEqual(hash(d), hash(e))
2376
2377        dic = {d: 1}
2378        dic[e] = 2
2379        self.assertEqual(len(dic), 1)
2380        self.assertEqual(dic[d], 2)
2381        self.assertEqual(dic[e], 2)
2382
2383        d = self.theclass(0,  5, 17)
2384        e = self.theclass(0,  5, 17)
2385        self.assertEqual(d, e)
2386        self.assertEqual(hash(d), hash(e))
2387
2388        dic = {d: 1}
2389        dic[e] = 2
2390        self.assertEqual(len(dic), 1)
2391        self.assertEqual(dic[d], 2)
2392        self.assertEqual(dic[e], 2)
2393
2394    def test_isoformat(self):
2395        t = self.theclass(4, 5, 1, 123)
2396        self.assertEqual(t.isoformat(), "04:05:01.000123")
2397        self.assertEqual(t.isoformat(), str(t))
2398
2399        t = self.theclass()
2400        self.assertEqual(t.isoformat(), "00:00:00")
2401        self.assertEqual(t.isoformat(), str(t))
2402
2403        t = self.theclass(microsecond=1)
2404        self.assertEqual(t.isoformat(), "00:00:00.000001")
2405        self.assertEqual(t.isoformat(), str(t))
2406
2407        t = self.theclass(microsecond=10)
2408        self.assertEqual(t.isoformat(), "00:00:00.000010")
2409        self.assertEqual(t.isoformat(), str(t))
2410
2411        t = self.theclass(microsecond=100)
2412        self.assertEqual(t.isoformat(), "00:00:00.000100")
2413        self.assertEqual(t.isoformat(), str(t))
2414
2415        t = self.theclass(microsecond=1000)
2416        self.assertEqual(t.isoformat(), "00:00:00.001000")
2417        self.assertEqual(t.isoformat(), str(t))
2418
2419        t = self.theclass(microsecond=10000)
2420        self.assertEqual(t.isoformat(), "00:00:00.010000")
2421        self.assertEqual(t.isoformat(), str(t))
2422
2423        t = self.theclass(microsecond=100000)
2424        self.assertEqual(t.isoformat(), "00:00:00.100000")
2425        self.assertEqual(t.isoformat(), str(t))
2426
2427        t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
2428        self.assertEqual(t.isoformat(timespec='hours'), "12")
2429        self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
2430        self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
2431        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
2432        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
2433        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
2434        self.assertRaises(ValueError, t.isoformat, timespec='monkey')
2435
2436        t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
2437        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
2438
2439        t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
2440        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
2441        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
2442        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
2443
2444    def test_1653736(self):
2445        # verify it doesn't accept extra keyword arguments
2446        t = self.theclass(second=1)
2447        self.assertRaises(TypeError, t.isoformat, foo=3)
2448
2449    def test_strftime(self):
2450        t = self.theclass(1, 2, 3, 4)
2451        self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
2452        # A naive object replaces %z and %Z with empty strings.
2453        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
2454
2455    def test_format(self):
2456        t = self.theclass(1, 2, 3, 4)
2457        self.assertEqual(t.__format__(''), str(t))
2458
2459        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
2460            t.__format__(123)
2461
2462        # check that a derived class's __str__() gets called
2463        class A(self.theclass):
2464            def __str__(self):
2465                return 'A'
2466        a = A(1, 2, 3, 4)
2467        self.assertEqual(a.__format__(''), 'A')
2468
2469        # check that a derived class's strftime gets called
2470        class B(self.theclass):
2471            def strftime(self, format_spec):
2472                return 'B'
2473        b = B(1, 2, 3, 4)
2474        self.assertEqual(b.__format__(''), str(t))
2475
2476        for fmt in ['%H %M %S',
2477                    ]:
2478            self.assertEqual(t.__format__(fmt), t.strftime(fmt))
2479            self.assertEqual(a.__format__(fmt), t.strftime(fmt))
2480            self.assertEqual(b.__format__(fmt), 'B')
2481
2482    def test_str(self):
2483        self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
2484        self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
2485        self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
2486        self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
2487        self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
2488
2489    def test_repr(self):
2490        name = 'datetime.' + self.theclass.__name__
2491        self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
2492                         "%s(1, 2, 3, 4)" % name)
2493        self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
2494                         "%s(10, 2, 3, 4000)" % name)
2495        self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
2496                         "%s(0, 2, 3, 400000)" % name)
2497        self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
2498                         "%s(12, 2, 3)" % name)
2499        self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
2500                         "%s(23, 15)" % name)
2501
2502    def test_resolution_info(self):
2503        self.assertIsInstance(self.theclass.min, self.theclass)
2504        self.assertIsInstance(self.theclass.max, self.theclass)
2505        self.assertIsInstance(self.theclass.resolution, timedelta)
2506        self.assertTrue(self.theclass.max > self.theclass.min)
2507
2508    def test_pickling(self):
2509        args = 20, 59, 16, 64**2
2510        orig = self.theclass(*args)
2511        for pickler, unpickler, proto in pickle_choices:
2512            green = pickler.dumps(orig, proto)
2513            derived = unpickler.loads(green)
2514            self.assertEqual(orig, derived)
2515        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
2516
2517    def test_pickling_subclass_time(self):
2518        args = 20, 59, 16, 64**2
2519        orig = SubclassTime(*args)
2520        for pickler, unpickler, proto in pickle_choices:
2521            green = pickler.dumps(orig, proto)
2522            derived = unpickler.loads(green)
2523            self.assertEqual(orig, derived)
2524
2525    def test_bool(self):
2526        # time is always True.
2527        cls = self.theclass
2528        self.assertTrue(cls(1))
2529        self.assertTrue(cls(0, 1))
2530        self.assertTrue(cls(0, 0, 1))
2531        self.assertTrue(cls(0, 0, 0, 1))
2532        self.assertTrue(cls(0))
2533        self.assertTrue(cls())
2534
2535    def test_replace(self):
2536        cls = self.theclass
2537        args = [1, 2, 3, 4]
2538        base = cls(*args)
2539        self.assertEqual(base, base.replace())
2540
2541        i = 0
2542        for name, newval in (("hour", 5),
2543                             ("minute", 6),
2544                             ("second", 7),
2545                             ("microsecond", 8)):
2546            newargs = args[:]
2547            newargs[i] = newval
2548            expected = cls(*newargs)
2549            got = base.replace(**{name: newval})
2550            self.assertEqual(expected, got)
2551            i += 1
2552
2553        # Out of bounds.
2554        base = cls(1)
2555        self.assertRaises(ValueError, base.replace, hour=24)
2556        self.assertRaises(ValueError, base.replace, minute=-1)
2557        self.assertRaises(ValueError, base.replace, second=100)
2558        self.assertRaises(ValueError, base.replace, microsecond=1000000)
2559
2560    def test_subclass_time(self):
2561
2562        class C(self.theclass):
2563            theAnswer = 42
2564
2565            def __new__(cls, *args, **kws):
2566                temp = kws.copy()
2567                extra = temp.pop('extra')
2568                result = self.theclass.__new__(cls, *args, **temp)
2569                result.extra = extra
2570                return result
2571
2572            def newmeth(self, start):
2573                return start + self.hour + self.second
2574
2575        args = 4, 5, 6
2576
2577        dt1 = self.theclass(*args)
2578        dt2 = C(*args, **{'extra': 7})
2579
2580        self.assertEqual(dt2.__class__, C)
2581        self.assertEqual(dt2.theAnswer, 42)
2582        self.assertEqual(dt2.extra, 7)
2583        self.assertEqual(dt1.isoformat(), dt2.isoformat())
2584        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
2585
2586    def test_backdoor_resistance(self):
2587        # see TestDate.test_backdoor_resistance().
2588        base = '2:59.0'
2589        for hour_byte in ' ', '9', chr(24), '\xff':
2590            self.assertRaises(TypeError, self.theclass,
2591                                         hour_byte + base[1:])
2592        # Good bytes, but bad tzinfo:
2593        with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
2594            self.theclass(bytes([1] * len(base)), 'EST')
2595
2596# A mixin for classes with a tzinfo= argument.  Subclasses must define
2597# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
2598# must be legit (which is true for time and datetime).
2599class TZInfoBase:
2600
2601    def test_argument_passing(self):
2602        cls = self.theclass
2603        # A datetime passes itself on, a time passes None.
2604        class introspective(tzinfo):
2605            def tzname(self, dt):    return dt and "real" or "none"
2606            def utcoffset(self, dt):
2607                return timedelta(minutes = dt and 42 or -42)
2608            dst = utcoffset
2609
2610        obj = cls(1, 2, 3, tzinfo=introspective())
2611
2612        expected = cls is time and "none" or "real"
2613        self.assertEqual(obj.tzname(), expected)
2614
2615        expected = timedelta(minutes=(cls is time and -42 or 42))
2616        self.assertEqual(obj.utcoffset(), expected)
2617        self.assertEqual(obj.dst(), expected)
2618
2619    def test_bad_tzinfo_classes(self):
2620        cls = self.theclass
2621        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
2622
2623        class NiceTry(object):
2624            def __init__(self): pass
2625            def utcoffset(self, dt): pass
2626        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
2627
2628        class BetterTry(tzinfo):
2629            def __init__(self): pass
2630            def utcoffset(self, dt): pass
2631        b = BetterTry()
2632        t = cls(1, 1, 1, tzinfo=b)
2633        self.assertIs(t.tzinfo, b)
2634
2635    def test_utc_offset_out_of_bounds(self):
2636        class Edgy(tzinfo):
2637            def __init__(self, offset):
2638                self.offset = timedelta(minutes=offset)
2639            def utcoffset(self, dt):
2640                return self.offset
2641
2642        cls = self.theclass
2643        for offset, legit in ((-1440, False),
2644                              (-1439, True),
2645                              (1439, True),
2646                              (1440, False)):
2647            if cls is time:
2648                t = cls(1, 2, 3, tzinfo=Edgy(offset))
2649            elif cls is datetime:
2650                t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
2651            else:
2652                assert 0, "impossible"
2653            if legit:
2654                aofs = abs(offset)
2655                h, m = divmod(aofs, 60)
2656                tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
2657                if isinstance(t, datetime):
2658                    t = t.timetz()
2659                self.assertEqual(str(t), "01:02:03" + tag)
2660            else:
2661                self.assertRaises(ValueError, str, t)
2662
2663    def test_tzinfo_classes(self):
2664        cls = self.theclass
2665        class C1(tzinfo):
2666            def utcoffset(self, dt): return None
2667            def dst(self, dt): return None
2668            def tzname(self, dt): return None
2669        for t in (cls(1, 1, 1),
2670                  cls(1, 1, 1, tzinfo=None),
2671                  cls(1, 1, 1, tzinfo=C1())):
2672            self.assertIsNone(t.utcoffset())
2673            self.assertIsNone(t.dst())
2674            self.assertIsNone(t.tzname())
2675
2676        class C3(tzinfo):
2677            def utcoffset(self, dt): return timedelta(minutes=-1439)
2678            def dst(self, dt): return timedelta(minutes=1439)
2679            def tzname(self, dt): return "aname"
2680        t = cls(1, 1, 1, tzinfo=C3())
2681        self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
2682        self.assertEqual(t.dst(), timedelta(minutes=1439))
2683        self.assertEqual(t.tzname(), "aname")
2684
2685        # Wrong types.
2686        class C4(tzinfo):
2687            def utcoffset(self, dt): return "aname"
2688            def dst(self, dt): return 7
2689            def tzname(self, dt): return 0
2690        t = cls(1, 1, 1, tzinfo=C4())
2691        self.assertRaises(TypeError, t.utcoffset)
2692        self.assertRaises(TypeError, t.dst)
2693        self.assertRaises(TypeError, t.tzname)
2694
2695        # Offset out of range.
2696        class C6(tzinfo):
2697            def utcoffset(self, dt): return timedelta(hours=-24)
2698            def dst(self, dt): return timedelta(hours=24)
2699        t = cls(1, 1, 1, tzinfo=C6())
2700        self.assertRaises(ValueError, t.utcoffset)
2701        self.assertRaises(ValueError, t.dst)
2702
2703        # Not a whole number of seconds.
2704        class C7(tzinfo):
2705            def utcoffset(self, dt): return timedelta(microseconds=61)
2706            def dst(self, dt): return timedelta(microseconds=-81)
2707        t = cls(1, 1, 1, tzinfo=C7())
2708        self.assertRaises(ValueError, t.utcoffset)
2709        self.assertRaises(ValueError, t.dst)
2710
2711    def test_aware_compare(self):
2712        cls = self.theclass
2713
2714        # Ensure that utcoffset() gets ignored if the comparands have
2715        # the same tzinfo member.
2716        class OperandDependentOffset(tzinfo):
2717            def utcoffset(self, t):
2718                if t.minute < 10:
2719                    # d0 and d1 equal after adjustment
2720                    return timedelta(minutes=t.minute)
2721                else:
2722                    # d2 off in the weeds
2723                    return timedelta(minutes=59)
2724
2725        base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
2726        d0 = base.replace(minute=3)
2727        d1 = base.replace(minute=9)
2728        d2 = base.replace(minute=11)
2729        for x in d0, d1, d2:
2730            for y in d0, d1, d2:
2731                for op in lt, le, gt, ge, eq, ne:
2732                    got = op(x, y)
2733                    expected = op(x.minute, y.minute)
2734                    self.assertEqual(got, expected)
2735
2736        # However, if they're different members, uctoffset is not ignored.
2737        # Note that a time can't actually have an operand-depedent offset,
2738        # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
2739        # so skip this test for time.
2740        if cls is not time:
2741            d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
2742            d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
2743            d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
2744            for x in d0, d1, d2:
2745                for y in d0, d1, d2:
2746                    got = (x > y) - (x < y)
2747                    if (x is d0 or x is d1) and (y is d0 or y is d1):
2748                        expected = 0
2749                    elif x is y is d2:
2750                        expected = 0
2751                    elif x is d2:
2752                        expected = -1
2753                    else:
2754                        assert y is d2
2755                        expected = 1
2756                    self.assertEqual(got, expected)
2757
2758
2759# Testing time objects with a non-None tzinfo.
2760class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
2761    theclass = time
2762
2763    def test_empty(self):
2764        t = self.theclass()
2765        self.assertEqual(t.hour, 0)
2766        self.assertEqual(t.minute, 0)
2767        self.assertEqual(t.second, 0)
2768        self.assertEqual(t.microsecond, 0)
2769        self.assertIsNone(t.tzinfo)
2770
2771    def test_zones(self):
2772        est = FixedOffset(-300, "EST", 1)
2773        utc = FixedOffset(0, "UTC", -2)
2774        met = FixedOffset(60, "MET", 3)
2775        t1 = time( 7, 47, tzinfo=est)
2776        t2 = time(12, 47, tzinfo=utc)
2777        t3 = time(13, 47, tzinfo=met)
2778        t4 = time(microsecond=40)
2779        t5 = time(microsecond=40, tzinfo=utc)
2780
2781        self.assertEqual(t1.tzinfo, est)
2782        self.assertEqual(t2.tzinfo, utc)
2783        self.assertEqual(t3.tzinfo, met)
2784        self.assertIsNone(t4.tzinfo)
2785        self.assertEqual(t5.tzinfo, utc)
2786
2787        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
2788        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
2789        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
2790        self.assertIsNone(t4.utcoffset())
2791        self.assertRaises(TypeError, t1.utcoffset, "no args")
2792
2793        self.assertEqual(t1.tzname(), "EST")
2794        self.assertEqual(t2.tzname(), "UTC")
2795        self.assertEqual(t3.tzname(), "MET")
2796        self.assertIsNone(t4.tzname())
2797        self.assertRaises(TypeError, t1.tzname, "no args")
2798
2799        self.assertEqual(t1.dst(), timedelta(minutes=1))
2800        self.assertEqual(t2.dst(), timedelta(minutes=-2))
2801        self.assertEqual(t3.dst(), timedelta(minutes=3))
2802        self.assertIsNone(t4.dst())
2803        self.assertRaises(TypeError, t1.dst, "no args")
2804
2805        self.assertEqual(hash(t1), hash(t2))
2806        self.assertEqual(hash(t1), hash(t3))
2807        self.assertEqual(hash(t2), hash(t3))
2808
2809        self.assertEqual(t1, t2)
2810        self.assertEqual(t1, t3)
2811        self.assertEqual(t2, t3)
2812        self.assertNotEqual(t4, t5) # mixed tz-aware & naive
2813        self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
2814        self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
2815
2816        self.assertEqual(str(t1), "07:47:00-05:00")
2817        self.assertEqual(str(t2), "12:47:00+00:00")
2818        self.assertEqual(str(t3), "13:47:00+01:00")
2819        self.assertEqual(str(t4), "00:00:00.000040")
2820        self.assertEqual(str(t5), "00:00:00.000040+00:00")
2821
2822        self.assertEqual(t1.isoformat(), "07:47:00-05:00")
2823        self.assertEqual(t2.isoformat(), "12:47:00+00:00")
2824        self.assertEqual(t3.isoformat(), "13:47:00+01:00")
2825        self.assertEqual(t4.isoformat(), "00:00:00.000040")
2826        self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
2827
2828        d = 'datetime.time'
2829        self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
2830        self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
2831        self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
2832        self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
2833        self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
2834
2835        self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
2836                                     "07:47:00 %Z=EST %z=-0500")
2837        self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
2838        self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
2839
2840        yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
2841        t1 = time(23, 59, tzinfo=yuck)
2842        self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
2843                                     "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
2844
2845        # Check that an invalid tzname result raises an exception.
2846        class Badtzname(tzinfo):
2847            tz = 42
2848            def tzname(self, dt): return self.tz
2849        t = time(2, 3, 4, tzinfo=Badtzname())
2850        self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
2851        self.assertRaises(TypeError, t.strftime, "%Z")
2852
2853        # Issue #6697:
2854        if '_Fast' in str(self):
2855            Badtzname.tz = '\ud800'
2856            self.assertRaises(ValueError, t.strftime, "%Z")
2857
2858    def test_hash_edge_cases(self):
2859        # Offsets that overflow a basic time.
2860        t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
2861        t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
2862        self.assertEqual(hash(t1), hash(t2))
2863
2864        t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
2865        t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
2866        self.assertEqual(hash(t1), hash(t2))
2867
2868    def test_pickling(self):
2869        # Try one without a tzinfo.
2870        args = 20, 59, 16, 64**2
2871        orig = self.theclass(*args)
2872        for pickler, unpickler, proto in pickle_choices:
2873            green = pickler.dumps(orig, proto)
2874            derived = unpickler.loads(green)
2875            self.assertEqual(orig, derived)
2876        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
2877
2878        # Try one with a tzinfo.
2879        tinfo = PicklableFixedOffset(-300, 'cookie')
2880        orig = self.theclass(5, 6, 7, tzinfo=tinfo)
2881        for pickler, unpickler, proto in pickle_choices:
2882            green = pickler.dumps(orig, proto)
2883            derived = unpickler.loads(green)
2884            self.assertEqual(orig, derived)
2885            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
2886            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
2887            self.assertEqual(derived.tzname(), 'cookie')
2888        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
2889
2890    def test_more_bool(self):
2891        # time is always True.
2892        cls = self.theclass
2893
2894        t = cls(0, tzinfo=FixedOffset(-300, ""))
2895        self.assertTrue(t)
2896
2897        t = cls(5, tzinfo=FixedOffset(-300, ""))
2898        self.assertTrue(t)
2899
2900        t = cls(5, tzinfo=FixedOffset(300, ""))
2901        self.assertTrue(t)
2902
2903        t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
2904        self.assertTrue(t)
2905
2906    def test_replace(self):
2907        cls = self.theclass
2908        z100 = FixedOffset(100, "+100")
2909        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
2910        args = [1, 2, 3, 4, z100]
2911        base = cls(*args)
2912        self.assertEqual(base, base.replace())
2913
2914        i = 0
2915        for name, newval in (("hour", 5),
2916                             ("minute", 6),
2917                             ("second", 7),
2918                             ("microsecond", 8),
2919                             ("tzinfo", zm200)):
2920            newargs = args[:]
2921            newargs[i] = newval
2922            expected = cls(*newargs)
2923            got = base.replace(**{name: newval})
2924            self.assertEqual(expected, got)
2925            i += 1
2926
2927        # Ensure we can get rid of a tzinfo.
2928        self.assertEqual(base.tzname(), "+100")
2929        base2 = base.replace(tzinfo=None)
2930        self.assertIsNone(base2.tzinfo)
2931        self.assertIsNone(base2.tzname())
2932
2933        # Ensure we can add one.
2934        base3 = base2.replace(tzinfo=z100)
2935        self.assertEqual(base, base3)
2936        self.assertIs(base.tzinfo, base3.tzinfo)
2937
2938        # Out of bounds.
2939        base = cls(1)
2940        self.assertRaises(ValueError, base.replace, hour=24)
2941        self.assertRaises(ValueError, base.replace, minute=-1)
2942        self.assertRaises(ValueError, base.replace, second=100)
2943        self.assertRaises(ValueError, base.replace, microsecond=1000000)
2944
2945    def test_mixed_compare(self):
2946        t1 = time(1, 2, 3)
2947        t2 = time(1, 2, 3)
2948        self.assertEqual(t1, t2)
2949        t2 = t2.replace(tzinfo=None)
2950        self.assertEqual(t1, t2)
2951        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
2952        self.assertEqual(t1, t2)
2953        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
2954        self.assertNotEqual(t1, t2)
2955
2956        # In time w/ identical tzinfo objects, utcoffset is ignored.
2957        class Varies(tzinfo):
2958            def __init__(self):
2959                self.offset = timedelta(minutes=22)
2960            def utcoffset(self, t):
2961                self.offset += timedelta(minutes=1)
2962                return self.offset
2963
2964        v = Varies()
2965        t1 = t2.replace(tzinfo=v)
2966        t2 = t2.replace(tzinfo=v)
2967        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
2968        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
2969        self.assertEqual(t1, t2)
2970
2971        # But if they're not identical, it isn't ignored.
2972        t2 = t2.replace(tzinfo=Varies())
2973        self.assertTrue(t1 < t2)  # t1's offset counter still going up
2974
2975    def test_subclass_timetz(self):
2976
2977        class C(self.theclass):
2978            theAnswer = 42
2979
2980            def __new__(cls, *args, **kws):
2981                temp = kws.copy()
2982                extra = temp.pop('extra')
2983                result = self.theclass.__new__(cls, *args, **temp)
2984                result.extra = extra
2985                return result
2986
2987            def newmeth(self, start):
2988                return start + self.hour + self.second
2989
2990        args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
2991
2992        dt1 = self.theclass(*args)
2993        dt2 = C(*args, **{'extra': 7})
2994
2995        self.assertEqual(dt2.__class__, C)
2996        self.assertEqual(dt2.theAnswer, 42)
2997        self.assertEqual(dt2.extra, 7)
2998        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
2999        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3000
3001
3002# Testing datetime objects with a non-None tzinfo.
3003
3004class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
3005    theclass = datetime
3006
3007    def test_trivial(self):
3008        dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
3009        self.assertEqual(dt.year, 1)
3010        self.assertEqual(dt.month, 2)
3011        self.assertEqual(dt.day, 3)
3012        self.assertEqual(dt.hour, 4)
3013        self.assertEqual(dt.minute, 5)
3014        self.assertEqual(dt.second, 6)
3015        self.assertEqual(dt.microsecond, 7)
3016        self.assertEqual(dt.tzinfo, None)
3017
3018    def test_even_more_compare(self):
3019        # The test_compare() and test_more_compare() inherited from TestDate
3020        # and TestDateTime covered non-tzinfo cases.
3021
3022        # Smallest possible after UTC adjustment.
3023        t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3024        # Largest possible after UTC adjustment.
3025        t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3026                           tzinfo=FixedOffset(-1439, ""))
3027
3028        # Make sure those compare correctly, and w/o overflow.
3029        self.assertTrue(t1 < t2)
3030        self.assertTrue(t1 != t2)
3031        self.assertTrue(t2 > t1)
3032
3033        self.assertEqual(t1, t1)
3034        self.assertEqual(t2, t2)
3035
3036        # Equal afer adjustment.
3037        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
3038        t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
3039        self.assertEqual(t1, t2)
3040
3041        # Change t1 not to subtract a minute, and t1 should be larger.
3042        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
3043        self.assertTrue(t1 > t2)
3044
3045        # Change t1 to subtract 2 minutes, and t1 should be smaller.
3046        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
3047        self.assertTrue(t1 < t2)
3048
3049        # Back to the original t1, but make seconds resolve it.
3050        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3051                           second=1)
3052        self.assertTrue(t1 > t2)
3053
3054        # Likewise, but make microseconds resolve it.
3055        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
3056                           microsecond=1)
3057        self.assertTrue(t1 > t2)
3058
3059        # Make t2 naive and it should differ.
3060        t2 = self.theclass.min
3061        self.assertNotEqual(t1, t2)
3062        self.assertEqual(t2, t2)
3063
3064        # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
3065        class Naive(tzinfo):
3066            def utcoffset(self, dt): return None
3067        t2 = self.theclass(5, 6, 7, tzinfo=Naive())
3068        self.assertNotEqual(t1, t2)
3069        self.assertEqual(t2, t2)
3070
3071        # OTOH, it's OK to compare two of these mixing the two ways of being
3072        # naive.
3073        t1 = self.theclass(5, 6, 7)
3074        self.assertEqual(t1, t2)
3075
3076        # Try a bogus uctoffset.
3077        class Bogus(tzinfo):
3078            def utcoffset(self, dt):
3079                return timedelta(minutes=1440) # out of bounds
3080        t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
3081        t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
3082        self.assertRaises(ValueError, lambda: t1 == t2)
3083
3084    def test_pickling(self):
3085        # Try one without a tzinfo.
3086        args = 6, 7, 23, 20, 59, 1, 64**2
3087        orig = self.theclass(*args)
3088        for pickler, unpickler, proto in pickle_choices:
3089            green = pickler.dumps(orig, proto)
3090            derived = unpickler.loads(green)
3091            self.assertEqual(orig, derived)
3092        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3093
3094        # Try one with a tzinfo.
3095        tinfo = PicklableFixedOffset(-300, 'cookie')
3096        orig = self.theclass(*args, **{'tzinfo': tinfo})
3097        derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
3098        for pickler, unpickler, proto in pickle_choices:
3099            green = pickler.dumps(orig, proto)
3100            derived = unpickler.loads(green)
3101            self.assertEqual(orig, derived)
3102            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3103            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3104            self.assertEqual(derived.tzname(), 'cookie')
3105        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3106
3107    def test_extreme_hashes(self):
3108        # If an attempt is made to hash these via subtracting the offset
3109        # then hashing a datetime object, OverflowError results.  The
3110        # Python implementation used to blow up here.
3111        t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
3112        hash(t)
3113        t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3114                          tzinfo=FixedOffset(-1439, ""))
3115        hash(t)
3116
3117        # OTOH, an OOB offset should blow up.
3118        t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
3119        self.assertRaises(ValueError, hash, t)
3120
3121    def test_zones(self):
3122        est = FixedOffset(-300, "EST")
3123        utc = FixedOffset(0, "UTC")
3124        met = FixedOffset(60, "MET")
3125        t1 = datetime(2002, 3, 19,  7, 47, tzinfo=est)
3126        t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
3127        t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
3128        self.assertEqual(t1.tzinfo, est)
3129        self.assertEqual(t2.tzinfo, utc)
3130        self.assertEqual(t3.tzinfo, met)
3131        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3132        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3133        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3134        self.assertEqual(t1.tzname(), "EST")
3135        self.assertEqual(t2.tzname(), "UTC")
3136        self.assertEqual(t3.tzname(), "MET")
3137        self.assertEqual(hash(t1), hash(t2))
3138        self.assertEqual(hash(t1), hash(t3))
3139        self.assertEqual(hash(t2), hash(t3))
3140        self.assertEqual(t1, t2)
3141        self.assertEqual(t1, t3)
3142        self.assertEqual(t2, t3)
3143        self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
3144        self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
3145        self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
3146        d = 'datetime.datetime(2002, 3, 19, '
3147        self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
3148        self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
3149        self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
3150
3151    def test_combine(self):
3152        met = FixedOffset(60, "MET")
3153        d = date(2002, 3, 4)
3154        tz = time(18, 45, 3, 1234, tzinfo=met)
3155        dt = datetime.combine(d, tz)
3156        self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
3157                                        tzinfo=met))
3158
3159    def test_extract(self):
3160        met = FixedOffset(60, "MET")
3161        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
3162        self.assertEqual(dt.date(), date(2002, 3, 4))
3163        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
3164        self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
3165
3166    def test_tz_aware_arithmetic(self):
3167        import random
3168
3169        now = self.theclass.now()
3170        tz55 = FixedOffset(-330, "west 5:30")
3171        timeaware = now.time().replace(tzinfo=tz55)
3172        nowaware = self.theclass.combine(now.date(), timeaware)
3173        self.assertIs(nowaware.tzinfo, tz55)
3174        self.assertEqual(nowaware.timetz(), timeaware)
3175
3176        # Can't mix aware and non-aware.
3177        self.assertRaises(TypeError, lambda: now - nowaware)
3178        self.assertRaises(TypeError, lambda: nowaware - now)
3179
3180        # And adding datetime's doesn't make sense, aware or not.
3181        self.assertRaises(TypeError, lambda: now + nowaware)
3182        self.assertRaises(TypeError, lambda: nowaware + now)
3183        self.assertRaises(TypeError, lambda: nowaware + nowaware)
3184
3185        # Subtracting should yield 0.
3186        self.assertEqual(now - now, timedelta(0))
3187        self.assertEqual(nowaware - nowaware, timedelta(0))
3188
3189        # Adding a delta should preserve tzinfo.
3190        delta = timedelta(weeks=1, minutes=12, microseconds=5678)
3191        nowawareplus = nowaware + delta
3192        self.assertIs(nowaware.tzinfo, tz55)
3193        nowawareplus2 = delta + nowaware
3194        self.assertIs(nowawareplus2.tzinfo, tz55)
3195        self.assertEqual(nowawareplus, nowawareplus2)
3196
3197        # that - delta should be what we started with, and that - what we
3198        # started with should be delta.
3199        diff = nowawareplus - delta
3200        self.assertIs(diff.tzinfo, tz55)
3201        self.assertEqual(nowaware, diff)
3202        self.assertRaises(TypeError, lambda: delta - nowawareplus)
3203        self.assertEqual(nowawareplus - nowaware, delta)
3204
3205        # Make up a random timezone.
3206        tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
3207        # Attach it to nowawareplus.
3208        nowawareplus = nowawareplus.replace(tzinfo=tzr)
3209        self.assertIs(nowawareplus.tzinfo, tzr)
3210        # Make sure the difference takes the timezone adjustments into account.
3211        got = nowaware - nowawareplus
3212        # Expected:  (nowaware base - nowaware offset) -
3213        #            (nowawareplus base - nowawareplus offset) =
3214        #            (nowaware base - nowawareplus base) +
3215        #            (nowawareplus offset - nowaware offset) =
3216        #            -delta + nowawareplus offset - nowaware offset
3217        expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
3218        self.assertEqual(got, expected)
3219
3220        # Try max possible difference.
3221        min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
3222        max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
3223                            tzinfo=FixedOffset(-1439, "max"))
3224        maxdiff = max - min
3225        self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
3226                                  timedelta(minutes=2*1439))
3227        # Different tzinfo, but the same offset
3228        tza = timezone(HOUR, 'A')
3229        tzb = timezone(HOUR, 'B')
3230        delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
3231        self.assertEqual(delta, self.theclass.min - self.theclass.max)
3232
3233    def test_tzinfo_now(self):
3234        meth = self.theclass.now
3235        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3236        base = meth()
3237        # Try with and without naming the keyword.
3238        off42 = FixedOffset(42, "42")
3239        another = meth(off42)
3240        again = meth(tz=off42)
3241        self.assertIs(another.tzinfo, again.tzinfo)
3242        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3243        # Bad argument with and w/o naming the keyword.
3244        self.assertRaises(TypeError, meth, 16)
3245        self.assertRaises(TypeError, meth, tzinfo=16)
3246        # Bad keyword name.
3247        self.assertRaises(TypeError, meth, tinfo=off42)
3248        # Too many args.
3249        self.assertRaises(TypeError, meth, off42, off42)
3250
3251        # We don't know which time zone we're in, and don't have a tzinfo
3252        # class to represent it, so seeing whether a tz argument actually
3253        # does a conversion is tricky.
3254        utc = FixedOffset(0, "utc", 0)
3255        for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
3256                        timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
3257            for dummy in range(3):
3258                now = datetime.now(weirdtz)
3259                self.assertIs(now.tzinfo, weirdtz)
3260                utcnow = datetime.utcnow().replace(tzinfo=utc)
3261                now2 = utcnow.astimezone(weirdtz)
3262                if abs(now - now2) < timedelta(seconds=30):
3263                    break
3264                # Else the code is broken, or more than 30 seconds passed between
3265                # calls; assuming the latter, just try again.
3266            else:
3267                # Three strikes and we're out.
3268                self.fail("utcnow(), now(tz), or astimezone() may be broken")
3269
3270    def test_tzinfo_fromtimestamp(self):
3271        import time
3272        meth = self.theclass.fromtimestamp
3273        ts = time.time()
3274        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3275        base = meth(ts)
3276        # Try with and without naming the keyword.
3277        off42 = FixedOffset(42, "42")
3278        another = meth(ts, off42)
3279        again = meth(ts, tz=off42)
3280        self.assertIs(another.tzinfo, again.tzinfo)
3281        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
3282        # Bad argument with and w/o naming the keyword.
3283        self.assertRaises(TypeError, meth, ts, 16)
3284        self.assertRaises(TypeError, meth, ts, tzinfo=16)
3285        # Bad keyword name.
3286        self.assertRaises(TypeError, meth, ts, tinfo=off42)
3287        # Too many args.
3288        self.assertRaises(TypeError, meth, ts, off42, off42)
3289        # Too few args.
3290        self.assertRaises(TypeError, meth)
3291
3292        # Try to make sure tz= actually does some conversion.
3293        timestamp = 1000000000
3294        utcdatetime = datetime.utcfromtimestamp(timestamp)
3295        # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
3296        # But on some flavor of Mac, it's nowhere near that.  So we can't have
3297        # any idea here what time that actually is, we can only test that
3298        # relative changes match.
3299        utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
3300        tz = FixedOffset(utcoffset, "tz", 0)
3301        expected = utcdatetime + utcoffset
3302        got = datetime.fromtimestamp(timestamp, tz)
3303        self.assertEqual(expected, got.replace(tzinfo=None))
3304
3305    def test_tzinfo_utcnow(self):
3306        meth = self.theclass.utcnow
3307        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3308        base = meth()
3309        # Try with and without naming the keyword; for whatever reason,
3310        # utcnow() doesn't accept a tzinfo argument.
3311        off42 = FixedOffset(42, "42")
3312        self.assertRaises(TypeError, meth, off42)
3313        self.assertRaises(TypeError, meth, tzinfo=off42)
3314
3315    def test_tzinfo_utcfromtimestamp(self):
3316        import time
3317        meth = self.theclass.utcfromtimestamp
3318        ts = time.time()
3319        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
3320        base = meth(ts)
3321        # Try with and without naming the keyword; for whatever reason,
3322        # utcfromtimestamp() doesn't accept a tzinfo argument.
3323        off42 = FixedOffset(42, "42")
3324        self.assertRaises(TypeError, meth, ts, off42)
3325        self.assertRaises(TypeError, meth, ts, tzinfo=off42)
3326
3327    def test_tzinfo_timetuple(self):
3328        # TestDateTime tested most of this.  datetime adds a twist to the
3329        # DST flag.
3330        class DST(tzinfo):
3331            def __init__(self, dstvalue):
3332                if isinstance(dstvalue, int):
3333                    dstvalue = timedelta(minutes=dstvalue)
3334                self.dstvalue = dstvalue
3335            def dst(self, dt):
3336                return self.dstvalue
3337
3338        cls = self.theclass
3339        for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
3340            d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
3341            t = d.timetuple()
3342            self.assertEqual(1, t.tm_year)
3343            self.assertEqual(1, t.tm_mon)
3344            self.assertEqual(1, t.tm_mday)
3345            self.assertEqual(10, t.tm_hour)
3346            self.assertEqual(20, t.tm_min)
3347            self.assertEqual(30, t.tm_sec)
3348            self.assertEqual(0, t.tm_wday)
3349            self.assertEqual(1, t.tm_yday)
3350            self.assertEqual(flag, t.tm_isdst)
3351
3352        # dst() returns wrong type.
3353        self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
3354
3355        # dst() at the edge.
3356        self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
3357        self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
3358
3359        # dst() out of range.
3360        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
3361        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
3362
3363    def test_utctimetuple(self):
3364        class DST(tzinfo):
3365            def __init__(self, dstvalue=0):
3366                if isinstance(dstvalue, int):
3367                    dstvalue = timedelta(minutes=dstvalue)
3368                self.dstvalue = dstvalue
3369            def dst(self, dt):
3370                return self.dstvalue
3371
3372        cls = self.theclass
3373        # This can't work:  DST didn't implement utcoffset.
3374        self.assertRaises(NotImplementedError,
3375                          cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
3376
3377        class UOFS(DST):
3378            def __init__(self, uofs, dofs=None):
3379                DST.__init__(self, dofs)
3380                self.uofs = timedelta(minutes=uofs)
3381            def utcoffset(self, dt):
3382                return self.uofs
3383
3384        for dstvalue in -33, 33, 0, None:
3385            d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
3386            t = d.utctimetuple()
3387            self.assertEqual(d.year, t.tm_year)
3388            self.assertEqual(d.month, t.tm_mon)
3389            self.assertEqual(d.day, t.tm_mday)
3390            self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
3391            self.assertEqual(13, t.tm_min)
3392            self.assertEqual(d.second, t.tm_sec)
3393            self.assertEqual(d.weekday(), t.tm_wday)
3394            self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
3395                             t.tm_yday)
3396            # Ensure tm_isdst is 0 regardless of what dst() says: DST
3397            # is never in effect for a UTC time.
3398            self.assertEqual(0, t.tm_isdst)
3399
3400        # For naive datetime, utctimetuple == timetuple except for isdst
3401        d = cls(1, 2, 3, 10, 20, 30, 40)
3402        t = d.utctimetuple()
3403        self.assertEqual(t[:-1], d.timetuple()[:-1])
3404        self.assertEqual(0, t.tm_isdst)
3405        # Same if utcoffset is None
3406        class NOFS(DST):
3407            def utcoffset(self, dt):
3408                return None
3409        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
3410        t = d.utctimetuple()
3411        self.assertEqual(t[:-1], d.timetuple()[:-1])
3412        self.assertEqual(0, t.tm_isdst)
3413        # Check that bad tzinfo is detected
3414        class BOFS(DST):
3415            def utcoffset(self, dt):
3416                return "EST"
3417        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
3418        self.assertRaises(TypeError, d.utctimetuple)
3419
3420        # Check that utctimetuple() is the same as
3421        # astimezone(utc).timetuple()
3422        d = cls(2010, 11, 13, 14, 15, 16, 171819)
3423        for tz in [timezone.min, timezone.utc, timezone.max]:
3424            dtz = d.replace(tzinfo=tz)
3425            self.assertEqual(dtz.utctimetuple()[:-1],
3426                             dtz.astimezone(timezone.utc).timetuple()[:-1])
3427        # At the edges, UTC adjustment can produce years out-of-range
3428        # for a datetime object.  Ensure that an OverflowError is
3429        # raised.
3430        tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
3431        # That goes back 1 minute less than a full day.
3432        self.assertRaises(OverflowError, tiny.utctimetuple)
3433
3434        huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
3435        # That goes forward 1 minute less than a full day.
3436        self.assertRaises(OverflowError, huge.utctimetuple)
3437        # More overflow cases
3438        tiny = cls.min.replace(tzinfo=timezone(MINUTE))
3439        self.assertRaises(OverflowError, tiny.utctimetuple)
3440        huge = cls.max.replace(tzinfo=timezone(-MINUTE))
3441        self.assertRaises(OverflowError, huge.utctimetuple)
3442
3443    def test_tzinfo_isoformat(self):
3444        zero = FixedOffset(0, "+00:00")
3445        plus = FixedOffset(220, "+03:40")
3446        minus = FixedOffset(-231, "-03:51")
3447        unknown = FixedOffset(None, "")
3448
3449        cls = self.theclass
3450        datestr = '0001-02-03'
3451        for ofs in None, zero, plus, minus, unknown:
3452            for us in 0, 987001:
3453                d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
3454                timestr = '04:05:59' + (us and '.987001' or '')
3455                ofsstr = ofs is not None and d.tzname() or ''
3456                tailstr = timestr + ofsstr
3457                iso = d.isoformat()
3458                self.assertEqual(iso, datestr + 'T' + tailstr)
3459                self.assertEqual(iso, d.isoformat('T'))
3460                self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
3461                self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
3462                self.assertEqual(str(d), datestr + ' ' + tailstr)
3463
3464    def test_replace(self):
3465        cls = self.theclass
3466        z100 = FixedOffset(100, "+100")
3467        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3468        args = [1, 2, 3, 4, 5, 6, 7, z100]
3469        base = cls(*args)
3470        self.assertEqual(base, base.replace())
3471
3472        i = 0
3473        for name, newval in (("year", 2),
3474                             ("month", 3),
3475                             ("day", 4),
3476                             ("hour", 5),
3477                             ("minute", 6),
3478                             ("second", 7),
3479                             ("microsecond", 8),
3480                             ("tzinfo", zm200)):
3481            newargs = args[:]
3482            newargs[i] = newval
3483            expected = cls(*newargs)
3484            got = base.replace(**{name: newval})
3485            self.assertEqual(expected, got)
3486            i += 1
3487
3488        # Ensure we can get rid of a tzinfo.
3489        self.assertEqual(base.tzname(), "+100")
3490        base2 = base.replace(tzinfo=None)
3491        self.assertIsNone(base2.tzinfo)
3492        self.assertIsNone(base2.tzname())
3493
3494        # Ensure we can add one.
3495        base3 = base2.replace(tzinfo=z100)
3496        self.assertEqual(base, base3)
3497        self.assertIs(base.tzinfo, base3.tzinfo)
3498
3499        # Out of bounds.
3500        base = cls(2000, 2, 29)
3501        self.assertRaises(ValueError, base.replace, year=2001)
3502
3503    def test_more_astimezone(self):
3504        # The inherited test_astimezone covered some trivial and error cases.
3505        fnone = FixedOffset(None, "None")
3506        f44m = FixedOffset(44, "44")
3507        fm5h = FixedOffset(-timedelta(hours=5), "m300")
3508
3509        dt = self.theclass.now(tz=f44m)
3510        self.assertIs(dt.tzinfo, f44m)
3511        # Replacing with degenerate tzinfo raises an exception.
3512        self.assertRaises(ValueError, dt.astimezone, fnone)
3513        # Replacing with same tzinfo makes no change.
3514        x = dt.astimezone(dt.tzinfo)
3515        self.assertIs(x.tzinfo, f44m)
3516        self.assertEqual(x.date(), dt.date())
3517        self.assertEqual(x.time(), dt.time())
3518
3519        # Replacing with different tzinfo does adjust.
3520        got = dt.astimezone(fm5h)
3521        self.assertIs(got.tzinfo, fm5h)
3522        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
3523        expected = dt - dt.utcoffset()  # in effect, convert to UTC
3524        expected += fm5h.utcoffset(dt)  # and from there to local time
3525        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
3526        self.assertEqual(got.date(), expected.date())
3527        self.assertEqual(got.time(), expected.time())
3528        self.assertEqual(got.timetz(), expected.timetz())
3529        self.assertIs(got.tzinfo, expected.tzinfo)
3530        self.assertEqual(got, expected)
3531
3532    @support.run_with_tz('UTC')
3533    def test_astimezone_default_utc(self):
3534        dt = self.theclass.now(timezone.utc)
3535        self.assertEqual(dt.astimezone(None), dt)
3536        self.assertEqual(dt.astimezone(), dt)
3537
3538    # Note that offset in TZ variable has the opposite sign to that
3539    # produced by %z directive.
3540    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3541    def test_astimezone_default_eastern(self):
3542        dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
3543        local = dt.astimezone()
3544        self.assertEqual(dt, local)
3545        self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
3546        dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
3547        local = dt.astimezone()
3548        self.assertEqual(dt, local)
3549        self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
3550
3551    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
3552    def test_astimezone_default_near_fold(self):
3553        # Issue #26616.
3554        u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
3555        t = u.astimezone()
3556        s = t.astimezone()
3557        self.assertEqual(t.tzinfo, s.tzinfo)
3558
3559    def test_aware_subtract(self):
3560        cls = self.theclass
3561
3562        # Ensure that utcoffset() is ignored when the operands have the
3563        # same tzinfo member.
3564        class OperandDependentOffset(tzinfo):
3565            def utcoffset(self, t):
3566                if t.minute < 10:
3567                    # d0 and d1 equal after adjustment
3568                    return timedelta(minutes=t.minute)
3569                else:
3570                    # d2 off in the weeds
3571                    return timedelta(minutes=59)
3572
3573        base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
3574        d0 = base.replace(minute=3)
3575        d1 = base.replace(minute=9)
3576        d2 = base.replace(minute=11)
3577        for x in d0, d1, d2:
3578            for y in d0, d1, d2:
3579                got = x - y
3580                expected = timedelta(minutes=x.minute - y.minute)
3581                self.assertEqual(got, expected)
3582
3583        # OTOH, if the tzinfo members are distinct, utcoffsets aren't
3584        # ignored.
3585        base = cls(8, 9, 10, 11, 12, 13, 14)
3586        d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3587        d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3588        d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3589        for x in d0, d1, d2:
3590            for y in d0, d1, d2:
3591                got = x - y
3592                if (x is d0 or x is d1) and (y is d0 or y is d1):
3593                    expected = timedelta(0)
3594                elif x is y is d2:
3595                    expected = timedelta(0)
3596                elif x is d2:
3597                    expected = timedelta(minutes=(11-59)-0)
3598                else:
3599                    assert y is d2
3600                    expected = timedelta(minutes=0-(11-59))
3601                self.assertEqual(got, expected)
3602
3603    def test_mixed_compare(self):
3604        t1 = datetime(1, 2, 3, 4, 5, 6, 7)
3605        t2 = datetime(1, 2, 3, 4, 5, 6, 7)
3606        self.assertEqual(t1, t2)
3607        t2 = t2.replace(tzinfo=None)
3608        self.assertEqual(t1, t2)
3609        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3610        self.assertEqual(t1, t2)
3611        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3612        self.assertNotEqual(t1, t2)
3613
3614        # In datetime w/ identical tzinfo objects, utcoffset is ignored.
3615        class Varies(tzinfo):
3616            def __init__(self):
3617                self.offset = timedelta(minutes=22)
3618            def utcoffset(self, t):
3619                self.offset += timedelta(minutes=1)
3620                return self.offset
3621
3622        v = Varies()
3623        t1 = t2.replace(tzinfo=v)
3624        t2 = t2.replace(tzinfo=v)
3625        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3626        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3627        self.assertEqual(t1, t2)
3628
3629        # But if they're not identical, it isn't ignored.
3630        t2 = t2.replace(tzinfo=Varies())
3631        self.assertTrue(t1 < t2)  # t1's offset counter still going up
3632
3633    def test_subclass_datetimetz(self):
3634
3635        class C(self.theclass):
3636            theAnswer = 42
3637
3638            def __new__(cls, *args, **kws):
3639                temp = kws.copy()
3640                extra = temp.pop('extra')
3641                result = self.theclass.__new__(cls, *args, **temp)
3642                result.extra = extra
3643                return result
3644
3645            def newmeth(self, start):
3646                return start + self.hour + self.year
3647
3648        args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3649
3650        dt1 = self.theclass(*args)
3651        dt2 = C(*args, **{'extra': 7})
3652
3653        self.assertEqual(dt2.__class__, C)
3654        self.assertEqual(dt2.theAnswer, 42)
3655        self.assertEqual(dt2.extra, 7)
3656        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3657        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
3658
3659# Pain to set up DST-aware tzinfo classes.
3660
3661def first_sunday_on_or_after(dt):
3662    days_to_go = 6 - dt.weekday()
3663    if days_to_go:
3664        dt += timedelta(days_to_go)
3665    return dt
3666
3667ZERO = timedelta(0)
3668MINUTE = timedelta(minutes=1)
3669HOUR = timedelta(hours=1)
3670DAY = timedelta(days=1)
3671# In the US, DST starts at 2am (standard time) on the first Sunday in April.
3672DSTSTART = datetime(1, 4, 1, 2)
3673# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
3674# which is the first Sunday on or after Oct 25.  Because we view 1:MM as
3675# being standard time on that day, there is no spelling in local time of
3676# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
3677DSTEND = datetime(1, 10, 25, 1)
3678
3679class USTimeZone(tzinfo):
3680
3681    def __init__(self, hours, reprname, stdname, dstname):
3682        self.stdoffset = timedelta(hours=hours)
3683        self.reprname = reprname
3684        self.stdname = stdname
3685        self.dstname = dstname
3686
3687    def __repr__(self):
3688        return self.reprname
3689
3690    def tzname(self, dt):
3691        if self.dst(dt):
3692            return self.dstname
3693        else:
3694            return self.stdname
3695
3696    def utcoffset(self, dt):
3697        return self.stdoffset + self.dst(dt)
3698
3699    def dst(self, dt):
3700        if dt is None or dt.tzinfo is None:
3701            # An exception instead may be sensible here, in one or more of
3702            # the cases.
3703            return ZERO
3704        assert dt.tzinfo is self
3705
3706        # Find first Sunday in April.
3707        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
3708        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
3709
3710        # Find last Sunday in October.
3711        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
3712        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
3713
3714        # Can't compare naive to aware objects, so strip the timezone from
3715        # dt first.
3716        if start <= dt.replace(tzinfo=None) < end:
3717            return HOUR
3718        else:
3719            return ZERO
3720
3721Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
3722Central  = USTimeZone(-6, "Central",  "CST", "CDT")
3723Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
3724Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
3725utc_real = FixedOffset(0, "UTC", 0)
3726# For better test coverage, we want another flavor of UTC that's west of
3727# the Eastern and Pacific timezones.
3728utc_fake = FixedOffset(-12*60, "UTCfake", 0)
3729
3730class TestTimezoneConversions(unittest.TestCase):
3731    # The DST switch times for 2002, in std time.
3732    dston = datetime(2002, 4, 7, 2)
3733    dstoff = datetime(2002, 10, 27, 1)
3734
3735    theclass = datetime
3736
3737    # Check a time that's inside DST.
3738    def checkinside(self, dt, tz, utc, dston, dstoff):
3739        self.assertEqual(dt.dst(), HOUR)
3740
3741        # Conversion to our own timezone is always an identity.
3742        self.assertEqual(dt.astimezone(tz), dt)
3743
3744        asutc = dt.astimezone(utc)
3745        there_and_back = asutc.astimezone(tz)
3746
3747        # Conversion to UTC and back isn't always an identity here,
3748        # because there are redundant spellings (in local time) of
3749        # UTC time when DST begins:  the clock jumps from 1:59:59
3750        # to 3:00:00, and a local time of 2:MM:SS doesn't really
3751        # make sense then.  The classes above treat 2:MM:SS as
3752        # daylight time then (it's "after 2am"), really an alias
3753        # for 1:MM:SS standard time.  The latter form is what
3754        # conversion back from UTC produces.
3755        if dt.date() == dston.date() and dt.hour == 2:
3756            # We're in the redundant hour, and coming back from
3757            # UTC gives the 1:MM:SS standard-time spelling.
3758            self.assertEqual(there_and_back + HOUR, dt)
3759            # Although during was considered to be in daylight
3760            # time, there_and_back is not.
3761            self.assertEqual(there_and_back.dst(), ZERO)
3762            # They're the same times in UTC.
3763            self.assertEqual(there_and_back.astimezone(utc),
3764                             dt.astimezone(utc))
3765        else:
3766            # We're not in the redundant hour.
3767            self.assertEqual(dt, there_and_back)
3768
3769        # Because we have a redundant spelling when DST begins, there is
3770        # (unfortunately) an hour when DST ends that can't be spelled at all in
3771        # local time.  When DST ends, the clock jumps from 1:59 back to 1:00
3772        # again.  The hour 1:MM DST has no spelling then:  1:MM is taken to be
3773        # standard time.  1:MM DST == 0:MM EST, but 0:MM is taken to be
3774        # daylight time.  The hour 1:MM daylight == 0:MM standard can't be
3775        # expressed in local time.  Nevertheless, we want conversion back
3776        # from UTC to mimic the local clock's "repeat an hour" behavior.
3777        nexthour_utc = asutc + HOUR
3778        nexthour_tz = nexthour_utc.astimezone(tz)
3779        if dt.date() == dstoff.date() and dt.hour == 0:
3780            # We're in the hour before the last DST hour.  The last DST hour
3781            # is ineffable.  We want the conversion back to repeat 1:MM.
3782            self.assertEqual(nexthour_tz, dt.replace(hour=1))
3783            nexthour_utc += HOUR
3784            nexthour_tz = nexthour_utc.astimezone(tz)
3785            self.assertEqual(nexthour_tz, dt.replace(hour=1))
3786        else:
3787            self.assertEqual(nexthour_tz - dt, HOUR)
3788
3789    # Check a time that's outside DST.
3790    def checkoutside(self, dt, tz, utc):
3791        self.assertEqual(dt.dst(), ZERO)
3792
3793        # Conversion to our own timezone is always an identity.
3794        self.assertEqual(dt.astimezone(tz), dt)
3795
3796        # Converting to UTC and back is an identity too.
3797        asutc = dt.astimezone(utc)
3798        there_and_back = asutc.astimezone(tz)
3799        self.assertEqual(dt, there_and_back)
3800
3801    def convert_between_tz_and_utc(self, tz, utc):
3802        dston = self.dston.replace(tzinfo=tz)
3803        # Because 1:MM on the day DST ends is taken as being standard time,
3804        # there is no spelling in tz for the last hour of daylight time.
3805        # For purposes of the test, the last hour of DST is 0:MM, which is
3806        # taken as being daylight time (and 1:MM is taken as being standard
3807        # time).
3808        dstoff = self.dstoff.replace(tzinfo=tz)
3809        for delta in (timedelta(weeks=13),
3810                      DAY,
3811                      HOUR,
3812                      timedelta(minutes=1),
3813                      timedelta(microseconds=1)):
3814
3815            self.checkinside(dston, tz, utc, dston, dstoff)
3816            for during in dston + delta, dstoff - delta:
3817                self.checkinside(during, tz, utc, dston, dstoff)
3818
3819            self.checkoutside(dstoff, tz, utc)
3820            for outside in dston - delta, dstoff + delta:
3821                self.checkoutside(outside, tz, utc)
3822
3823    def test_easy(self):
3824        # Despite the name of this test, the endcases are excruciating.
3825        self.convert_between_tz_and_utc(Eastern, utc_real)
3826        self.convert_between_tz_and_utc(Pacific, utc_real)
3827        self.convert_between_tz_and_utc(Eastern, utc_fake)
3828        self.convert_between_tz_and_utc(Pacific, utc_fake)
3829        # The next is really dancing near the edge.  It works because
3830        # Pacific and Eastern are far enough apart that their "problem
3831        # hours" don't overlap.
3832        self.convert_between_tz_and_utc(Eastern, Pacific)
3833        self.convert_between_tz_and_utc(Pacific, Eastern)
3834        # OTOH, these fail!  Don't enable them.  The difficulty is that
3835        # the edge case tests assume that every hour is representable in
3836        # the "utc" class.  This is always true for a fixed-offset tzinfo
3837        # class (lke utc_real and utc_fake), but not for Eastern or Central.
3838        # For these adjacent DST-aware time zones, the range of time offsets
3839        # tested ends up creating hours in the one that aren't representable
3840        # in the other.  For the same reason, we would see failures in the
3841        # Eastern vs Pacific tests too if we added 3*HOUR to the list of
3842        # offset deltas in convert_between_tz_and_utc().
3843        #
3844        # self.convert_between_tz_and_utc(Eastern, Central)  # can't work
3845        # self.convert_between_tz_and_utc(Central, Eastern)  # can't work
3846
3847    def test_tricky(self):
3848        # 22:00 on day before daylight starts.
3849        fourback = self.dston - timedelta(hours=4)
3850        ninewest = FixedOffset(-9*60, "-0900", 0)
3851        fourback = fourback.replace(tzinfo=ninewest)
3852        # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST.  Since it's "after
3853        # 2", we should get the 3 spelling.
3854        # If we plug 22:00 the day before into Eastern, it "looks like std
3855        # time", so its offset is returned as -5, and -5 - -9 = 4.  Adding 4
3856        # to 22:00 lands on 2:00, which makes no sense in local time (the
3857        # local clock jumps from 1 to 3).  The point here is to make sure we
3858        # get the 3 spelling.
3859        expected = self.dston.replace(hour=3)
3860        got = fourback.astimezone(Eastern).replace(tzinfo=None)
3861        self.assertEqual(expected, got)
3862
3863        # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST.  In that
3864        # case we want the 1:00 spelling.
3865        sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
3866        # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
3867        # and adding -4-0 == -4 gives the 2:00 spelling.  We want the 1:00 EST
3868        # spelling.
3869        expected = self.dston.replace(hour=1)
3870        got = sixutc.astimezone(Eastern).replace(tzinfo=None)
3871        self.assertEqual(expected, got)
3872
3873        # Now on the day DST ends, we want "repeat an hour" behavior.
3874        #  UTC  4:MM  5:MM  6:MM  7:MM  checking these
3875        #  EST 23:MM  0:MM  1:MM  2:MM
3876        #  EDT  0:MM  1:MM  2:MM  3:MM
3877        # wall  0:MM  1:MM  1:MM  2:MM  against these
3878        for utc in utc_real, utc_fake:
3879            for tz in Eastern, Pacific:
3880                first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
3881                # Convert that to UTC.
3882                first_std_hour -= tz.utcoffset(None)
3883                # Adjust for possibly fake UTC.
3884                asutc = first_std_hour + utc.utcoffset(None)
3885                # First UTC hour to convert; this is 4:00 when utc=utc_real &
3886                # tz=Eastern.
3887                asutcbase = asutc.replace(tzinfo=utc)
3888                for tzhour in (0, 1, 1, 2):
3889                    expectedbase = self.dstoff.replace(hour=tzhour)
3890                    for minute in 0, 30, 59:
3891                        expected = expectedbase.replace(minute=minute)
3892                        asutc = asutcbase.replace(minute=minute)
3893                        astz = asutc.astimezone(tz)
3894                        self.assertEqual(astz.replace(tzinfo=None), expected)
3895                    asutcbase += HOUR
3896
3897
3898    def test_bogus_dst(self):
3899        class ok(tzinfo):
3900            def utcoffset(self, dt): return HOUR
3901            def dst(self, dt): return HOUR
3902
3903        now = self.theclass.now().replace(tzinfo=utc_real)
3904        # Doesn't blow up.
3905        now.astimezone(ok())
3906
3907        # Does blow up.
3908        class notok(ok):
3909            def dst(self, dt): return None
3910        self.assertRaises(ValueError, now.astimezone, notok())
3911
3912        # Sometimes blow up. In the following, tzinfo.dst()
3913        # implementation may return None or not None depending on
3914        # whether DST is assumed to be in effect.  In this situation,
3915        # a ValueError should be raised by astimezone().
3916        class tricky_notok(ok):
3917            def dst(self, dt):
3918                if dt.year == 2000:
3919                    return None
3920                else:
3921                    return 10*HOUR
3922        dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
3923        self.assertRaises(ValueError, dt.astimezone, tricky_notok())
3924
3925    def test_fromutc(self):
3926        self.assertRaises(TypeError, Eastern.fromutc)   # not enough args
3927        now = datetime.utcnow().replace(tzinfo=utc_real)
3928        self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
3929        now = now.replace(tzinfo=Eastern)   # insert correct tzinfo
3930        enow = Eastern.fromutc(now)         # doesn't blow up
3931        self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
3932        self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
3933        self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
3934
3935        # Always converts UTC to standard time.
3936        class FauxUSTimeZone(USTimeZone):
3937            def fromutc(self, dt):
3938                return dt + self.stdoffset
3939        FEastern  = FauxUSTimeZone(-5, "FEastern",  "FEST", "FEDT")
3940
3941        #  UTC  4:MM  5:MM  6:MM  7:MM  8:MM  9:MM
3942        #  EST 23:MM  0:MM  1:MM  2:MM  3:MM  4:MM
3943        #  EDT  0:MM  1:MM  2:MM  3:MM  4:MM  5:MM
3944
3945        # Check around DST start.
3946        start = self.dston.replace(hour=4, tzinfo=Eastern)
3947        fstart = start.replace(tzinfo=FEastern)
3948        for wall in 23, 0, 1, 3, 4, 5:
3949            expected = start.replace(hour=wall)
3950            if wall == 23:
3951                expected -= timedelta(days=1)
3952            got = Eastern.fromutc(start)
3953            self.assertEqual(expected, got)
3954
3955            expected = fstart + FEastern.stdoffset
3956            got = FEastern.fromutc(fstart)
3957            self.assertEqual(expected, got)
3958
3959            # Ensure astimezone() calls fromutc() too.
3960            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3961            self.assertEqual(expected, got)
3962
3963            start += HOUR
3964            fstart += HOUR
3965
3966        # Check around DST end.
3967        start = self.dstoff.replace(hour=4, tzinfo=Eastern)
3968        fstart = start.replace(tzinfo=FEastern)
3969        for wall in 0, 1, 1, 2, 3, 4:
3970            expected = start.replace(hour=wall)
3971            got = Eastern.fromutc(start)
3972            self.assertEqual(expected, got)
3973
3974            expected = fstart + FEastern.stdoffset
3975            got = FEastern.fromutc(fstart)
3976            self.assertEqual(expected, got)
3977
3978            # Ensure astimezone() calls fromutc() too.
3979            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
3980            self.assertEqual(expected, got)
3981
3982            start += HOUR
3983            fstart += HOUR
3984
3985
3986#############################################################################
3987# oddballs
3988
3989class Oddballs(unittest.TestCase):
3990
3991    def test_bug_1028306(self):
3992        # Trying to compare a date to a datetime should act like a mixed-
3993        # type comparison, despite that datetime is a subclass of date.
3994        as_date = date.today()
3995        as_datetime = datetime.combine(as_date, time())
3996        self.assertTrue(as_date != as_datetime)
3997        self.assertTrue(as_datetime != as_date)
3998        self.assertFalse(as_date == as_datetime)
3999        self.assertFalse(as_datetime == as_date)
4000        self.assertRaises(TypeError, lambda: as_date < as_datetime)
4001        self.assertRaises(TypeError, lambda: as_datetime < as_date)
4002        self.assertRaises(TypeError, lambda: as_date <= as_datetime)
4003        self.assertRaises(TypeError, lambda: as_datetime <= as_date)
4004        self.assertRaises(TypeError, lambda: as_date > as_datetime)
4005        self.assertRaises(TypeError, lambda: as_datetime > as_date)
4006        self.assertRaises(TypeError, lambda: as_date >= as_datetime)
4007        self.assertRaises(TypeError, lambda: as_datetime >= as_date)
4008
4009        # Nevertheless, comparison should work with the base-class (date)
4010        # projection if use of a date method is forced.
4011        self.assertEqual(as_date.__eq__(as_datetime), True)
4012        different_day = (as_date.day + 1) % 20 + 1
4013        as_different = as_datetime.replace(day= different_day)
4014        self.assertEqual(as_date.__eq__(as_different), False)
4015
4016        # And date should compare with other subclasses of date.  If a
4017        # subclass wants to stop this, it's up to the subclass to do so.
4018        date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
4019        self.assertEqual(as_date, date_sc)
4020        self.assertEqual(date_sc, as_date)
4021
4022        # Ditto for datetimes.
4023        datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
4024                                       as_date.day, 0, 0, 0)
4025        self.assertEqual(as_datetime, datetime_sc)
4026        self.assertEqual(datetime_sc, as_datetime)
4027
4028    def test_extra_attributes(self):
4029        for x in [date.today(),
4030                  time(),
4031                  datetime.utcnow(),
4032                  timedelta(),
4033                  tzinfo(),
4034                  timezone(timedelta())]:
4035            with self.assertRaises(AttributeError):
4036                x.abc = 1
4037
4038    def test_check_arg_types(self):
4039        class Number:
4040            def __init__(self, value):
4041                self.value = value
4042            def __int__(self):
4043                return self.value
4044
4045        for xx in [decimal.Decimal(10),
4046                   decimal.Decimal('10.9'),
4047                   Number(10)]:
4048            self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
4049                             datetime(xx, xx, xx, xx, xx, xx, xx))
4050
4051        with self.assertRaisesRegex(TypeError, '^an integer is required '
4052                                              r'\(got type str\)$'):
4053            datetime(10, 10, '10')
4054
4055        f10 = Number(10.9)
4056        with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
4057                                              r'\(type float\)$'):
4058            datetime(10, 10, f10)
4059
4060        class Float(float):
4061            pass
4062        s10 = Float(10.9)
4063        with self.assertRaisesRegex(TypeError, '^integer argument expected, '
4064                                               'got float$'):
4065            datetime(10, 10, s10)
4066
4067        with self.assertRaises(TypeError):
4068            datetime(10., 10, 10)
4069        with self.assertRaises(TypeError):
4070            datetime(10, 10., 10)
4071        with self.assertRaises(TypeError):
4072            datetime(10, 10, 10.)
4073        with self.assertRaises(TypeError):
4074            datetime(10, 10, 10, 10.)
4075        with self.assertRaises(TypeError):
4076            datetime(10, 10, 10, 10, 10.)
4077        with self.assertRaises(TypeError):
4078            datetime(10, 10, 10, 10, 10, 10.)
4079        with self.assertRaises(TypeError):
4080            datetime(10, 10, 10, 10, 10, 10, 10.)
4081
4082#############################################################################
4083# Local Time Disambiguation
4084
4085# An experimental reimplementation of fromutc that respects the "fold" flag.
4086
4087class tzinfo2(tzinfo):
4088
4089    def fromutc(self, dt):
4090        "datetime in UTC -> datetime in local time."
4091
4092        if not isinstance(dt, datetime):
4093            raise TypeError("fromutc() requires a datetime argument")
4094        if dt.tzinfo is not self:
4095            raise ValueError("dt.tzinfo is not self")
4096        # Returned value satisfies
4097        #          dt + ldt.utcoffset() = ldt
4098        off0 = dt.replace(fold=0).utcoffset()
4099        off1 = dt.replace(fold=1).utcoffset()
4100        if off0 is None or off1 is None or dt.dst() is None:
4101            raise ValueError
4102        if off0 == off1:
4103            ldt = dt + off0
4104            off1 = ldt.utcoffset()
4105            if off0 == off1:
4106                return ldt
4107        # Now, we discovered both possible offsets, so
4108        # we can just try four possible solutions:
4109        for off in [off0, off1]:
4110            ldt = dt + off
4111            if ldt.utcoffset() == off:
4112                return ldt
4113            ldt = ldt.replace(fold=1)
4114            if ldt.utcoffset() == off:
4115                return ldt
4116
4117        raise ValueError("No suitable local time found")
4118
4119# Reimplementing simplified US timezones to respect the "fold" flag:
4120
4121class USTimeZone2(tzinfo2):
4122
4123    def __init__(self, hours, reprname, stdname, dstname):
4124        self.stdoffset = timedelta(hours=hours)
4125        self.reprname = reprname
4126        self.stdname = stdname
4127        self.dstname = dstname
4128
4129    def __repr__(self):
4130        return self.reprname
4131
4132    def tzname(self, dt):
4133        if self.dst(dt):
4134            return self.dstname
4135        else:
4136            return self.stdname
4137
4138    def utcoffset(self, dt):
4139        return self.stdoffset + self.dst(dt)
4140
4141    def dst(self, dt):
4142        if dt is None or dt.tzinfo is None:
4143            # An exception instead may be sensible here, in one or more of
4144            # the cases.
4145            return ZERO
4146        assert dt.tzinfo is self
4147
4148        # Find first Sunday in April.
4149        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4150        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4151
4152        # Find last Sunday in October.
4153        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4154        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4155
4156        # Can't compare naive to aware objects, so strip the timezone from
4157        # dt first.
4158        dt = dt.replace(tzinfo=None)
4159        if start + HOUR <= dt < end:
4160            # DST is in effect.
4161            return HOUR
4162        elif end <= dt < end + HOUR:
4163            # Fold (an ambiguous hour): use dt.fold to disambiguate.
4164            return ZERO if dt.fold else HOUR
4165        elif start <= dt < start + HOUR:
4166            # Gap (a non-existent hour): reverse the fold rule.
4167            return HOUR if dt.fold else ZERO
4168        else:
4169            # DST is off.
4170            return ZERO
4171
4172Eastern2  = USTimeZone2(-5, "Eastern2",  "EST", "EDT")
4173Central2  = USTimeZone2(-6, "Central2",  "CST", "CDT")
4174Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
4175Pacific2  = USTimeZone2(-8, "Pacific2",  "PST", "PDT")
4176
4177# Europe_Vilnius_1941 tzinfo implementation reproduces the following
4178# 1941 transition from Olson's tzdist:
4179#
4180# Zone NAME           GMTOFF RULES  FORMAT [UNTIL]
4181# ZoneEurope/Vilnius  1:00   -      CET    1940 Aug  3
4182#                     3:00   -      MSK    1941 Jun 24
4183#                     1:00   C-Eur  CE%sT  1944 Aug
4184#
4185# $ zdump -v Europe/Vilnius | grep 1941
4186# Europe/Vilnius  Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
4187# Europe/Vilnius  Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
4188
4189class Europe_Vilnius_1941(tzinfo):
4190    def _utc_fold(self):
4191        return [datetime(1941, 6, 23, 21, tzinfo=self),  # Mon Jun 23 21:00:00 1941 UTC
4192                datetime(1941, 6, 23, 22, tzinfo=self)]  # Mon Jun 23 22:00:00 1941 UTC
4193
4194    def _loc_fold(self):
4195        return [datetime(1941, 6, 23, 23, tzinfo=self),  # Mon Jun 23 23:00:00 1941 MSK / CEST
4196                datetime(1941, 6, 24, 0, tzinfo=self)]   # Mon Jun 24 00:00:00 1941 CEST
4197
4198    def utcoffset(self, dt):
4199        fold_start, fold_stop = self._loc_fold()
4200        if dt < fold_start:
4201            return 3 * HOUR
4202        if dt < fold_stop:
4203            return (2 if dt.fold else 3) * HOUR
4204        # if dt >= fold_stop
4205        return 2 * HOUR
4206
4207    def dst(self, dt):
4208        fold_start, fold_stop = self._loc_fold()
4209        if dt < fold_start:
4210            return 0 * HOUR
4211        if dt < fold_stop:
4212            return (1 if dt.fold else 0) * HOUR
4213        # if dt >= fold_stop
4214        return 1 * HOUR
4215
4216    def tzname(self, dt):
4217        fold_start, fold_stop = self._loc_fold()
4218        if dt < fold_start:
4219            return 'MSK'
4220        if dt < fold_stop:
4221            return ('MSK', 'CEST')[dt.fold]
4222        # if dt >= fold_stop
4223        return 'CEST'
4224
4225    def fromutc(self, dt):
4226        assert dt.fold == 0
4227        assert dt.tzinfo is self
4228        if dt.year != 1941:
4229            raise NotImplementedError
4230        fold_start, fold_stop = self._utc_fold()
4231        if dt < fold_start:
4232            return dt + 3 * HOUR
4233        if dt < fold_stop:
4234            return (dt + 2 * HOUR).replace(fold=1)
4235        # if dt >= fold_stop
4236        return dt + 2 * HOUR
4237
4238
4239class TestLocalTimeDisambiguation(unittest.TestCase):
4240
4241    def test_vilnius_1941_fromutc(self):
4242        Vilnius = Europe_Vilnius_1941()
4243
4244        gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
4245        ldt = gdt.astimezone(Vilnius)
4246        self.assertEqual(ldt.strftime("%c %Z%z"),
4247                         'Mon Jun 23 23:59:59 1941 MSK+0300')
4248        self.assertEqual(ldt.fold, 0)
4249        self.assertFalse(ldt.dst())
4250
4251        gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
4252        ldt = gdt.astimezone(Vilnius)
4253        self.assertEqual(ldt.strftime("%c %Z%z"),
4254                         'Mon Jun 23 23:00:00 1941 CEST+0200')
4255        self.assertEqual(ldt.fold, 1)
4256        self.assertTrue(ldt.dst())
4257
4258        gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
4259        ldt = gdt.astimezone(Vilnius)
4260        self.assertEqual(ldt.strftime("%c %Z%z"),
4261                         'Tue Jun 24 00:00:00 1941 CEST+0200')
4262        self.assertEqual(ldt.fold, 0)
4263        self.assertTrue(ldt.dst())
4264
4265    def test_vilnius_1941_toutc(self):
4266        Vilnius = Europe_Vilnius_1941()
4267
4268        ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
4269        gdt = ldt.astimezone(timezone.utc)
4270        self.assertEqual(gdt.strftime("%c %Z"),
4271                         'Mon Jun 23 19:59:59 1941 UTC')
4272
4273        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
4274        gdt = ldt.astimezone(timezone.utc)
4275        self.assertEqual(gdt.strftime("%c %Z"),
4276                         'Mon Jun 23 20:59:59 1941 UTC')
4277
4278        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
4279        gdt = ldt.astimezone(timezone.utc)
4280        self.assertEqual(gdt.strftime("%c %Z"),
4281                         'Mon Jun 23 21:59:59 1941 UTC')
4282
4283        ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
4284        gdt = ldt.astimezone(timezone.utc)
4285        self.assertEqual(gdt.strftime("%c %Z"),
4286                         'Mon Jun 23 22:00:00 1941 UTC')
4287
4288
4289    def test_constructors(self):
4290        t = time(0, fold=1)
4291        dt = datetime(1, 1, 1, fold=1)
4292        self.assertEqual(t.fold, 1)
4293        self.assertEqual(dt.fold, 1)
4294        with self.assertRaises(TypeError):
4295            time(0, 0, 0, 0, None, 0)
4296
4297    def test_member(self):
4298        dt = datetime(1, 1, 1, fold=1)
4299        t = dt.time()
4300        self.assertEqual(t.fold, 1)
4301        t = dt.timetz()
4302        self.assertEqual(t.fold, 1)
4303
4304    def test_replace(self):
4305        t = time(0)
4306        dt = datetime(1, 1, 1)
4307        self.assertEqual(t.replace(fold=1).fold, 1)
4308        self.assertEqual(dt.replace(fold=1).fold, 1)
4309        self.assertEqual(t.replace(fold=0).fold, 0)
4310        self.assertEqual(dt.replace(fold=0).fold, 0)
4311        # Check that replacement of other fields does not change "fold".
4312        t = t.replace(fold=1, tzinfo=Eastern)
4313        dt = dt.replace(fold=1, tzinfo=Eastern)
4314        self.assertEqual(t.replace(tzinfo=None).fold, 1)
4315        self.assertEqual(dt.replace(tzinfo=None).fold, 1)
4316        # Check that fold is a keyword-only argument
4317        with self.assertRaises(TypeError):
4318            t.replace(1, 1, 1, None, 1)
4319        with self.assertRaises(TypeError):
4320            dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
4321
4322    def test_comparison(self):
4323        t = time(0)
4324        dt = datetime(1, 1, 1)
4325        self.assertEqual(t, t.replace(fold=1))
4326        self.assertEqual(dt, dt.replace(fold=1))
4327
4328    def test_hash(self):
4329        t = time(0)
4330        dt = datetime(1, 1, 1)
4331        self.assertEqual(hash(t), hash(t.replace(fold=1)))
4332        self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
4333
4334    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4335    def test_fromtimestamp(self):
4336        s = 1414906200
4337        dt0 = datetime.fromtimestamp(s)
4338        dt1 = datetime.fromtimestamp(s + 3600)
4339        self.assertEqual(dt0.fold, 0)
4340        self.assertEqual(dt1.fold, 1)
4341
4342    @support.run_with_tz('Australia/Lord_Howe')
4343    def test_fromtimestamp_lord_howe(self):
4344        tm = _time.localtime(1.4e9)
4345        if _time.strftime('%Z%z', tm) != 'LHST+1030':
4346            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4347        # $ TZ=Australia/Lord_Howe date -r 1428158700
4348        # Sun Apr  5 01:45:00 LHDT 2015
4349        # $ TZ=Australia/Lord_Howe date -r 1428160500
4350        # Sun Apr  5 01:45:00 LHST 2015
4351        s = 1428158700
4352        t0 = datetime.fromtimestamp(s)
4353        t1 = datetime.fromtimestamp(s + 1800)
4354        self.assertEqual(t0, t1)
4355        self.assertEqual(t0.fold, 0)
4356        self.assertEqual(t1.fold, 1)
4357
4358
4359    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4360    def test_timestamp(self):
4361        dt0 = datetime(2014, 11, 2, 1, 30)
4362        dt1 = dt0.replace(fold=1)
4363        self.assertEqual(dt0.timestamp() + 3600,
4364                         dt1.timestamp())
4365
4366    @support.run_with_tz('Australia/Lord_Howe')
4367    def test_timestamp_lord_howe(self):
4368        tm = _time.localtime(1.4e9)
4369        if _time.strftime('%Z%z', tm) != 'LHST+1030':
4370            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
4371        t = datetime(2015, 4, 5, 1, 45)
4372        s0 = t.replace(fold=0).timestamp()
4373        s1 = t.replace(fold=1).timestamp()
4374        self.assertEqual(s0 + 1800, s1)
4375
4376
4377    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4378    def test_astimezone(self):
4379        dt0 = datetime(2014, 11, 2, 1, 30)
4380        dt1 = dt0.replace(fold=1)
4381        # Convert both naive instances to aware.
4382        adt0 = dt0.astimezone()
4383        adt1 = dt1.astimezone()
4384        # Check that the first instance in DST zone and the second in STD
4385        self.assertEqual(adt0.tzname(), 'EDT')
4386        self.assertEqual(adt1.tzname(), 'EST')
4387        self.assertEqual(adt0 + HOUR, adt1)
4388        # Aware instances with fixed offset tzinfo's always have fold=0
4389        self.assertEqual(adt0.fold, 0)
4390        self.assertEqual(adt1.fold, 0)
4391
4392
4393    def test_pickle_fold(self):
4394        t = time(fold=1)
4395        dt = datetime(1, 1, 1, fold=1)
4396        for pickler, unpickler, proto in pickle_choices:
4397            for x in [t, dt]:
4398                s = pickler.dumps(x, proto)
4399                y = unpickler.loads(s)
4400                self.assertEqual(x, y)
4401                self.assertEqual((0 if proto < 4 else x.fold), y.fold)
4402
4403    def test_repr(self):
4404        t = time(fold=1)
4405        dt = datetime(1, 1, 1, fold=1)
4406        self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
4407        self.assertEqual(repr(dt),
4408                         'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
4409
4410    def test_dst(self):
4411        # Let's first establish that things work in regular times.
4412        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4413        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4414        self.assertEqual(dt_summer.dst(), HOUR)
4415        self.assertEqual(dt_winter.dst(), ZERO)
4416        # The disambiguation flag is ignored
4417        self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
4418        self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
4419
4420        # Pick local time in the fold.
4421        for minute in [0, 30, 59]:
4422            dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
4423            # With fold=0 (the default) it is in DST.
4424            self.assertEqual(dt.dst(), HOUR)
4425            # With fold=1 it is in STD.
4426            self.assertEqual(dt.replace(fold=1).dst(), ZERO)
4427
4428        # Pick local time in the gap.
4429        for minute in [0, 30, 59]:
4430            dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
4431            # With fold=0 (the default) it is in STD.
4432            self.assertEqual(dt.dst(), ZERO)
4433            # With fold=1 it is in DST.
4434            self.assertEqual(dt.replace(fold=1).dst(), HOUR)
4435
4436
4437    def test_utcoffset(self):
4438        # Let's first establish that things work in regular times.
4439        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
4440        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
4441        self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
4442        self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
4443        # The disambiguation flag is ignored
4444        self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
4445        self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
4446
4447    def test_fromutc(self):
4448        # Let's first establish that things work in regular times.
4449        u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
4450        u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
4451        t_summer = Eastern2.fromutc(u_summer)
4452        t_winter = Eastern2.fromutc(u_winter)
4453        self.assertEqual(t_summer, u_summer - 4 * HOUR)
4454        self.assertEqual(t_winter, u_winter - 5 * HOUR)
4455        self.assertEqual(t_summer.fold, 0)
4456        self.assertEqual(t_winter.fold, 0)
4457
4458        # What happens in the fall-back fold?
4459        u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
4460        t0 = Eastern2.fromutc(u)
4461        u += HOUR
4462        t1 = Eastern2.fromutc(u)
4463        self.assertEqual(t0, t1)
4464        self.assertEqual(t0.fold, 0)
4465        self.assertEqual(t1.fold, 1)
4466        # The tricky part is when u is in the local fold:
4467        u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
4468        t = Eastern2.fromutc(u)
4469        self.assertEqual((t.day, t.hour), (26, 21))
4470        # .. or gets into the local fold after a standard time adjustment
4471        u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
4472        t = Eastern2.fromutc(u)
4473        self.assertEqual((t.day, t.hour), (27, 1))
4474
4475        # What happens in the spring-forward gap?
4476        u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
4477        t = Eastern2.fromutc(u)
4478        self.assertEqual((t.day, t.hour), (6, 21))
4479
4480    def test_mixed_compare_regular(self):
4481        t = datetime(2000, 1, 1, tzinfo=Eastern2)
4482        self.assertEqual(t, t.astimezone(timezone.utc))
4483        t = datetime(2000, 6, 1, tzinfo=Eastern2)
4484        self.assertEqual(t, t.astimezone(timezone.utc))
4485
4486    def test_mixed_compare_fold(self):
4487        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4488        t_fold_utc = t_fold.astimezone(timezone.utc)
4489        self.assertNotEqual(t_fold, t_fold_utc)
4490
4491    def test_mixed_compare_gap(self):
4492        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4493        t_gap_utc = t_gap.astimezone(timezone.utc)
4494        self.assertNotEqual(t_gap, t_gap_utc)
4495
4496    def test_hash_aware(self):
4497        t = datetime(2000, 1, 1, tzinfo=Eastern2)
4498        self.assertEqual(hash(t), hash(t.replace(fold=1)))
4499        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
4500        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
4501        self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
4502        self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
4503
4504SEC = timedelta(0, 1)
4505
4506def pairs(iterable):
4507    a, b = itertools.tee(iterable)
4508    next(b, None)
4509    return zip(a, b)
4510
4511class ZoneInfo(tzinfo):
4512    zoneroot = '/usr/share/zoneinfo'
4513    def __init__(self, ut, ti):
4514        """
4515
4516        :param ut: array
4517            Array of transition point timestamps
4518        :param ti: list
4519            A list of (offset, isdst, abbr) tuples
4520        :return: None
4521        """
4522        self.ut = ut
4523        self.ti = ti
4524        self.lt = self.invert(ut, ti)
4525
4526    @staticmethod
4527    def invert(ut, ti):
4528        lt = (array('q', ut), array('q', ut))
4529        if ut:
4530            offset = ti[0][0] // SEC
4531            lt[0][0] += offset
4532            lt[1][0] += offset
4533            for i in range(1, len(ut)):
4534                lt[0][i] += ti[i-1][0] // SEC
4535                lt[1][i] += ti[i][0] // SEC
4536        return lt
4537
4538    @classmethod
4539    def fromfile(cls, fileobj):
4540        if fileobj.read(4).decode() != "TZif":
4541            raise ValueError("not a zoneinfo file")
4542        fileobj.seek(32)
4543        counts = array('i')
4544        counts.fromfile(fileobj, 3)
4545        if sys.byteorder != 'big':
4546            counts.byteswap()
4547
4548        ut = array('i')
4549        ut.fromfile(fileobj, counts[0])
4550        if sys.byteorder != 'big':
4551            ut.byteswap()
4552
4553        type_indices = array('B')
4554        type_indices.fromfile(fileobj, counts[0])
4555
4556        ttis = []
4557        for i in range(counts[1]):
4558            ttis.append(struct.unpack(">lbb", fileobj.read(6)))
4559
4560        abbrs = fileobj.read(counts[2])
4561
4562        # Convert ttis
4563        for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
4564            abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
4565            ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
4566
4567        ti = [None] * len(ut)
4568        for i, idx in enumerate(type_indices):
4569            ti[i] = ttis[idx]
4570
4571        self = cls(ut, ti)
4572
4573        return self
4574
4575    @classmethod
4576    def fromname(cls, name):
4577        path = os.path.join(cls.zoneroot, name)
4578        with open(path, 'rb') as f:
4579            return cls.fromfile(f)
4580
4581    EPOCHORDINAL = date(1970, 1, 1).toordinal()
4582
4583    def fromutc(self, dt):
4584        """datetime in UTC -> datetime in local time."""
4585
4586        if not isinstance(dt, datetime):
4587            raise TypeError("fromutc() requires a datetime argument")
4588        if dt.tzinfo is not self:
4589            raise ValueError("dt.tzinfo is not self")
4590
4591        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4592                     + dt.hour * 3600
4593                     + dt.minute * 60
4594                     + dt.second)
4595
4596        if timestamp < self.ut[1]:
4597            tti = self.ti[0]
4598            fold = 0
4599        else:
4600            idx = bisect.bisect_right(self.ut, timestamp)
4601            assert self.ut[idx-1] <= timestamp
4602            assert idx == len(self.ut) or timestamp < self.ut[idx]
4603            tti_prev, tti = self.ti[idx-2:idx]
4604            # Detect fold
4605            shift = tti_prev[0] - tti[0]
4606            fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
4607        dt += tti[0]
4608        if fold:
4609            return dt.replace(fold=1)
4610        else:
4611            return dt
4612
4613    def _find_ti(self, dt, i):
4614        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
4615             + dt.hour * 3600
4616             + dt.minute * 60
4617             + dt.second)
4618        lt = self.lt[dt.fold]
4619        idx = bisect.bisect_right(lt, timestamp)
4620
4621        return self.ti[max(0, idx - 1)][i]
4622
4623    def utcoffset(self, dt):
4624        return self._find_ti(dt, 0)
4625
4626    def dst(self, dt):
4627        isdst = self._find_ti(dt, 1)
4628        # XXX: We cannot accurately determine the "save" value,
4629        # so let's return 1h whenever DST is in effect.  Since
4630        # we don't use dst() in fromutc(), it is unlikely that
4631        # it will be needed for anything more than bool(dst()).
4632        return ZERO if isdst else HOUR
4633
4634    def tzname(self, dt):
4635        return self._find_ti(dt, 2)
4636
4637    @classmethod
4638    def zonenames(cls, zonedir=None):
4639        if zonedir is None:
4640            zonedir = cls.zoneroot
4641        zone_tab = os.path.join(zonedir, 'zone.tab')
4642        try:
4643            f = open(zone_tab)
4644        except OSError:
4645            return
4646        with f:
4647            for line in f:
4648                line = line.strip()
4649                if line and not line.startswith('#'):
4650                    yield line.split()[2]
4651
4652    @classmethod
4653    def stats(cls, start_year=1):
4654        count = gap_count = fold_count = zeros_count = 0
4655        min_gap = min_fold = timedelta.max
4656        max_gap = max_fold = ZERO
4657        min_gap_datetime = max_gap_datetime = datetime.min
4658        min_gap_zone = max_gap_zone = None
4659        min_fold_datetime = max_fold_datetime = datetime.min
4660        min_fold_zone = max_fold_zone = None
4661        stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
4662        for zonename in cls.zonenames():
4663            count += 1
4664            tz = cls.fromname(zonename)
4665            for dt, shift in tz.transitions():
4666                if dt < stats_since:
4667                    continue
4668                if shift > ZERO:
4669                    gap_count += 1
4670                    if (shift, dt) > (max_gap, max_gap_datetime):
4671                        max_gap = shift
4672                        max_gap_zone = zonename
4673                        max_gap_datetime = dt
4674                    if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
4675                        min_gap = shift
4676                        min_gap_zone = zonename
4677                        min_gap_datetime = dt
4678                elif shift < ZERO:
4679                    fold_count += 1
4680                    shift = -shift
4681                    if (shift, dt) > (max_fold, max_fold_datetime):
4682                        max_fold = shift
4683                        max_fold_zone = zonename
4684                        max_fold_datetime = dt
4685                    if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
4686                        min_fold = shift
4687                        min_fold_zone = zonename
4688                        min_fold_datetime = dt
4689                else:
4690                    zeros_count += 1
4691        trans_counts = (gap_count, fold_count, zeros_count)
4692        print("Number of zones:       %5d" % count)
4693        print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
4694              ((sum(trans_counts),) + trans_counts))
4695        print("Min gap:         %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
4696        print("Max gap:         %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
4697        print("Min fold:        %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
4698        print("Max fold:        %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
4699
4700
4701    def transitions(self):
4702        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4703            shift = ti[0] - prev_ti[0]
4704            yield datetime.utcfromtimestamp(t), shift
4705
4706    def nondst_folds(self):
4707        """Find all folds with the same value of isdst on both sides of the transition."""
4708        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
4709            shift = ti[0] - prev_ti[0]
4710            if shift < ZERO and ti[1] == prev_ti[1]:
4711                yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
4712
4713    @classmethod
4714    def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
4715        count = 0
4716        for zonename in cls.zonenames():
4717            tz = cls.fromname(zonename)
4718            for dt, shift, prev_abbr, abbr in tz.nondst_folds():
4719                if dt.year < start_year or same_abbr and prev_abbr != abbr:
4720                    continue
4721                count += 1
4722                print("%3d) %-30s %s %10s %5s -> %s" %
4723                      (count, zonename, dt, shift, prev_abbr, abbr))
4724
4725    def folds(self):
4726        for t, shift in self.transitions():
4727            if shift < ZERO:
4728                yield t, -shift
4729
4730    def gaps(self):
4731        for t, shift in self.transitions():
4732            if shift > ZERO:
4733                yield t, shift
4734
4735    def zeros(self):
4736        for t, shift in self.transitions():
4737            if not shift:
4738                yield t
4739
4740
4741class ZoneInfoTest(unittest.TestCase):
4742    zonename = 'America/New_York'
4743
4744    def setUp(self):
4745        if sys.platform == "win32":
4746            self.skipTest("Skipping zoneinfo tests on Windows")
4747        try:
4748            self.tz = ZoneInfo.fromname(self.zonename)
4749        except FileNotFoundError as err:
4750            self.skipTest("Skipping %s: %s" % (self.zonename, err))
4751
4752    def assertEquivDatetimes(self, a, b):
4753        self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
4754                         (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
4755
4756    def test_folds(self):
4757        tz = self.tz
4758        for dt, shift in tz.folds():
4759            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4760                udt = dt + x
4761                ldt = tz.fromutc(udt.replace(tzinfo=tz))
4762                self.assertEqual(ldt.fold, 1)
4763                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4764                self.assertEquivDatetimes(adt, ldt)
4765                utcoffset = ldt.utcoffset()
4766                self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
4767                # Round trip
4768                self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
4769                                          udt.replace(tzinfo=timezone.utc))
4770
4771
4772            for x in [-timedelta.resolution, shift]:
4773                udt = dt + x
4774                udt = udt.replace(tzinfo=tz)
4775                ldt = tz.fromutc(udt)
4776                self.assertEqual(ldt.fold, 0)
4777
4778    def test_gaps(self):
4779        tz = self.tz
4780        for dt, shift in tz.gaps():
4781            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
4782                udt = dt + x
4783                udt = udt.replace(tzinfo=tz)
4784                ldt = tz.fromutc(udt)
4785                self.assertEqual(ldt.fold, 0)
4786                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
4787                self.assertEquivDatetimes(adt, ldt)
4788                utcoffset = ldt.utcoffset()
4789                self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
4790                # Create a local time inside the gap
4791                ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
4792                self.assertLess(ldt.replace(fold=1).utcoffset(),
4793                                ldt.replace(fold=0).utcoffset(),
4794                                "At %s." % ldt)
4795
4796            for x in [-timedelta.resolution, shift]:
4797                udt = dt + x
4798                ldt = tz.fromutc(udt.replace(tzinfo=tz))
4799                self.assertEqual(ldt.fold, 0)
4800
4801    def test_system_transitions(self):
4802        if ('Riyadh8' in self.zonename or
4803            # From tzdata NEWS file:
4804            # The files solar87, solar88, and solar89 are no longer distributed.
4805            # They were a negative experiment - that is, a demonstration that
4806            # tz data can represent solar time only with some difficulty and error.
4807            # Their presence in the distribution caused confusion, as Riyadh
4808            # civil time was generally not solar time in those years.
4809                self.zonename.startswith('right/')):
4810            self.skipTest("Skipping %s" % self.zonename)
4811        tz = self.tz
4812        TZ = os.environ.get('TZ')
4813        os.environ['TZ'] = self.zonename
4814        try:
4815            _time.tzset()
4816            for udt, shift in tz.transitions():
4817                if udt.year >= 2037:
4818                    # System support for times around the end of 32-bit time_t
4819                    # and later is flaky on many systems.
4820                    break
4821                s0 = (udt - datetime(1970, 1, 1)) // SEC
4822                ss = shift // SEC   # shift seconds
4823                for x in [-40 * 3600, -20*3600, -1, 0,
4824                          ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
4825                    s = s0 + x
4826                    sdt = datetime.fromtimestamp(s)
4827                    tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
4828                    self.assertEquivDatetimes(sdt, tzdt)
4829                    s1 = sdt.timestamp()
4830                    self.assertEqual(s, s1)
4831                if ss > 0:  # gap
4832                    # Create local time inside the gap
4833                    dt = datetime.fromtimestamp(s0) - shift / 2
4834                    ts0 = dt.timestamp()
4835                    ts1 = dt.replace(fold=1).timestamp()
4836                    self.assertEqual(ts0, s0 + ss / 2)
4837                    self.assertEqual(ts1, s0 - ss / 2)
4838        finally:
4839            if TZ is None:
4840                del os.environ['TZ']
4841            else:
4842                os.environ['TZ'] = TZ
4843            _time.tzset()
4844
4845
4846class ZoneInfoCompleteTest(unittest.TestSuite):
4847    def __init__(self):
4848        tests = []
4849        if is_resource_enabled('tzdata'):
4850            for name in ZoneInfo.zonenames():
4851                Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
4852                Test.zonename = name
4853                for method in dir(Test):
4854                    if method.startswith('test_'):
4855                        tests.append(Test(method))
4856        super().__init__(tests)
4857
4858# Iran had a sub-minute UTC offset before 1946.
4859class IranTest(ZoneInfoTest):
4860    zonename = 'Asia/Tehran'
4861
4862def load_tests(loader, standard_tests, pattern):
4863    standard_tests.addTest(ZoneInfoCompleteTest())
4864    return standard_tests
4865
4866
4867if __name__ == "__main__":
4868    unittest.main()
4869