• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test date/time type.
2
3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
4"""
5import itertools
6import bisect
7import copy
8import decimal
9import sys
10import os
11import pickle
12import random
13import re
14import struct
15import unittest
16
17from array import array
18
19from operator import lt, le, gt, ge, eq, ne, truediv, floordiv, mod
20
21from test import support
22from test.support import is_resource_enabled, ALWAYS_EQ, LARGEST, SMALLEST
23
24import datetime as datetime_module
25from datetime import MINYEAR, MAXYEAR
26from datetime import timedelta
27from datetime import tzinfo
28from datetime import time
29from datetime import timezone
30from datetime import date, datetime
31import time as _time
32
33import _testcapi
34
35# Needed by test_datetime
36import _strptime
37#
38
39pickle_loads = {pickle.loads, pickle._loads}
40
41pickle_choices = [(pickle, pickle, proto)
42                  for proto in range(pickle.HIGHEST_PROTOCOL + 1)]
43assert len(pickle_choices) == pickle.HIGHEST_PROTOCOL + 1
44
45# An arbitrary collection of objects of non-datetime types, for testing
46# mixed-type comparisons.
47OTHERSTUFF = (10, 34.5, "abc", {}, [], ())
48
49
50# XXX Copied from test_float.
51INF = float("inf")
52NAN = float("nan")
53
54
55#############################################################################
56# module tests
57
58class TestModule(unittest.TestCase):
59
60    def test_constants(self):
61        datetime = datetime_module
62        self.assertEqual(datetime.MINYEAR, 1)
63        self.assertEqual(datetime.MAXYEAR, 9999)
64
65    def test_name_cleanup(self):
66        if '_Pure' in self.__class__.__name__:
67            self.skipTest('Only run for Fast C implementation')
68
69        datetime = datetime_module
70        names = set(name for name in dir(datetime)
71                    if not name.startswith('__') and not name.endswith('__'))
72        allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime',
73                       'datetime_CAPI', 'time', 'timedelta', 'timezone',
74                       'tzinfo', 'sys'])
75        self.assertEqual(names - allowed, set([]))
76
77    def test_divide_and_round(self):
78        if '_Fast' in self.__class__.__name__:
79            self.skipTest('Only run for Pure Python implementation')
80
81        dar = datetime_module._divide_and_round
82
83        self.assertEqual(dar(-10, -3), 3)
84        self.assertEqual(dar(5, -2), -2)
85
86        # four cases: (2 signs of a) x (2 signs of b)
87        self.assertEqual(dar(7, 3), 2)
88        self.assertEqual(dar(-7, 3), -2)
89        self.assertEqual(dar(7, -3), -2)
90        self.assertEqual(dar(-7, -3), 2)
91
92        # ties to even - eight cases:
93        # (2 signs of a) x (2 signs of b) x (even / odd quotient)
94        self.assertEqual(dar(10, 4), 2)
95        self.assertEqual(dar(-10, 4), -2)
96        self.assertEqual(dar(10, -4), -2)
97        self.assertEqual(dar(-10, -4), 2)
98
99        self.assertEqual(dar(6, 4), 2)
100        self.assertEqual(dar(-6, 4), -2)
101        self.assertEqual(dar(6, -4), -2)
102        self.assertEqual(dar(-6, -4), 2)
103
104
105#############################################################################
106# tzinfo tests
107
108class FixedOffset(tzinfo):
109
110    def __init__(self, offset, name, dstoffset=42):
111        if isinstance(offset, int):
112            offset = timedelta(minutes=offset)
113        if isinstance(dstoffset, int):
114            dstoffset = timedelta(minutes=dstoffset)
115        self.__offset = offset
116        self.__name = name
117        self.__dstoffset = dstoffset
118    def __repr__(self):
119        return self.__name.lower()
120    def utcoffset(self, dt):
121        return self.__offset
122    def tzname(self, dt):
123        return self.__name
124    def dst(self, dt):
125        return self.__dstoffset
126
127class PicklableFixedOffset(FixedOffset):
128
129    def __init__(self, offset=None, name=None, dstoffset=None):
130        FixedOffset.__init__(self, offset, name, dstoffset)
131
132    def __getstate__(self):
133        return self.__dict__
134
135class _TZInfo(tzinfo):
136    def utcoffset(self, datetime_module):
137        return random.random()
138
139class TestTZInfo(unittest.TestCase):
140
141    def test_refcnt_crash_bug_22044(self):
142        tz1 = _TZInfo()
143        dt1 = datetime(2014, 7, 21, 11, 32, 3, 0, tz1)
144        with self.assertRaises(TypeError):
145            dt1.utcoffset()
146
147    def test_non_abstractness(self):
148        # In order to allow subclasses to get pickled, the C implementation
149        # wasn't able to get away with having __init__ raise
150        # NotImplementedError.
151        useless = tzinfo()
152        dt = datetime.max
153        self.assertRaises(NotImplementedError, useless.tzname, dt)
154        self.assertRaises(NotImplementedError, useless.utcoffset, dt)
155        self.assertRaises(NotImplementedError, useless.dst, dt)
156
157    def test_subclass_must_override(self):
158        class NotEnough(tzinfo):
159            def __init__(self, offset, name):
160                self.__offset = offset
161                self.__name = name
162        self.assertTrue(issubclass(NotEnough, tzinfo))
163        ne = NotEnough(3, "NotByALongShot")
164        self.assertIsInstance(ne, tzinfo)
165
166        dt = datetime.now()
167        self.assertRaises(NotImplementedError, ne.tzname, dt)
168        self.assertRaises(NotImplementedError, ne.utcoffset, dt)
169        self.assertRaises(NotImplementedError, ne.dst, dt)
170
171    def test_normal(self):
172        fo = FixedOffset(3, "Three")
173        self.assertIsInstance(fo, tzinfo)
174        for dt in datetime.now(), None:
175            self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
176            self.assertEqual(fo.tzname(dt), "Three")
177            self.assertEqual(fo.dst(dt), timedelta(minutes=42))
178
179    def test_pickling_base(self):
180        # There's no point to pickling tzinfo objects on their own (they
181        # carry no data), but they need to be picklable anyway else
182        # concrete subclasses can't be pickled.
183        orig = tzinfo.__new__(tzinfo)
184        self.assertIs(type(orig), tzinfo)
185        for pickler, unpickler, proto in pickle_choices:
186            green = pickler.dumps(orig, proto)
187            derived = unpickler.loads(green)
188            self.assertIs(type(derived), tzinfo)
189
190    def test_pickling_subclass(self):
191        # Make sure we can pickle/unpickle an instance of a subclass.
192        offset = timedelta(minutes=-300)
193        for otype, args in [
194            (PicklableFixedOffset, (offset, 'cookie')),
195            (timezone, (offset,)),
196            (timezone, (offset, "EST"))]:
197            orig = otype(*args)
198            oname = orig.tzname(None)
199            self.assertIsInstance(orig, tzinfo)
200            self.assertIs(type(orig), otype)
201            self.assertEqual(orig.utcoffset(None), offset)
202            self.assertEqual(orig.tzname(None), oname)
203            for pickler, unpickler, proto in pickle_choices:
204                green = pickler.dumps(orig, proto)
205                derived = unpickler.loads(green)
206                self.assertIsInstance(derived, tzinfo)
207                self.assertIs(type(derived), otype)
208                self.assertEqual(derived.utcoffset(None), offset)
209                self.assertEqual(derived.tzname(None), oname)
210
211    def test_issue23600(self):
212        DSTDIFF = DSTOFFSET = timedelta(hours=1)
213
214        class UKSummerTime(tzinfo):
215            """Simple time zone which pretends to always be in summer time, since
216                that's what shows the failure.
217            """
218
219            def utcoffset(self, dt):
220                return DSTOFFSET
221
222            def dst(self, dt):
223                return DSTDIFF
224
225            def tzname(self, dt):
226                return 'UKSummerTime'
227
228        tz = UKSummerTime()
229        u = datetime(2014, 4, 26, 12, 1, tzinfo=tz)
230        t = tz.fromutc(u)
231        self.assertEqual(t - t.utcoffset(), u)
232
233
234class TestTimeZone(unittest.TestCase):
235
236    def setUp(self):
237        self.ACDT = timezone(timedelta(hours=9.5), 'ACDT')
238        self.EST = timezone(-timedelta(hours=5), 'EST')
239        self.DT = datetime(2010, 1, 1)
240
241    def test_str(self):
242        for tz in [self.ACDT, self.EST, timezone.utc,
243                   timezone.min, timezone.max]:
244            self.assertEqual(str(tz), tz.tzname(None))
245
246    def test_repr(self):
247        datetime = datetime_module
248        for tz in [self.ACDT, self.EST, timezone.utc,
249                   timezone.min, timezone.max]:
250            # test round-trip
251            tzrep = repr(tz)
252            self.assertEqual(tz, eval(tzrep))
253
254    def test_class_members(self):
255        limit = timedelta(hours=23, minutes=59)
256        self.assertEqual(timezone.utc.utcoffset(None), ZERO)
257        self.assertEqual(timezone.min.utcoffset(None), -limit)
258        self.assertEqual(timezone.max.utcoffset(None), limit)
259
260    def test_constructor(self):
261        self.assertIs(timezone.utc, timezone(timedelta(0)))
262        self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
263        self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
264        for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
265            tz = timezone(subminute)
266            self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
267        # invalid offsets
268        for invalid in [timedelta(1, 1), timedelta(1)]:
269            self.assertRaises(ValueError, timezone, invalid)
270            self.assertRaises(ValueError, timezone, -invalid)
271
272        with self.assertRaises(TypeError): timezone(None)
273        with self.assertRaises(TypeError): timezone(42)
274        with self.assertRaises(TypeError): timezone(ZERO, None)
275        with self.assertRaises(TypeError): timezone(ZERO, 42)
276        with self.assertRaises(TypeError): timezone(ZERO, 'ABC', 'extra')
277
278    def test_inheritance(self):
279        self.assertIsInstance(timezone.utc, tzinfo)
280        self.assertIsInstance(self.EST, tzinfo)
281
282    def test_utcoffset(self):
283        dummy = self.DT
284        for h in [0, 1.5, 12]:
285            offset = h * HOUR
286            self.assertEqual(offset, timezone(offset).utcoffset(dummy))
287            self.assertEqual(-offset, timezone(-offset).utcoffset(dummy))
288
289        with self.assertRaises(TypeError): self.EST.utcoffset('')
290        with self.assertRaises(TypeError): self.EST.utcoffset(5)
291
292
293    def test_dst(self):
294        self.assertIsNone(timezone.utc.dst(self.DT))
295
296        with self.assertRaises(TypeError): self.EST.dst('')
297        with self.assertRaises(TypeError): self.EST.dst(5)
298
299    def test_tzname(self):
300        self.assertEqual('UTC', timezone.utc.tzname(None))
301        self.assertEqual('UTC', timezone(ZERO).tzname(None))
302        self.assertEqual('UTC-05:00', timezone(-5 * HOUR).tzname(None))
303        self.assertEqual('UTC+09:30', timezone(9.5 * HOUR).tzname(None))
304        self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
305        self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
306        # bpo-34482: Check that surrogates are handled properly.
307        self.assertEqual('\ud800', timezone(ZERO, '\ud800').tzname(None))
308
309        # Sub-minute offsets:
310        self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
311        self.assertEqual('UTC-01:06:40',
312                         timezone(-timedelta(0, 4000)).tzname(None))
313        self.assertEqual('UTC+01:06:40.000001',
314                         timezone(timedelta(0, 4000, 1)).tzname(None))
315        self.assertEqual('UTC-01:06:40.000001',
316                         timezone(-timedelta(0, 4000, 1)).tzname(None))
317
318        with self.assertRaises(TypeError): self.EST.tzname('')
319        with self.assertRaises(TypeError): self.EST.tzname(5)
320
321    def test_fromutc(self):
322        with self.assertRaises(ValueError):
323            timezone.utc.fromutc(self.DT)
324        with self.assertRaises(TypeError):
325            timezone.utc.fromutc('not datetime')
326        for tz in [self.EST, self.ACDT, Eastern]:
327            utctime = self.DT.replace(tzinfo=tz)
328            local = tz.fromutc(utctime)
329            self.assertEqual(local - utctime, tz.utcoffset(local))
330            self.assertEqual(local,
331                             self.DT.replace(tzinfo=timezone.utc))
332
333    def test_comparison(self):
334        self.assertNotEqual(timezone(ZERO), timezone(HOUR))
335        self.assertEqual(timezone(HOUR), timezone(HOUR))
336        self.assertEqual(timezone(-5 * HOUR), timezone(-5 * HOUR, 'EST'))
337        with self.assertRaises(TypeError): timezone(ZERO) < timezone(ZERO)
338        self.assertIn(timezone(ZERO), {timezone(ZERO)})
339        self.assertTrue(timezone(ZERO) != None)
340        self.assertFalse(timezone(ZERO) ==  None)
341
342        tz = timezone(ZERO)
343        self.assertTrue(tz == ALWAYS_EQ)
344        self.assertFalse(tz != ALWAYS_EQ)
345        self.assertTrue(tz < LARGEST)
346        self.assertFalse(tz > LARGEST)
347        self.assertTrue(tz <= LARGEST)
348        self.assertFalse(tz >= LARGEST)
349        self.assertFalse(tz < SMALLEST)
350        self.assertTrue(tz > SMALLEST)
351        self.assertFalse(tz <= SMALLEST)
352        self.assertTrue(tz >= SMALLEST)
353
354    def test_aware_datetime(self):
355        # test that timezone instances can be used by datetime
356        t = datetime(1, 1, 1)
357        for tz in [timezone.min, timezone.max, timezone.utc]:
358            self.assertEqual(tz.tzname(t),
359                             t.replace(tzinfo=tz).tzname())
360            self.assertEqual(tz.utcoffset(t),
361                             t.replace(tzinfo=tz).utcoffset())
362            self.assertEqual(tz.dst(t),
363                             t.replace(tzinfo=tz).dst())
364
365    def test_pickle(self):
366        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
367            for pickler, unpickler, proto in pickle_choices:
368                tz_copy = unpickler.loads(pickler.dumps(tz, proto))
369                self.assertEqual(tz_copy, tz)
370        tz = timezone.utc
371        for pickler, unpickler, proto in pickle_choices:
372            tz_copy = unpickler.loads(pickler.dumps(tz, proto))
373            self.assertIs(tz_copy, tz)
374
375    def test_copy(self):
376        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
377            tz_copy = copy.copy(tz)
378            self.assertEqual(tz_copy, tz)
379        tz = timezone.utc
380        tz_copy = copy.copy(tz)
381        self.assertIs(tz_copy, tz)
382
383    def test_deepcopy(self):
384        for tz in self.ACDT, self.EST, timezone.min, timezone.max:
385            tz_copy = copy.deepcopy(tz)
386            self.assertEqual(tz_copy, tz)
387        tz = timezone.utc
388        tz_copy = copy.deepcopy(tz)
389        self.assertIs(tz_copy, tz)
390
391    def test_offset_boundaries(self):
392        # Test timedeltas close to the boundaries
393        time_deltas = [
394            timedelta(hours=23, minutes=59),
395            timedelta(hours=23, minutes=59, seconds=59),
396            timedelta(hours=23, minutes=59, seconds=59, microseconds=999999),
397        ]
398        time_deltas.extend([-delta for delta in time_deltas])
399
400        for delta in time_deltas:
401            with self.subTest(test_type='good', delta=delta):
402                timezone(delta)
403
404        # Test timedeltas on and outside the boundaries
405        bad_time_deltas = [
406            timedelta(hours=24),
407            timedelta(hours=24, microseconds=1),
408        ]
409        bad_time_deltas.extend([-delta for delta in bad_time_deltas])
410
411        for delta in bad_time_deltas:
412            with self.subTest(test_type='bad', delta=delta):
413                with self.assertRaises(ValueError):
414                    timezone(delta)
415
416    def test_comparison_with_tzinfo(self):
417        # Constructing tzinfo objects directly should not be done by users
418        # and serves only to check the bug described in bpo-37915
419        self.assertNotEqual(timezone.utc, tzinfo())
420        self.assertNotEqual(timezone(timedelta(hours=1)), tzinfo())
421
422#############################################################################
423# Base class for testing a particular aspect of timedelta, time, date and
424# datetime comparisons.
425
426class HarmlessMixedComparison:
427    # Test that __eq__ and __ne__ don't complain for mixed-type comparisons.
428
429    # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a
430    # legit constructor.
431
432    def test_harmless_mixed_comparison(self):
433        me = self.theclass(1, 1, 1)
434
435        self.assertFalse(me == ())
436        self.assertTrue(me != ())
437        self.assertFalse(() == me)
438        self.assertTrue(() != me)
439
440        self.assertIn(me, [1, 20, [], me])
441        self.assertIn([], [me, 1, 20, []])
442
443        # Comparison to objects of unsupported types should return
444        # NotImplemented which falls back to the right hand side's __eq__
445        # method. In this case, ALWAYS_EQ.__eq__ always returns True.
446        # ALWAYS_EQ.__ne__ always returns False.
447        self.assertTrue(me == ALWAYS_EQ)
448        self.assertFalse(me != ALWAYS_EQ)
449
450        # If the other class explicitly defines ordering
451        # relative to our class, it is allowed to do so
452        self.assertTrue(me < LARGEST)
453        self.assertFalse(me > LARGEST)
454        self.assertTrue(me <= LARGEST)
455        self.assertFalse(me >= LARGEST)
456        self.assertFalse(me < SMALLEST)
457        self.assertTrue(me > SMALLEST)
458        self.assertFalse(me <= SMALLEST)
459        self.assertTrue(me >= SMALLEST)
460
461    def test_harmful_mixed_comparison(self):
462        me = self.theclass(1, 1, 1)
463
464        self.assertRaises(TypeError, lambda: me < ())
465        self.assertRaises(TypeError, lambda: me <= ())
466        self.assertRaises(TypeError, lambda: me > ())
467        self.assertRaises(TypeError, lambda: me >= ())
468
469        self.assertRaises(TypeError, lambda: () < me)
470        self.assertRaises(TypeError, lambda: () <= me)
471        self.assertRaises(TypeError, lambda: () > me)
472        self.assertRaises(TypeError, lambda: () >= me)
473
474#############################################################################
475# timedelta tests
476
477class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase):
478
479    theclass = timedelta
480
481    def test_constructor(self):
482        eq = self.assertEqual
483        td = timedelta
484
485        # Check keyword args to constructor
486        eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0,
487                    milliseconds=0, microseconds=0))
488        eq(td(1), td(days=1))
489        eq(td(0, 1), td(seconds=1))
490        eq(td(0, 0, 1), td(microseconds=1))
491        eq(td(weeks=1), td(days=7))
492        eq(td(days=1), td(hours=24))
493        eq(td(hours=1), td(minutes=60))
494        eq(td(minutes=1), td(seconds=60))
495        eq(td(seconds=1), td(milliseconds=1000))
496        eq(td(milliseconds=1), td(microseconds=1000))
497
498        # Check float args to constructor
499        eq(td(weeks=1.0/7), td(days=1))
500        eq(td(days=1.0/24), td(hours=1))
501        eq(td(hours=1.0/60), td(minutes=1))
502        eq(td(minutes=1.0/60), td(seconds=1))
503        eq(td(seconds=0.001), td(milliseconds=1))
504        eq(td(milliseconds=0.001), td(microseconds=1))
505
506    def test_computations(self):
507        eq = self.assertEqual
508        td = timedelta
509
510        a = td(7) # One week
511        b = td(0, 60) # One minute
512        c = td(0, 0, 1000) # One millisecond
513        eq(a+b+c, td(7, 60, 1000))
514        eq(a-b, td(6, 24*3600 - 60))
515        eq(b.__rsub__(a), td(6, 24*3600 - 60))
516        eq(-a, td(-7))
517        eq(+a, td(7))
518        eq(-b, td(-1, 24*3600 - 60))
519        eq(-c, td(-1, 24*3600 - 1, 999000))
520        eq(abs(a), a)
521        eq(abs(-a), a)
522        eq(td(6, 24*3600), a)
523        eq(td(0, 0, 60*1000000), b)
524        eq(a*10, td(70))
525        eq(a*10, 10*a)
526        eq(a*10, 10*a)
527        eq(b*10, td(0, 600))
528        eq(10*b, td(0, 600))
529        eq(b*10, td(0, 600))
530        eq(c*10, td(0, 0, 10000))
531        eq(10*c, td(0, 0, 10000))
532        eq(c*10, td(0, 0, 10000))
533        eq(a*-1, -a)
534        eq(b*-2, -b-b)
535        eq(c*-2, -c+-c)
536        eq(b*(60*24), (b*60)*24)
537        eq(b*(60*24), (60*b)*24)
538        eq(c*1000, td(0, 1))
539        eq(1000*c, td(0, 1))
540        eq(a//7, td(1))
541        eq(b//10, td(0, 6))
542        eq(c//1000, td(0, 0, 1))
543        eq(a//10, td(0, 7*24*360))
544        eq(a//3600000, td(0, 0, 7*24*1000))
545        eq(a/0.5, td(14))
546        eq(b/0.5, td(0, 120))
547        eq(a/7, td(1))
548        eq(b/10, td(0, 6))
549        eq(c/1000, td(0, 0, 1))
550        eq(a/10, td(0, 7*24*360))
551        eq(a/3600000, td(0, 0, 7*24*1000))
552
553        # Multiplication by float
554        us = td(microseconds=1)
555        eq((3*us) * 0.5, 2*us)
556        eq((5*us) * 0.5, 2*us)
557        eq(0.5 * (3*us), 2*us)
558        eq(0.5 * (5*us), 2*us)
559        eq((-3*us) * 0.5, -2*us)
560        eq((-5*us) * 0.5, -2*us)
561
562        # Issue #23521
563        eq(td(seconds=1) * 0.123456, td(microseconds=123456))
564        eq(td(seconds=1) * 0.6112295, td(microseconds=611229))
565
566        # Division by int and float
567        eq((3*us) / 2, 2*us)
568        eq((5*us) / 2, 2*us)
569        eq((-3*us) / 2.0, -2*us)
570        eq((-5*us) / 2.0, -2*us)
571        eq((3*us) / -2, -2*us)
572        eq((5*us) / -2, -2*us)
573        eq((3*us) / -2.0, -2*us)
574        eq((5*us) / -2.0, -2*us)
575        for i in range(-10, 10):
576            eq((i*us/3)//us, round(i/3))
577        for i in range(-10, 10):
578            eq((i*us/-3)//us, round(i/-3))
579
580        # Issue #23521
581        eq(td(seconds=1) / (1 / 0.6112295), td(microseconds=611229))
582
583        # Issue #11576
584        eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998),
585           td(0, 0, 1))
586        eq(td(999999999, 1, 1) - td(999999999, 1, 0),
587           td(0, 0, 1))
588
589    def test_disallowed_computations(self):
590        a = timedelta(42)
591
592        # Add/sub ints or floats should be illegal
593        for i in 1, 1.0:
594            self.assertRaises(TypeError, lambda: a+i)
595            self.assertRaises(TypeError, lambda: a-i)
596            self.assertRaises(TypeError, lambda: i+a)
597            self.assertRaises(TypeError, lambda: i-a)
598
599        # Division of int by timedelta doesn't make sense.
600        # Division by zero doesn't make sense.
601        zero = 0
602        self.assertRaises(TypeError, lambda: zero // a)
603        self.assertRaises(ZeroDivisionError, lambda: a // zero)
604        self.assertRaises(ZeroDivisionError, lambda: a / zero)
605        self.assertRaises(ZeroDivisionError, lambda: a / 0.0)
606        self.assertRaises(TypeError, lambda: a / '')
607
608    @support.requires_IEEE_754
609    def test_disallowed_special(self):
610        a = timedelta(42)
611        self.assertRaises(ValueError, a.__mul__, NAN)
612        self.assertRaises(ValueError, a.__truediv__, NAN)
613
614    def test_basic_attributes(self):
615        days, seconds, us = 1, 7, 31
616        td = timedelta(days, seconds, us)
617        self.assertEqual(td.days, days)
618        self.assertEqual(td.seconds, seconds)
619        self.assertEqual(td.microseconds, us)
620
621    def test_total_seconds(self):
622        td = timedelta(days=365)
623        self.assertEqual(td.total_seconds(), 31536000.0)
624        for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]:
625            td = timedelta(seconds=total_seconds)
626            self.assertEqual(td.total_seconds(), total_seconds)
627        # Issue8644: Test that td.total_seconds() has the same
628        # accuracy as td / timedelta(seconds=1).
629        for ms in [-1, -2, -123]:
630            td = timedelta(microseconds=ms)
631            self.assertEqual(td.total_seconds(), td / timedelta(seconds=1))
632
633    def test_carries(self):
634        t1 = timedelta(days=100,
635                       weeks=-7,
636                       hours=-24*(100-49),
637                       minutes=-3,
638                       seconds=12,
639                       microseconds=(3*60 - 12) * 1e6 + 1)
640        t2 = timedelta(microseconds=1)
641        self.assertEqual(t1, t2)
642
643    def test_hash_equality(self):
644        t1 = timedelta(days=100,
645                       weeks=-7,
646                       hours=-24*(100-49),
647                       minutes=-3,
648                       seconds=12,
649                       microseconds=(3*60 - 12) * 1000000)
650        t2 = timedelta()
651        self.assertEqual(hash(t1), hash(t2))
652
653        t1 += timedelta(weeks=7)
654        t2 += timedelta(days=7*7)
655        self.assertEqual(t1, t2)
656        self.assertEqual(hash(t1), hash(t2))
657
658        d = {t1: 1}
659        d[t2] = 2
660        self.assertEqual(len(d), 1)
661        self.assertEqual(d[t1], 2)
662
663    def test_pickling(self):
664        args = 12, 34, 56
665        orig = timedelta(*args)
666        for pickler, unpickler, proto in pickle_choices:
667            green = pickler.dumps(orig, proto)
668            derived = unpickler.loads(green)
669            self.assertEqual(orig, derived)
670
671    def test_compare(self):
672        t1 = timedelta(2, 3, 4)
673        t2 = timedelta(2, 3, 4)
674        self.assertEqual(t1, t2)
675        self.assertTrue(t1 <= t2)
676        self.assertTrue(t1 >= t2)
677        self.assertFalse(t1 != t2)
678        self.assertFalse(t1 < t2)
679        self.assertFalse(t1 > t2)
680
681        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
682            t2 = timedelta(*args)   # this is larger than t1
683            self.assertTrue(t1 < t2)
684            self.assertTrue(t2 > t1)
685            self.assertTrue(t1 <= t2)
686            self.assertTrue(t2 >= t1)
687            self.assertTrue(t1 != t2)
688            self.assertTrue(t2 != t1)
689            self.assertFalse(t1 == t2)
690            self.assertFalse(t2 == t1)
691            self.assertFalse(t1 > t2)
692            self.assertFalse(t2 < t1)
693            self.assertFalse(t1 >= t2)
694            self.assertFalse(t2 <= t1)
695
696        for badarg in OTHERSTUFF:
697            self.assertEqual(t1 == badarg, False)
698            self.assertEqual(t1 != badarg, True)
699            self.assertEqual(badarg == t1, False)
700            self.assertEqual(badarg != t1, True)
701
702            self.assertRaises(TypeError, lambda: t1 <= badarg)
703            self.assertRaises(TypeError, lambda: t1 < badarg)
704            self.assertRaises(TypeError, lambda: t1 > badarg)
705            self.assertRaises(TypeError, lambda: t1 >= badarg)
706            self.assertRaises(TypeError, lambda: badarg <= t1)
707            self.assertRaises(TypeError, lambda: badarg < t1)
708            self.assertRaises(TypeError, lambda: badarg > t1)
709            self.assertRaises(TypeError, lambda: badarg >= t1)
710
711    def test_str(self):
712        td = timedelta
713        eq = self.assertEqual
714
715        eq(str(td(1)), "1 day, 0:00:00")
716        eq(str(td(-1)), "-1 day, 0:00:00")
717        eq(str(td(2)), "2 days, 0:00:00")
718        eq(str(td(-2)), "-2 days, 0:00:00")
719
720        eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59")
721        eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04")
722        eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)),
723           "-210 days, 23:12:34")
724
725        eq(str(td(milliseconds=1)), "0:00:00.001000")
726        eq(str(td(microseconds=3)), "0:00:00.000003")
727
728        eq(str(td(days=999999999, hours=23, minutes=59, seconds=59,
729                   microseconds=999999)),
730           "999999999 days, 23:59:59.999999")
731
732    def test_repr(self):
733        name = 'datetime.' + self.theclass.__name__
734        self.assertEqual(repr(self.theclass(1)),
735                         "%s(days=1)" % name)
736        self.assertEqual(repr(self.theclass(10, 2)),
737                         "%s(days=10, seconds=2)" % name)
738        self.assertEqual(repr(self.theclass(-10, 2, 400000)),
739                         "%s(days=-10, seconds=2, microseconds=400000)" % name)
740        self.assertEqual(repr(self.theclass(seconds=60)),
741                         "%s(seconds=60)" % name)
742        self.assertEqual(repr(self.theclass()),
743                         "%s(0)" % name)
744        self.assertEqual(repr(self.theclass(microseconds=100)),
745                         "%s(microseconds=100)" % name)
746        self.assertEqual(repr(self.theclass(days=1, microseconds=100)),
747                         "%s(days=1, microseconds=100)" % name)
748        self.assertEqual(repr(self.theclass(seconds=1, microseconds=100)),
749                         "%s(seconds=1, microseconds=100)" % name)
750
751    def test_roundtrip(self):
752        for td in (timedelta(days=999999999, hours=23, minutes=59,
753                             seconds=59, microseconds=999999),
754                   timedelta(days=-999999999),
755                   timedelta(days=-999999999, seconds=1),
756                   timedelta(days=1, seconds=2, microseconds=3)):
757
758            # Verify td -> string -> td identity.
759            s = repr(td)
760            self.assertTrue(s.startswith('datetime.'))
761            s = s[9:]
762            td2 = eval(s)
763            self.assertEqual(td, td2)
764
765            # Verify identity via reconstructing from pieces.
766            td2 = timedelta(td.days, td.seconds, td.microseconds)
767            self.assertEqual(td, td2)
768
769    def test_resolution_info(self):
770        self.assertIsInstance(timedelta.min, timedelta)
771        self.assertIsInstance(timedelta.max, timedelta)
772        self.assertIsInstance(timedelta.resolution, timedelta)
773        self.assertTrue(timedelta.max > timedelta.min)
774        self.assertEqual(timedelta.min, timedelta(-999999999))
775        self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1))
776        self.assertEqual(timedelta.resolution, timedelta(0, 0, 1))
777
778    def test_overflow(self):
779        tiny = timedelta.resolution
780
781        td = timedelta.min + tiny
782        td -= tiny  # no problem
783        self.assertRaises(OverflowError, td.__sub__, tiny)
784        self.assertRaises(OverflowError, td.__add__, -tiny)
785
786        td = timedelta.max - tiny
787        td += tiny  # no problem
788        self.assertRaises(OverflowError, td.__add__, tiny)
789        self.assertRaises(OverflowError, td.__sub__, -tiny)
790
791        self.assertRaises(OverflowError, lambda: -timedelta.max)
792
793        day = timedelta(1)
794        self.assertRaises(OverflowError, day.__mul__, 10**9)
795        self.assertRaises(OverflowError, day.__mul__, 1e9)
796        self.assertRaises(OverflowError, day.__truediv__, 1e-20)
797        self.assertRaises(OverflowError, day.__truediv__, 1e-10)
798        self.assertRaises(OverflowError, day.__truediv__, 9e-10)
799
800    @support.requires_IEEE_754
801    def _test_overflow_special(self):
802        day = timedelta(1)
803        self.assertRaises(OverflowError, day.__mul__, INF)
804        self.assertRaises(OverflowError, day.__mul__, -INF)
805
806    def test_microsecond_rounding(self):
807        td = timedelta
808        eq = self.assertEqual
809
810        # Single-field rounding.
811        eq(td(milliseconds=0.4/1000), td(0))    # rounds to 0
812        eq(td(milliseconds=-0.4/1000), td(0))    # rounds to 0
813        eq(td(milliseconds=0.5/1000), td(microseconds=0))
814        eq(td(milliseconds=-0.5/1000), td(microseconds=-0))
815        eq(td(milliseconds=0.6/1000), td(microseconds=1))
816        eq(td(milliseconds=-0.6/1000), td(microseconds=-1))
817        eq(td(milliseconds=1.5/1000), td(microseconds=2))
818        eq(td(milliseconds=-1.5/1000), td(microseconds=-2))
819        eq(td(seconds=0.5/10**6), td(microseconds=0))
820        eq(td(seconds=-0.5/10**6), td(microseconds=-0))
821        eq(td(seconds=1/2**7), td(microseconds=7812))
822        eq(td(seconds=-1/2**7), td(microseconds=-7812))
823
824        # Rounding due to contributions from more than one field.
825        us_per_hour = 3600e6
826        us_per_day = us_per_hour * 24
827        eq(td(days=.4/us_per_day), td(0))
828        eq(td(hours=.2/us_per_hour), td(0))
829        eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1))
830
831        eq(td(days=-.4/us_per_day), td(0))
832        eq(td(hours=-.2/us_per_hour), td(0))
833        eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1))
834
835        # Test for a patch in Issue 8860
836        eq(td(microseconds=0.5), 0.5*td(microseconds=1.0))
837        eq(td(microseconds=0.5)//td.resolution, 0.5*td.resolution//td.resolution)
838
839    def test_massive_normalization(self):
840        td = timedelta(microseconds=-1)
841        self.assertEqual((td.days, td.seconds, td.microseconds),
842                         (-1, 24*3600-1, 999999))
843
844    def test_bool(self):
845        self.assertTrue(timedelta(1))
846        self.assertTrue(timedelta(0, 1))
847        self.assertTrue(timedelta(0, 0, 1))
848        self.assertTrue(timedelta(microseconds=1))
849        self.assertFalse(timedelta(0))
850
851    def test_subclass_timedelta(self):
852
853        class T(timedelta):
854            @staticmethod
855            def from_td(td):
856                return T(td.days, td.seconds, td.microseconds)
857
858            def as_hours(self):
859                sum = (self.days * 24 +
860                       self.seconds / 3600.0 +
861                       self.microseconds / 3600e6)
862                return round(sum)
863
864        t1 = T(days=1)
865        self.assertIs(type(t1), T)
866        self.assertEqual(t1.as_hours(), 24)
867
868        t2 = T(days=-1, seconds=-3600)
869        self.assertIs(type(t2), T)
870        self.assertEqual(t2.as_hours(), -25)
871
872        t3 = t1 + t2
873        self.assertIs(type(t3), timedelta)
874        t4 = T.from_td(t3)
875        self.assertIs(type(t4), T)
876        self.assertEqual(t3.days, t4.days)
877        self.assertEqual(t3.seconds, t4.seconds)
878        self.assertEqual(t3.microseconds, t4.microseconds)
879        self.assertEqual(str(t3), str(t4))
880        self.assertEqual(t4.as_hours(), -1)
881
882    def test_subclass_date(self):
883        class DateSubclass(date):
884            pass
885
886        d1 = DateSubclass(2018, 1, 5)
887        td = timedelta(days=1)
888
889        tests = [
890            ('add', lambda d, t: d + t, DateSubclass(2018, 1, 6)),
891            ('radd', lambda d, t: t + d, DateSubclass(2018, 1, 6)),
892            ('sub', lambda d, t: d - t, DateSubclass(2018, 1, 4)),
893        ]
894
895        for name, func, expected in tests:
896            with self.subTest(name):
897                act = func(d1, td)
898                self.assertEqual(act, expected)
899                self.assertIsInstance(act, DateSubclass)
900
901    def test_subclass_datetime(self):
902        class DateTimeSubclass(datetime):
903            pass
904
905        d1 = DateTimeSubclass(2018, 1, 5, 12, 30)
906        td = timedelta(days=1, minutes=30)
907
908        tests = [
909            ('add', lambda d, t: d + t, DateTimeSubclass(2018, 1, 6, 13)),
910            ('radd', lambda d, t: t + d, DateTimeSubclass(2018, 1, 6, 13)),
911            ('sub', lambda d, t: d - t, DateTimeSubclass(2018, 1, 4, 12)),
912        ]
913
914        for name, func, expected in tests:
915            with self.subTest(name):
916                act = func(d1, td)
917                self.assertEqual(act, expected)
918                self.assertIsInstance(act, DateTimeSubclass)
919
920    def test_division(self):
921        t = timedelta(hours=1, minutes=24, seconds=19)
922        second = timedelta(seconds=1)
923        self.assertEqual(t / second, 5059.0)
924        self.assertEqual(t // second, 5059)
925
926        t = timedelta(minutes=2, seconds=30)
927        minute = timedelta(minutes=1)
928        self.assertEqual(t / minute, 2.5)
929        self.assertEqual(t // minute, 2)
930
931        zerotd = timedelta(0)
932        self.assertRaises(ZeroDivisionError, truediv, t, zerotd)
933        self.assertRaises(ZeroDivisionError, floordiv, t, zerotd)
934
935        # self.assertRaises(TypeError, truediv, t, 2)
936        # note: floor division of a timedelta by an integer *is*
937        # currently permitted.
938
939    def test_remainder(self):
940        t = timedelta(minutes=2, seconds=30)
941        minute = timedelta(minutes=1)
942        r = t % minute
943        self.assertEqual(r, timedelta(seconds=30))
944
945        t = timedelta(minutes=-2, seconds=30)
946        r = t %  minute
947        self.assertEqual(r, timedelta(seconds=30))
948
949        zerotd = timedelta(0)
950        self.assertRaises(ZeroDivisionError, mod, t, zerotd)
951
952        self.assertRaises(TypeError, mod, t, 10)
953
954    def test_divmod(self):
955        t = timedelta(minutes=2, seconds=30)
956        minute = timedelta(minutes=1)
957        q, r = divmod(t, minute)
958        self.assertEqual(q, 2)
959        self.assertEqual(r, timedelta(seconds=30))
960
961        t = timedelta(minutes=-2, seconds=30)
962        q, r = divmod(t, minute)
963        self.assertEqual(q, -2)
964        self.assertEqual(r, timedelta(seconds=30))
965
966        zerotd = timedelta(0)
967        self.assertRaises(ZeroDivisionError, divmod, t, zerotd)
968
969        self.assertRaises(TypeError, divmod, t, 10)
970
971    def test_issue31293(self):
972        # The interpreter shouldn't crash in case a timedelta is divided or
973        # multiplied by a float with a bad as_integer_ratio() method.
974        def get_bad_float(bad_ratio):
975            class BadFloat(float):
976                def as_integer_ratio(self):
977                    return bad_ratio
978            return BadFloat()
979
980        with self.assertRaises(TypeError):
981            timedelta() / get_bad_float(1 << 1000)
982        with self.assertRaises(TypeError):
983            timedelta() * get_bad_float(1 << 1000)
984
985        for bad_ratio in [(), (42, ), (1, 2, 3)]:
986            with self.assertRaises(ValueError):
987                timedelta() / get_bad_float(bad_ratio)
988            with self.assertRaises(ValueError):
989                timedelta() * get_bad_float(bad_ratio)
990
991    def test_issue31752(self):
992        # The interpreter shouldn't crash because divmod() returns negative
993        # remainder.
994        class BadInt(int):
995            def __mul__(self, other):
996                return Prod()
997            def __rmul__(self, other):
998                return Prod()
999            def __floordiv__(self, other):
1000                return Prod()
1001            def __rfloordiv__(self, other):
1002                return Prod()
1003
1004        class Prod:
1005            def __add__(self, other):
1006                return Sum()
1007            def __radd__(self, other):
1008                return Sum()
1009
1010        class Sum(int):
1011            def __divmod__(self, other):
1012                return divmodresult
1013
1014        for divmodresult in [None, (), (0, 1, 2), (0, -1)]:
1015            with self.subTest(divmodresult=divmodresult):
1016                # The following examples should not crash.
1017                try:
1018                    timedelta(microseconds=BadInt(1))
1019                except TypeError:
1020                    pass
1021                try:
1022                    timedelta(hours=BadInt(1))
1023                except TypeError:
1024                    pass
1025                try:
1026                    timedelta(weeks=BadInt(1))
1027                except (TypeError, ValueError):
1028                    pass
1029                try:
1030                    timedelta(1) * BadInt(1)
1031                except (TypeError, ValueError):
1032                    pass
1033                try:
1034                    BadInt(1) * timedelta(1)
1035                except TypeError:
1036                    pass
1037                try:
1038                    timedelta(1) // BadInt(1)
1039                except TypeError:
1040                    pass
1041
1042
1043#############################################################################
1044# date tests
1045
1046class TestDateOnly(unittest.TestCase):
1047    # Tests here won't pass if also run on datetime objects, so don't
1048    # subclass this to test datetimes too.
1049
1050    def test_delta_non_days_ignored(self):
1051        dt = date(2000, 1, 2)
1052        delta = timedelta(days=1, hours=2, minutes=3, seconds=4,
1053                          microseconds=5)
1054        days = timedelta(delta.days)
1055        self.assertEqual(days, timedelta(1))
1056
1057        dt2 = dt + delta
1058        self.assertEqual(dt2, dt + days)
1059
1060        dt2 = delta + dt
1061        self.assertEqual(dt2, dt + days)
1062
1063        dt2 = dt - delta
1064        self.assertEqual(dt2, dt - days)
1065
1066        delta = -delta
1067        days = timedelta(delta.days)
1068        self.assertEqual(days, timedelta(-2))
1069
1070        dt2 = dt + delta
1071        self.assertEqual(dt2, dt + days)
1072
1073        dt2 = delta + dt
1074        self.assertEqual(dt2, dt + days)
1075
1076        dt2 = dt - delta
1077        self.assertEqual(dt2, dt - days)
1078
1079class SubclassDate(date):
1080    sub_var = 1
1081
1082class TestDate(HarmlessMixedComparison, unittest.TestCase):
1083    # Tests here should pass for both dates and datetimes, except for a
1084    # few tests that TestDateTime overrides.
1085
1086    theclass = date
1087
1088    def test_basic_attributes(self):
1089        dt = self.theclass(2002, 3, 1)
1090        self.assertEqual(dt.year, 2002)
1091        self.assertEqual(dt.month, 3)
1092        self.assertEqual(dt.day, 1)
1093
1094    def test_roundtrip(self):
1095        for dt in (self.theclass(1, 2, 3),
1096                   self.theclass.today()):
1097            # Verify dt -> string -> date identity.
1098            s = repr(dt)
1099            self.assertTrue(s.startswith('datetime.'))
1100            s = s[9:]
1101            dt2 = eval(s)
1102            self.assertEqual(dt, dt2)
1103
1104            # Verify identity via reconstructing from pieces.
1105            dt2 = self.theclass(dt.year, dt.month, dt.day)
1106            self.assertEqual(dt, dt2)
1107
1108    def test_ordinal_conversions(self):
1109        # Check some fixed values.
1110        for y, m, d, n in [(1, 1, 1, 1),      # calendar origin
1111                           (1, 12, 31, 365),
1112                           (2, 1, 1, 366),
1113                           # first example from "Calendrical Calculations"
1114                           (1945, 11, 12, 710347)]:
1115            d = self.theclass(y, m, d)
1116            self.assertEqual(n, d.toordinal())
1117            fromord = self.theclass.fromordinal(n)
1118            self.assertEqual(d, fromord)
1119            if hasattr(fromord, "hour"):
1120            # if we're checking something fancier than a date, verify
1121            # the extra fields have been zeroed out
1122                self.assertEqual(fromord.hour, 0)
1123                self.assertEqual(fromord.minute, 0)
1124                self.assertEqual(fromord.second, 0)
1125                self.assertEqual(fromord.microsecond, 0)
1126
1127        # Check first and last days of year spottily across the whole
1128        # range of years supported.
1129        for year in range(MINYEAR, MAXYEAR+1, 7):
1130            # Verify (year, 1, 1) -> ordinal -> y, m, d is identity.
1131            d = self.theclass(year, 1, 1)
1132            n = d.toordinal()
1133            d2 = self.theclass.fromordinal(n)
1134            self.assertEqual(d, d2)
1135            # Verify that moving back a day gets to the end of year-1.
1136            if year > 1:
1137                d = self.theclass.fromordinal(n-1)
1138                d2 = self.theclass(year-1, 12, 31)
1139                self.assertEqual(d, d2)
1140                self.assertEqual(d2.toordinal(), n-1)
1141
1142        # Test every day in a leap-year and a non-leap year.
1143        dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
1144        for year, isleap in (2000, True), (2002, False):
1145            n = self.theclass(year, 1, 1).toordinal()
1146            for month, maxday in zip(range(1, 13), dim):
1147                if month == 2 and isleap:
1148                    maxday += 1
1149                for day in range(1, maxday+1):
1150                    d = self.theclass(year, month, day)
1151                    self.assertEqual(d.toordinal(), n)
1152                    self.assertEqual(d, self.theclass.fromordinal(n))
1153                    n += 1
1154
1155    def test_extreme_ordinals(self):
1156        a = self.theclass.min
1157        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
1158        aord = a.toordinal()
1159        b = a.fromordinal(aord)
1160        self.assertEqual(a, b)
1161
1162        self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1))
1163
1164        b = a + timedelta(days=1)
1165        self.assertEqual(b.toordinal(), aord + 1)
1166        self.assertEqual(b, self.theclass.fromordinal(aord + 1))
1167
1168        a = self.theclass.max
1169        a = self.theclass(a.year, a.month, a.day)  # get rid of time parts
1170        aord = a.toordinal()
1171        b = a.fromordinal(aord)
1172        self.assertEqual(a, b)
1173
1174        self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1))
1175
1176        b = a - timedelta(days=1)
1177        self.assertEqual(b.toordinal(), aord - 1)
1178        self.assertEqual(b, self.theclass.fromordinal(aord - 1))
1179
1180    def test_bad_constructor_arguments(self):
1181        # bad years
1182        self.theclass(MINYEAR, 1, 1)  # no exception
1183        self.theclass(MAXYEAR, 1, 1)  # no exception
1184        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
1185        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
1186        # bad months
1187        self.theclass(2000, 1, 1)    # no exception
1188        self.theclass(2000, 12, 1)   # no exception
1189        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
1190        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
1191        # bad days
1192        self.theclass(2000, 2, 29)   # no exception
1193        self.theclass(2004, 2, 29)   # no exception
1194        self.theclass(2400, 2, 29)   # no exception
1195        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
1196        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
1197        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
1198        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
1199        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
1200        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
1201
1202    def test_hash_equality(self):
1203        d = self.theclass(2000, 12, 31)
1204        # same thing
1205        e = self.theclass(2000, 12, 31)
1206        self.assertEqual(d, e)
1207        self.assertEqual(hash(d), hash(e))
1208
1209        dic = {d: 1}
1210        dic[e] = 2
1211        self.assertEqual(len(dic), 1)
1212        self.assertEqual(dic[d], 2)
1213        self.assertEqual(dic[e], 2)
1214
1215        d = self.theclass(2001,  1,  1)
1216        # same thing
1217        e = self.theclass(2001,  1,  1)
1218        self.assertEqual(d, e)
1219        self.assertEqual(hash(d), hash(e))
1220
1221        dic = {d: 1}
1222        dic[e] = 2
1223        self.assertEqual(len(dic), 1)
1224        self.assertEqual(dic[d], 2)
1225        self.assertEqual(dic[e], 2)
1226
1227    def test_computations(self):
1228        a = self.theclass(2002, 1, 31)
1229        b = self.theclass(1956, 1, 31)
1230        c = self.theclass(2001,2,1)
1231
1232        diff = a-b
1233        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
1234        self.assertEqual(diff.seconds, 0)
1235        self.assertEqual(diff.microseconds, 0)
1236
1237        day = timedelta(1)
1238        week = timedelta(7)
1239        a = self.theclass(2002, 3, 2)
1240        self.assertEqual(a + day, self.theclass(2002, 3, 3))
1241        self.assertEqual(day + a, self.theclass(2002, 3, 3))
1242        self.assertEqual(a - day, self.theclass(2002, 3, 1))
1243        self.assertEqual(-day + a, self.theclass(2002, 3, 1))
1244        self.assertEqual(a + week, self.theclass(2002, 3, 9))
1245        self.assertEqual(a - week, self.theclass(2002, 2, 23))
1246        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1))
1247        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3))
1248        self.assertEqual((a + week) - a, week)
1249        self.assertEqual((a + day) - a, day)
1250        self.assertEqual((a - week) - a, -week)
1251        self.assertEqual((a - day) - a, -day)
1252        self.assertEqual(a - (a + week), -week)
1253        self.assertEqual(a - (a + day), -day)
1254        self.assertEqual(a - (a - week), week)
1255        self.assertEqual(a - (a - day), day)
1256        self.assertEqual(c - (c - day), day)
1257
1258        # Add/sub ints or floats should be illegal
1259        for i in 1, 1.0:
1260            self.assertRaises(TypeError, lambda: a+i)
1261            self.assertRaises(TypeError, lambda: a-i)
1262            self.assertRaises(TypeError, lambda: i+a)
1263            self.assertRaises(TypeError, lambda: i-a)
1264
1265        # delta - date is senseless.
1266        self.assertRaises(TypeError, lambda: day - a)
1267        # mixing date and (delta or date) via * or // is senseless
1268        self.assertRaises(TypeError, lambda: day * a)
1269        self.assertRaises(TypeError, lambda: a * day)
1270        self.assertRaises(TypeError, lambda: day // a)
1271        self.assertRaises(TypeError, lambda: a // day)
1272        self.assertRaises(TypeError, lambda: a * a)
1273        self.assertRaises(TypeError, lambda: a // a)
1274        # date + date is senseless
1275        self.assertRaises(TypeError, lambda: a + a)
1276
1277    def test_overflow(self):
1278        tiny = self.theclass.resolution
1279
1280        for delta in [tiny, timedelta(1), timedelta(2)]:
1281            dt = self.theclass.min + delta
1282            dt -= delta  # no problem
1283            self.assertRaises(OverflowError, dt.__sub__, delta)
1284            self.assertRaises(OverflowError, dt.__add__, -delta)
1285
1286            dt = self.theclass.max - delta
1287            dt += delta  # no problem
1288            self.assertRaises(OverflowError, dt.__add__, delta)
1289            self.assertRaises(OverflowError, dt.__sub__, -delta)
1290
1291    def test_fromtimestamp(self):
1292        import time
1293
1294        # Try an arbitrary fixed value.
1295        year, month, day = 1999, 9, 19
1296        ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1))
1297        d = self.theclass.fromtimestamp(ts)
1298        self.assertEqual(d.year, year)
1299        self.assertEqual(d.month, month)
1300        self.assertEqual(d.day, day)
1301
1302    def test_insane_fromtimestamp(self):
1303        # It's possible that some platform maps time_t to double,
1304        # and that this test will fail there.  This test should
1305        # exempt such platforms (provided they return reasonable
1306        # results!).
1307        for insane in -1e200, 1e200:
1308            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
1309                              insane)
1310
1311    def test_today(self):
1312        import time
1313
1314        # We claim that today() is like fromtimestamp(time.time()), so
1315        # prove it.
1316        for dummy in range(3):
1317            today = self.theclass.today()
1318            ts = time.time()
1319            todayagain = self.theclass.fromtimestamp(ts)
1320            if today == todayagain:
1321                break
1322            # There are several legit reasons that could fail:
1323            # 1. It recently became midnight, between the today() and the
1324            #    time() calls.
1325            # 2. The platform time() has such fine resolution that we'll
1326            #    never get the same value twice.
1327            # 3. The platform time() has poor resolution, and we just
1328            #    happened to call today() right before a resolution quantum
1329            #    boundary.
1330            # 4. The system clock got fiddled between calls.
1331            # In any case, wait a little while and try again.
1332            time.sleep(0.1)
1333
1334        # It worked or it didn't.  If it didn't, assume it's reason #2, and
1335        # let the test pass if they're within half a second of each other.
1336        if today != todayagain:
1337            self.assertAlmostEqual(todayagain, today,
1338                                   delta=timedelta(seconds=0.5))
1339
1340    def test_weekday(self):
1341        for i in range(7):
1342            # March 4, 2002 is a Monday
1343            self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i)
1344            self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1)
1345            # January 2, 1956 is a Monday
1346            self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i)
1347            self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1)
1348
1349    def test_isocalendar(self):
1350        # Check examples from
1351        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1352        for i in range(7):
1353            d = self.theclass(2003, 12, 22+i)
1354            self.assertEqual(d.isocalendar(), (2003, 52, i+1))
1355            d = self.theclass(2003, 12, 29) + timedelta(i)
1356            self.assertEqual(d.isocalendar(), (2004, 1, i+1))
1357            d = self.theclass(2004, 1, 5+i)
1358            self.assertEqual(d.isocalendar(), (2004, 2, i+1))
1359            d = self.theclass(2009, 12, 21+i)
1360            self.assertEqual(d.isocalendar(), (2009, 52, i+1))
1361            d = self.theclass(2009, 12, 28) + timedelta(i)
1362            self.assertEqual(d.isocalendar(), (2009, 53, i+1))
1363            d = self.theclass(2010, 1, 4+i)
1364            self.assertEqual(d.isocalendar(), (2010, 1, i+1))
1365
1366    def test_iso_long_years(self):
1367        # Calculate long ISO years and compare to table from
1368        # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
1369        ISO_LONG_YEARS_TABLE = """
1370              4   32   60   88
1371              9   37   65   93
1372             15   43   71   99
1373             20   48   76
1374             26   54   82
1375
1376            105  133  161  189
1377            111  139  167  195
1378            116  144  172
1379            122  150  178
1380            128  156  184
1381
1382            201  229  257  285
1383            207  235  263  291
1384            212  240  268  296
1385            218  246  274
1386            224  252  280
1387
1388            303  331  359  387
1389            308  336  364  392
1390            314  342  370  398
1391            320  348  376
1392            325  353  381
1393        """
1394        iso_long_years = sorted(map(int, ISO_LONG_YEARS_TABLE.split()))
1395        L = []
1396        for i in range(400):
1397            d = self.theclass(2000+i, 12, 31)
1398            d1 = self.theclass(1600+i, 12, 31)
1399            self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:])
1400            if d.isocalendar()[1] == 53:
1401                L.append(i)
1402        self.assertEqual(L, iso_long_years)
1403
1404    def test_isoformat(self):
1405        t = self.theclass(2, 3, 2)
1406        self.assertEqual(t.isoformat(), "0002-03-02")
1407
1408    def test_ctime(self):
1409        t = self.theclass(2002, 3, 2)
1410        self.assertEqual(t.ctime(), "Sat Mar  2 00:00:00 2002")
1411
1412    def test_strftime(self):
1413        t = self.theclass(2005, 3, 2)
1414        self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05")
1415        self.assertEqual(t.strftime(""), "") # SF bug #761337
1416        self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784
1417
1418        self.assertRaises(TypeError, t.strftime) # needs an arg
1419        self.assertRaises(TypeError, t.strftime, "one", "two") # too many args
1420        self.assertRaises(TypeError, t.strftime, 42) # arg wrong type
1421
1422        # test that unicode input is allowed (issue 2782)
1423        self.assertEqual(t.strftime("%m"), "03")
1424
1425        # A naive object replaces %z and %Z w/ empty strings.
1426        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
1427
1428        #make sure that invalid format specifiers are handled correctly
1429        #self.assertRaises(ValueError, t.strftime, "%e")
1430        #self.assertRaises(ValueError, t.strftime, "%")
1431        #self.assertRaises(ValueError, t.strftime, "%#")
1432
1433        #oh well, some systems just ignore those invalid ones.
1434        #at least, exercise them to make sure that no crashes
1435        #are generated
1436        for f in ["%e", "%", "%#"]:
1437            try:
1438                t.strftime(f)
1439            except ValueError:
1440                pass
1441
1442        # bpo-34482: Check that surrogates don't cause a crash.
1443        try:
1444            t.strftime('%y\ud800%m')
1445        except UnicodeEncodeError:
1446            pass
1447
1448        #check that this standard extension works
1449        t.strftime("%f")
1450
1451    def test_strftime_trailing_percent(self):
1452        # bpo-35066: Make sure trailing '%' doesn't cause datetime's strftime to
1453        # complain. Different libcs have different handling of trailing
1454        # percents, so we simply check datetime's strftime acts the same as
1455        # time.strftime.
1456        t = self.theclass(2005, 3, 2)
1457        try:
1458            _time.strftime('%')
1459        except ValueError:
1460            self.skipTest('time module does not support trailing %')
1461        self.assertEqual(t.strftime('%'), _time.strftime('%', t.timetuple()))
1462        self.assertEqual(
1463            t.strftime("m:%m d:%d y:%y %"),
1464            _time.strftime("m:03 d:02 y:05 %", t.timetuple()),
1465        )
1466
1467    def test_format(self):
1468        dt = self.theclass(2007, 9, 10)
1469        self.assertEqual(dt.__format__(''), str(dt))
1470
1471        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
1472            dt.__format__(123)
1473
1474        # check that a derived class's __str__() gets called
1475        class A(self.theclass):
1476            def __str__(self):
1477                return 'A'
1478        a = A(2007, 9, 10)
1479        self.assertEqual(a.__format__(''), 'A')
1480
1481        # check that a derived class's strftime gets called
1482        class B(self.theclass):
1483            def strftime(self, format_spec):
1484                return 'B'
1485        b = B(2007, 9, 10)
1486        self.assertEqual(b.__format__(''), str(dt))
1487
1488        for fmt in ["m:%m d:%d y:%y",
1489                    "m:%m d:%d y:%y H:%H M:%M S:%S",
1490                    "%z %Z",
1491                    ]:
1492            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
1493            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
1494            self.assertEqual(b.__format__(fmt), 'B')
1495
1496    def test_resolution_info(self):
1497        # XXX: Should min and max respect subclassing?
1498        if issubclass(self.theclass, datetime):
1499            expected_class = datetime
1500        else:
1501            expected_class = date
1502        self.assertIsInstance(self.theclass.min, expected_class)
1503        self.assertIsInstance(self.theclass.max, expected_class)
1504        self.assertIsInstance(self.theclass.resolution, timedelta)
1505        self.assertTrue(self.theclass.max > self.theclass.min)
1506
1507    def test_extreme_timedelta(self):
1508        big = self.theclass.max - self.theclass.min
1509        # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds
1510        n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds
1511        # n == 315537897599999999 ~= 2**58.13
1512        justasbig = timedelta(0, 0, n)
1513        self.assertEqual(big, justasbig)
1514        self.assertEqual(self.theclass.min + big, self.theclass.max)
1515        self.assertEqual(self.theclass.max - big, self.theclass.min)
1516
1517    def test_timetuple(self):
1518        for i in range(7):
1519            # January 2, 1956 is a Monday (0)
1520            d = self.theclass(1956, 1, 2+i)
1521            t = d.timetuple()
1522            self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1))
1523            # February 1, 1956 is a Wednesday (2)
1524            d = self.theclass(1956, 2, 1+i)
1525            t = d.timetuple()
1526            self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1))
1527            # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day
1528            # of the year.
1529            d = self.theclass(1956, 3, 1+i)
1530            t = d.timetuple()
1531            self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1))
1532            self.assertEqual(t.tm_year, 1956)
1533            self.assertEqual(t.tm_mon, 3)
1534            self.assertEqual(t.tm_mday, 1+i)
1535            self.assertEqual(t.tm_hour, 0)
1536            self.assertEqual(t.tm_min, 0)
1537            self.assertEqual(t.tm_sec, 0)
1538            self.assertEqual(t.tm_wday, (3+i)%7)
1539            self.assertEqual(t.tm_yday, 61+i)
1540            self.assertEqual(t.tm_isdst, -1)
1541
1542    def test_pickling(self):
1543        args = 6, 7, 23
1544        orig = self.theclass(*args)
1545        for pickler, unpickler, proto in pickle_choices:
1546            green = pickler.dumps(orig, proto)
1547            derived = unpickler.loads(green)
1548            self.assertEqual(orig, derived)
1549        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
1550
1551    def test_compat_unpickle(self):
1552        tests = [
1553            b"cdatetime\ndate\n(S'\\x07\\xdf\\x0b\\x1b'\ntR.",
1554            b'cdatetime\ndate\n(U\x04\x07\xdf\x0b\x1btR.',
1555            b'\x80\x02cdatetime\ndate\nU\x04\x07\xdf\x0b\x1b\x85R.',
1556        ]
1557        args = 2015, 11, 27
1558        expected = self.theclass(*args)
1559        for data in tests:
1560            for loads in pickle_loads:
1561                derived = loads(data, encoding='latin1')
1562                self.assertEqual(derived, expected)
1563
1564    def test_compare(self):
1565        t1 = self.theclass(2, 3, 4)
1566        t2 = self.theclass(2, 3, 4)
1567        self.assertEqual(t1, t2)
1568        self.assertTrue(t1 <= t2)
1569        self.assertTrue(t1 >= t2)
1570        self.assertFalse(t1 != t2)
1571        self.assertFalse(t1 < t2)
1572        self.assertFalse(t1 > t2)
1573
1574        for args in (3, 3, 3), (2, 4, 4), (2, 3, 5):
1575            t2 = self.theclass(*args)   # this is larger than t1
1576            self.assertTrue(t1 < t2)
1577            self.assertTrue(t2 > t1)
1578            self.assertTrue(t1 <= t2)
1579            self.assertTrue(t2 >= t1)
1580            self.assertTrue(t1 != t2)
1581            self.assertTrue(t2 != t1)
1582            self.assertFalse(t1 == t2)
1583            self.assertFalse(t2 == t1)
1584            self.assertFalse(t1 > t2)
1585            self.assertFalse(t2 < t1)
1586            self.assertFalse(t1 >= t2)
1587            self.assertFalse(t2 <= t1)
1588
1589        for badarg in OTHERSTUFF:
1590            self.assertEqual(t1 == badarg, False)
1591            self.assertEqual(t1 != badarg, True)
1592            self.assertEqual(badarg == t1, False)
1593            self.assertEqual(badarg != t1, True)
1594
1595            self.assertRaises(TypeError, lambda: t1 < badarg)
1596            self.assertRaises(TypeError, lambda: t1 > badarg)
1597            self.assertRaises(TypeError, lambda: t1 >= badarg)
1598            self.assertRaises(TypeError, lambda: badarg <= t1)
1599            self.assertRaises(TypeError, lambda: badarg < t1)
1600            self.assertRaises(TypeError, lambda: badarg > t1)
1601            self.assertRaises(TypeError, lambda: badarg >= t1)
1602
1603    def test_mixed_compare(self):
1604        our = self.theclass(2000, 4, 5)
1605
1606        # Our class can be compared for equality to other classes
1607        self.assertEqual(our == 1, False)
1608        self.assertEqual(1 == our, False)
1609        self.assertEqual(our != 1, True)
1610        self.assertEqual(1 != our, True)
1611
1612        # But the ordering is undefined
1613        self.assertRaises(TypeError, lambda: our < 1)
1614        self.assertRaises(TypeError, lambda: 1 < our)
1615
1616        # Repeat those tests with a different class
1617
1618        class SomeClass:
1619            pass
1620
1621        their = SomeClass()
1622        self.assertEqual(our == their, False)
1623        self.assertEqual(their == our, False)
1624        self.assertEqual(our != their, True)
1625        self.assertEqual(their != our, True)
1626        self.assertRaises(TypeError, lambda: our < their)
1627        self.assertRaises(TypeError, lambda: their < our)
1628
1629    def test_bool(self):
1630        # All dates are considered true.
1631        self.assertTrue(self.theclass.min)
1632        self.assertTrue(self.theclass.max)
1633
1634    def test_strftime_y2k(self):
1635        for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
1636            d = self.theclass(y, 1, 1)
1637            # Issue 13305:  For years < 1000, the value is not always
1638            # padded to 4 digits across platforms.  The C standard
1639            # assumes year >= 1900, so it does not specify the number
1640            # of digits.
1641            if d.strftime("%Y") != '%04d' % y:
1642                # Year 42 returns '42', not padded
1643                self.assertEqual(d.strftime("%Y"), '%d' % y)
1644                # '0042' is obtained anyway
1645                self.assertEqual(d.strftime("%4Y"), '%04d' % y)
1646
1647    def test_replace(self):
1648        cls = self.theclass
1649        args = [1, 2, 3]
1650        base = cls(*args)
1651        self.assertEqual(base, base.replace())
1652
1653        i = 0
1654        for name, newval in (("year", 2),
1655                             ("month", 3),
1656                             ("day", 4)):
1657            newargs = args[:]
1658            newargs[i] = newval
1659            expected = cls(*newargs)
1660            got = base.replace(**{name: newval})
1661            self.assertEqual(expected, got)
1662            i += 1
1663
1664        # Out of bounds.
1665        base = cls(2000, 2, 29)
1666        self.assertRaises(ValueError, base.replace, year=2001)
1667
1668    def test_subclass_replace(self):
1669        class DateSubclass(self.theclass):
1670            pass
1671
1672        dt = DateSubclass(2012, 1, 1)
1673        self.assertIs(type(dt.replace(year=2013)), DateSubclass)
1674
1675    def test_subclass_date(self):
1676
1677        class C(self.theclass):
1678            theAnswer = 42
1679
1680            def __new__(cls, *args, **kws):
1681                temp = kws.copy()
1682                extra = temp.pop('extra')
1683                result = self.theclass.__new__(cls, *args, **temp)
1684                result.extra = extra
1685                return result
1686
1687            def newmeth(self, start):
1688                return start + self.year + self.month
1689
1690        args = 2003, 4, 14
1691
1692        dt1 = self.theclass(*args)
1693        dt2 = C(*args, **{'extra': 7})
1694
1695        self.assertEqual(dt2.__class__, C)
1696        self.assertEqual(dt2.theAnswer, 42)
1697        self.assertEqual(dt2.extra, 7)
1698        self.assertEqual(dt1.toordinal(), dt2.toordinal())
1699        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7)
1700
1701    def test_subclass_alternate_constructors(self):
1702        # Test that alternate constructors call the constructor
1703        class DateSubclass(self.theclass):
1704            def __new__(cls, *args, **kwargs):
1705                result = self.theclass.__new__(cls, *args, **kwargs)
1706                result.extra = 7
1707
1708                return result
1709
1710        args = (2003, 4, 14)
1711        d_ord = 731319              # Equivalent ordinal date
1712        d_isoformat = '2003-04-14'  # Equivalent isoformat()
1713
1714        base_d = DateSubclass(*args)
1715        self.assertIsInstance(base_d, DateSubclass)
1716        self.assertEqual(base_d.extra, 7)
1717
1718        # Timestamp depends on time zone, so we'll calculate the equivalent here
1719        ts = datetime.combine(base_d, time(0)).timestamp()
1720
1721        test_cases = [
1722            ('fromordinal', (d_ord,)),
1723            ('fromtimestamp', (ts,)),
1724            ('fromisoformat', (d_isoformat,)),
1725        ]
1726
1727        for constr_name, constr_args in test_cases:
1728            for base_obj in (DateSubclass, base_d):
1729                # Test both the classmethod and method
1730                with self.subTest(base_obj_type=type(base_obj),
1731                                  constr_name=constr_name):
1732                    constr = getattr(base_obj, constr_name)
1733
1734                    dt = constr(*constr_args)
1735
1736                    # Test that it creates the right subclass
1737                    self.assertIsInstance(dt, DateSubclass)
1738
1739                    # Test that it's equal to the base object
1740                    self.assertEqual(dt, base_d)
1741
1742                    # Test that it called the constructor
1743                    self.assertEqual(dt.extra, 7)
1744
1745    def test_pickling_subclass_date(self):
1746
1747        args = 6, 7, 23
1748        orig = SubclassDate(*args)
1749        for pickler, unpickler, proto in pickle_choices:
1750            green = pickler.dumps(orig, proto)
1751            derived = unpickler.loads(green)
1752            self.assertEqual(orig, derived)
1753
1754    def test_backdoor_resistance(self):
1755        # For fast unpickling, the constructor accepts a pickle byte string.
1756        # This is a low-overhead backdoor.  A user can (by intent or
1757        # mistake) pass a string directly, which (if it's the right length)
1758        # will get treated like a pickle, and bypass the normal sanity
1759        # checks in the constructor.  This can create insane objects.
1760        # The constructor doesn't want to burn the time to validate all
1761        # fields, but does check the month field.  This stops, e.g.,
1762        # datetime.datetime('1995-03-25') from yielding an insane object.
1763        base = b'1995-03-25'
1764        if not issubclass(self.theclass, datetime):
1765            base = base[:4]
1766        for month_byte in b'9', b'\0', b'\r', b'\xff':
1767            self.assertRaises(TypeError, self.theclass,
1768                                         base[:2] + month_byte + base[3:])
1769        if issubclass(self.theclass, datetime):
1770            # Good bytes, but bad tzinfo:
1771            with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
1772                self.theclass(bytes([1] * len(base)), 'EST')
1773
1774        for ord_byte in range(1, 13):
1775            # This shouldn't blow up because of the month byte alone.  If
1776            # the implementation changes to do more-careful checking, it may
1777            # blow up because other fields are insane.
1778            self.theclass(base[:2] + bytes([ord_byte]) + base[3:])
1779
1780    def test_fromisoformat(self):
1781        # Test that isoformat() is reversible
1782        base_dates = [
1783            (1, 1, 1),
1784            (1000, 2, 14),
1785            (1900, 1, 1),
1786            (2000, 2, 29),
1787            (2004, 11, 12),
1788            (2004, 4, 3),
1789            (2017, 5, 30)
1790        ]
1791
1792        for dt_tuple in base_dates:
1793            dt = self.theclass(*dt_tuple)
1794            dt_str = dt.isoformat()
1795            with self.subTest(dt_str=dt_str):
1796                dt_rt = self.theclass.fromisoformat(dt.isoformat())
1797
1798                self.assertEqual(dt, dt_rt)
1799
1800    def test_fromisoformat_subclass(self):
1801        class DateSubclass(self.theclass):
1802            pass
1803
1804        dt = DateSubclass(2014, 12, 14)
1805
1806        dt_rt = DateSubclass.fromisoformat(dt.isoformat())
1807
1808        self.assertIsInstance(dt_rt, DateSubclass)
1809
1810    def test_fromisoformat_fails(self):
1811        # Test that fromisoformat() fails on invalid values
1812        bad_strs = [
1813            '',                 # Empty string
1814            '\ud800',           # bpo-34454: Surrogate code point
1815            '009-03-04',        # Not 10 characters
1816            '123456789',        # Not a date
1817            '200a-12-04',       # Invalid character in year
1818            '2009-1a-04',       # Invalid character in month
1819            '2009-12-0a',       # Invalid character in day
1820            '2009-01-32',       # Invalid day
1821            '2009-02-29',       # Invalid leap day
1822            '20090228',         # Valid ISO8601 output not from isoformat()
1823            '2009\ud80002\ud80028',     # Separators are surrogate codepoints
1824        ]
1825
1826        for bad_str in bad_strs:
1827            with self.assertRaises(ValueError):
1828                self.theclass.fromisoformat(bad_str)
1829
1830    def test_fromisoformat_fails_typeerror(self):
1831        # Test that fromisoformat fails when passed the wrong type
1832        import io
1833
1834        bad_types = [b'2009-03-01', None, io.StringIO('2009-03-01')]
1835        for bad_type in bad_types:
1836            with self.assertRaises(TypeError):
1837                self.theclass.fromisoformat(bad_type)
1838
1839    def test_fromisocalendar(self):
1840        # For each test case, assert that fromisocalendar is the
1841        # inverse of the isocalendar function
1842        dates = [
1843            (2016, 4, 3),
1844            (2005, 1, 2),       # (2004, 53, 7)
1845            (2008, 12, 30),     # (2009, 1, 2)
1846            (2010, 1, 2),       # (2009, 53, 6)
1847            (2009, 12, 31),     # (2009, 53, 4)
1848            (1900, 1, 1),       # Unusual non-leap year (year % 100 == 0)
1849            (1900, 12, 31),
1850            (2000, 1, 1),       # Unusual leap year (year % 400 == 0)
1851            (2000, 12, 31),
1852            (2004, 1, 1),       # Leap year
1853            (2004, 12, 31),
1854            (1, 1, 1),
1855            (9999, 12, 31),
1856            (MINYEAR, 1, 1),
1857            (MAXYEAR, 12, 31),
1858        ]
1859
1860        for datecomps in dates:
1861            with self.subTest(datecomps=datecomps):
1862                dobj = self.theclass(*datecomps)
1863                isocal = dobj.isocalendar()
1864
1865                d_roundtrip = self.theclass.fromisocalendar(*isocal)
1866
1867                self.assertEqual(dobj, d_roundtrip)
1868
1869    def test_fromisocalendar_value_errors(self):
1870        isocals = [
1871            (2019, 0, 1),
1872            (2019, -1, 1),
1873            (2019, 54, 1),
1874            (2019, 1, 0),
1875            (2019, 1, -1),
1876            (2019, 1, 8),
1877            (2019, 53, 1),
1878            (10000, 1, 1),
1879            (0, 1, 1),
1880            (9999999, 1, 1),
1881            (2<<32, 1, 1),
1882            (2019, 2<<32, 1),
1883            (2019, 1, 2<<32),
1884        ]
1885
1886        for isocal in isocals:
1887            with self.subTest(isocal=isocal):
1888                with self.assertRaises(ValueError):
1889                    self.theclass.fromisocalendar(*isocal)
1890
1891    def test_fromisocalendar_type_errors(self):
1892        err_txformers = [
1893            str,
1894            float,
1895            lambda x: None,
1896        ]
1897
1898        # Take a valid base tuple and transform it to contain one argument
1899        # with the wrong type. Repeat this for each argument, e.g.
1900        # [("2019", 1, 1), (2019, "1", 1), (2019, 1, "1"), ...]
1901        isocals = []
1902        base = (2019, 1, 1)
1903        for i in range(3):
1904            for txformer in err_txformers:
1905                err_val = list(base)
1906                err_val[i] = txformer(err_val[i])
1907                isocals.append(tuple(err_val))
1908
1909        for isocal in isocals:
1910            with self.subTest(isocal=isocal):
1911                with self.assertRaises(TypeError):
1912                    self.theclass.fromisocalendar(*isocal)
1913
1914
1915#############################################################################
1916# datetime tests
1917
1918class SubclassDatetime(datetime):
1919    sub_var = 1
1920
1921class TestDateTime(TestDate):
1922
1923    theclass = datetime
1924
1925    def test_basic_attributes(self):
1926        dt = self.theclass(2002, 3, 1, 12, 0)
1927        self.assertEqual(dt.year, 2002)
1928        self.assertEqual(dt.month, 3)
1929        self.assertEqual(dt.day, 1)
1930        self.assertEqual(dt.hour, 12)
1931        self.assertEqual(dt.minute, 0)
1932        self.assertEqual(dt.second, 0)
1933        self.assertEqual(dt.microsecond, 0)
1934
1935    def test_basic_attributes_nonzero(self):
1936        # Make sure all attributes are non-zero so bugs in
1937        # bit-shifting access show up.
1938        dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000)
1939        self.assertEqual(dt.year, 2002)
1940        self.assertEqual(dt.month, 3)
1941        self.assertEqual(dt.day, 1)
1942        self.assertEqual(dt.hour, 12)
1943        self.assertEqual(dt.minute, 59)
1944        self.assertEqual(dt.second, 59)
1945        self.assertEqual(dt.microsecond, 8000)
1946
1947    def test_roundtrip(self):
1948        for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7),
1949                   self.theclass.now()):
1950            # Verify dt -> string -> datetime identity.
1951            s = repr(dt)
1952            self.assertTrue(s.startswith('datetime.'))
1953            s = s[9:]
1954            dt2 = eval(s)
1955            self.assertEqual(dt, dt2)
1956
1957            # Verify identity via reconstructing from pieces.
1958            dt2 = self.theclass(dt.year, dt.month, dt.day,
1959                                dt.hour, dt.minute, dt.second,
1960                                dt.microsecond)
1961            self.assertEqual(dt, dt2)
1962
1963    def test_isoformat(self):
1964        t = self.theclass(1, 2, 3, 4, 5, 1, 123)
1965        self.assertEqual(t.isoformat(),    "0001-02-03T04:05:01.000123")
1966        self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123")
1967        self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123")
1968        self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123")
1969        # bpo-34482: Check that surrogates are handled properly.
1970        self.assertEqual(t.isoformat('\ud800'),
1971                         "0001-02-03\ud80004:05:01.000123")
1972        self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04")
1973        self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05")
1974        self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01")
1975        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1976        self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123")
1977        self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123")
1978        self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05")
1979        self.assertRaises(ValueError, t.isoformat, timespec='foo')
1980        # bpo-34482: Check that surrogates are handled properly.
1981        self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
1982        # str is ISO format with the separator forced to a blank.
1983        self.assertEqual(str(t), "0001-02-03 04:05:01.000123")
1984
1985        t = self.theclass(1, 2, 3, 4, 5, 1, 999500, tzinfo=timezone.utc)
1986        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999+00:00")
1987
1988        t = self.theclass(1, 2, 3, 4, 5, 1, 999500)
1989        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.999")
1990
1991        t = self.theclass(1, 2, 3, 4, 5, 1)
1992        self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01")
1993        self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000")
1994        self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000000")
1995
1996        t = self.theclass(2, 3, 2)
1997        self.assertEqual(t.isoformat(),    "0002-03-02T00:00:00")
1998        self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00")
1999        self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00")
2000        # str is ISO format with the separator forced to a blank.
2001        self.assertEqual(str(t), "0002-03-02 00:00:00")
2002        # ISO format with timezone
2003        tz = FixedOffset(timedelta(seconds=16), 'XXX')
2004        t = self.theclass(2, 3, 2, tzinfo=tz)
2005        self.assertEqual(t.isoformat(), "0002-03-02T00:00:00+00:00:16")
2006
2007    def test_isoformat_timezone(self):
2008        tzoffsets = [
2009            ('05:00', timedelta(hours=5)),
2010            ('02:00', timedelta(hours=2)),
2011            ('06:27', timedelta(hours=6, minutes=27)),
2012            ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
2013            ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
2014        ]
2015
2016        tzinfos = [
2017            ('', None),
2018            ('+00:00', timezone.utc),
2019            ('+00:00', timezone(timedelta(0))),
2020        ]
2021
2022        tzinfos += [
2023            (prefix + expected, timezone(sign * td))
2024            for expected, td in tzoffsets
2025            for prefix, sign in [('-', -1), ('+', 1)]
2026        ]
2027
2028        dt_base = self.theclass(2016, 4, 1, 12, 37, 9)
2029        exp_base = '2016-04-01T12:37:09'
2030
2031        for exp_tz, tzi in tzinfos:
2032            dt = dt_base.replace(tzinfo=tzi)
2033            exp = exp_base + exp_tz
2034            with self.subTest(tzi=tzi):
2035                assert dt.isoformat() == exp
2036
2037    def test_format(self):
2038        dt = self.theclass(2007, 9, 10, 4, 5, 1, 123)
2039        self.assertEqual(dt.__format__(''), str(dt))
2040
2041        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
2042            dt.__format__(123)
2043
2044        # check that a derived class's __str__() gets called
2045        class A(self.theclass):
2046            def __str__(self):
2047                return 'A'
2048        a = A(2007, 9, 10, 4, 5, 1, 123)
2049        self.assertEqual(a.__format__(''), 'A')
2050
2051        # check that a derived class's strftime gets called
2052        class B(self.theclass):
2053            def strftime(self, format_spec):
2054                return 'B'
2055        b = B(2007, 9, 10, 4, 5, 1, 123)
2056        self.assertEqual(b.__format__(''), str(dt))
2057
2058        for fmt in ["m:%m d:%d y:%y",
2059                    "m:%m d:%d y:%y H:%H M:%M S:%S",
2060                    "%z %Z",
2061                    ]:
2062            self.assertEqual(dt.__format__(fmt), dt.strftime(fmt))
2063            self.assertEqual(a.__format__(fmt), dt.strftime(fmt))
2064            self.assertEqual(b.__format__(fmt), 'B')
2065
2066    def test_more_ctime(self):
2067        # Test fields that TestDate doesn't touch.
2068        import time
2069
2070        t = self.theclass(2002, 3, 2, 18, 3, 5, 123)
2071        self.assertEqual(t.ctime(), "Sat Mar  2 18:03:05 2002")
2072        # Oops!  The next line fails on Win2K under MSVC 6, so it's commented
2073        # out.  The difference is that t.ctime() produces " 2" for the day,
2074        # but platform ctime() produces "02" for the day.  According to
2075        # C99, t.ctime() is correct here.
2076        # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2077
2078        # So test a case where that difference doesn't matter.
2079        t = self.theclass(2002, 3, 22, 18, 3, 5, 123)
2080        self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple())))
2081
2082    def test_tz_independent_comparing(self):
2083        dt1 = self.theclass(2002, 3, 1, 9, 0, 0)
2084        dt2 = self.theclass(2002, 3, 1, 10, 0, 0)
2085        dt3 = self.theclass(2002, 3, 1, 9, 0, 0)
2086        self.assertEqual(dt1, dt3)
2087        self.assertTrue(dt2 > dt3)
2088
2089        # Make sure comparison doesn't forget microseconds, and isn't done
2090        # via comparing a float timestamp (an IEEE double doesn't have enough
2091        # precision to span microsecond resolution across years 1 through 9999,
2092        # so comparing via timestamp necessarily calls some distinct values
2093        # equal).
2094        dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998)
2095        us = timedelta(microseconds=1)
2096        dt2 = dt1 + us
2097        self.assertEqual(dt2 - dt1, us)
2098        self.assertTrue(dt1 < dt2)
2099
2100    def test_strftime_with_bad_tzname_replace(self):
2101        # verify ok if tzinfo.tzname().replace() returns a non-string
2102        class MyTzInfo(FixedOffset):
2103            def tzname(self, dt):
2104                class MyStr(str):
2105                    def replace(self, *args):
2106                        return None
2107                return MyStr('name')
2108        t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name'))
2109        self.assertRaises(TypeError, t.strftime, '%Z')
2110
2111    def test_bad_constructor_arguments(self):
2112        # bad years
2113        self.theclass(MINYEAR, 1, 1)  # no exception
2114        self.theclass(MAXYEAR, 1, 1)  # no exception
2115        self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1)
2116        self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1)
2117        # bad months
2118        self.theclass(2000, 1, 1)    # no exception
2119        self.theclass(2000, 12, 1)   # no exception
2120        self.assertRaises(ValueError, self.theclass, 2000, 0, 1)
2121        self.assertRaises(ValueError, self.theclass, 2000, 13, 1)
2122        # bad days
2123        self.theclass(2000, 2, 29)   # no exception
2124        self.theclass(2004, 2, 29)   # no exception
2125        self.theclass(2400, 2, 29)   # no exception
2126        self.assertRaises(ValueError, self.theclass, 2000, 2, 30)
2127        self.assertRaises(ValueError, self.theclass, 2001, 2, 29)
2128        self.assertRaises(ValueError, self.theclass, 2100, 2, 29)
2129        self.assertRaises(ValueError, self.theclass, 1900, 2, 29)
2130        self.assertRaises(ValueError, self.theclass, 2000, 1, 0)
2131        self.assertRaises(ValueError, self.theclass, 2000, 1, 32)
2132        # bad hours
2133        self.theclass(2000, 1, 31, 0)    # no exception
2134        self.theclass(2000, 1, 31, 23)   # no exception
2135        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1)
2136        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24)
2137        # bad minutes
2138        self.theclass(2000, 1, 31, 23, 0)    # no exception
2139        self.theclass(2000, 1, 31, 23, 59)   # no exception
2140        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1)
2141        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60)
2142        # bad seconds
2143        self.theclass(2000, 1, 31, 23, 59, 0)    # no exception
2144        self.theclass(2000, 1, 31, 23, 59, 59)   # no exception
2145        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1)
2146        self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60)
2147        # bad microseconds
2148        self.theclass(2000, 1, 31, 23, 59, 59, 0)    # no exception
2149        self.theclass(2000, 1, 31, 23, 59, 59, 999999)   # no exception
2150        self.assertRaises(ValueError, self.theclass,
2151                          2000, 1, 31, 23, 59, 59, -1)
2152        self.assertRaises(ValueError, self.theclass,
2153                          2000, 1, 31, 23, 59, 59,
2154                          1000000)
2155        # bad fold
2156        self.assertRaises(ValueError, self.theclass,
2157                          2000, 1, 31, fold=-1)
2158        self.assertRaises(ValueError, self.theclass,
2159                          2000, 1, 31, fold=2)
2160        # Positional fold:
2161        self.assertRaises(TypeError, self.theclass,
2162                          2000, 1, 31, 23, 59, 59, 0, None, 1)
2163
2164    def test_hash_equality(self):
2165        d = self.theclass(2000, 12, 31, 23, 30, 17)
2166        e = self.theclass(2000, 12, 31, 23, 30, 17)
2167        self.assertEqual(d, e)
2168        self.assertEqual(hash(d), hash(e))
2169
2170        dic = {d: 1}
2171        dic[e] = 2
2172        self.assertEqual(len(dic), 1)
2173        self.assertEqual(dic[d], 2)
2174        self.assertEqual(dic[e], 2)
2175
2176        d = self.theclass(2001,  1,  1,  0,  5, 17)
2177        e = self.theclass(2001,  1,  1,  0,  5, 17)
2178        self.assertEqual(d, e)
2179        self.assertEqual(hash(d), hash(e))
2180
2181        dic = {d: 1}
2182        dic[e] = 2
2183        self.assertEqual(len(dic), 1)
2184        self.assertEqual(dic[d], 2)
2185        self.assertEqual(dic[e], 2)
2186
2187    def test_computations(self):
2188        a = self.theclass(2002, 1, 31)
2189        b = self.theclass(1956, 1, 31)
2190        diff = a-b
2191        self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4)))
2192        self.assertEqual(diff.seconds, 0)
2193        self.assertEqual(diff.microseconds, 0)
2194        a = self.theclass(2002, 3, 2, 17, 6)
2195        millisec = timedelta(0, 0, 1000)
2196        hour = timedelta(0, 3600)
2197        day = timedelta(1)
2198        week = timedelta(7)
2199        self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6))
2200        self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6))
2201        self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6))
2202        self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6))
2203        self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6))
2204        self.assertEqual(a - hour, a + -hour)
2205        self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6))
2206        self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6))
2207        self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6))
2208        self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6))
2209        self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6))
2210        self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6))
2211        self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6))
2212        self.assertEqual((a + week) - a, week)
2213        self.assertEqual((a + day) - a, day)
2214        self.assertEqual((a + hour) - a, hour)
2215        self.assertEqual((a + millisec) - a, millisec)
2216        self.assertEqual((a - week) - a, -week)
2217        self.assertEqual((a - day) - a, -day)
2218        self.assertEqual((a - hour) - a, -hour)
2219        self.assertEqual((a - millisec) - a, -millisec)
2220        self.assertEqual(a - (a + week), -week)
2221        self.assertEqual(a - (a + day), -day)
2222        self.assertEqual(a - (a + hour), -hour)
2223        self.assertEqual(a - (a + millisec), -millisec)
2224        self.assertEqual(a - (a - week), week)
2225        self.assertEqual(a - (a - day), day)
2226        self.assertEqual(a - (a - hour), hour)
2227        self.assertEqual(a - (a - millisec), millisec)
2228        self.assertEqual(a + (week + day + hour + millisec),
2229                         self.theclass(2002, 3, 10, 18, 6, 0, 1000))
2230        self.assertEqual(a + (week + day + hour + millisec),
2231                         (((a + week) + day) + hour) + millisec)
2232        self.assertEqual(a - (week + day + hour + millisec),
2233                         self.theclass(2002, 2, 22, 16, 5, 59, 999000))
2234        self.assertEqual(a - (week + day + hour + millisec),
2235                         (((a - week) - day) - hour) - millisec)
2236        # Add/sub ints or floats should be illegal
2237        for i in 1, 1.0:
2238            self.assertRaises(TypeError, lambda: a+i)
2239            self.assertRaises(TypeError, lambda: a-i)
2240            self.assertRaises(TypeError, lambda: i+a)
2241            self.assertRaises(TypeError, lambda: i-a)
2242
2243        # delta - datetime is senseless.
2244        self.assertRaises(TypeError, lambda: day - a)
2245        # mixing datetime and (delta or datetime) via * or // is senseless
2246        self.assertRaises(TypeError, lambda: day * a)
2247        self.assertRaises(TypeError, lambda: a * day)
2248        self.assertRaises(TypeError, lambda: day // a)
2249        self.assertRaises(TypeError, lambda: a // day)
2250        self.assertRaises(TypeError, lambda: a * a)
2251        self.assertRaises(TypeError, lambda: a // a)
2252        # datetime + datetime is senseless
2253        self.assertRaises(TypeError, lambda: a + a)
2254
2255    def test_pickling(self):
2256        args = 6, 7, 23, 20, 59, 1, 64**2
2257        orig = self.theclass(*args)
2258        for pickler, unpickler, proto in pickle_choices:
2259            green = pickler.dumps(orig, proto)
2260            derived = unpickler.loads(green)
2261            self.assertEqual(orig, derived)
2262        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
2263
2264    def test_more_pickling(self):
2265        a = self.theclass(2003, 2, 7, 16, 48, 37, 444116)
2266        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
2267            s = pickle.dumps(a, proto)
2268            b = pickle.loads(s)
2269            self.assertEqual(b.year, 2003)
2270            self.assertEqual(b.month, 2)
2271            self.assertEqual(b.day, 7)
2272
2273    def test_pickling_subclass_datetime(self):
2274        args = 6, 7, 23, 20, 59, 1, 64**2
2275        orig = SubclassDatetime(*args)
2276        for pickler, unpickler, proto in pickle_choices:
2277            green = pickler.dumps(orig, proto)
2278            derived = unpickler.loads(green)
2279            self.assertEqual(orig, derived)
2280
2281    def test_compat_unpickle(self):
2282        tests = [
2283            b'cdatetime\ndatetime\n('
2284            b"S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x00\\x10\\x00'\ntR.",
2285
2286            b'cdatetime\ndatetime\n('
2287            b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00tR.',
2288
2289            b'\x80\x02cdatetime\ndatetime\n'
2290            b'U\n\x07\xdf\x0b\x1b\x14;\x01\x00\x10\x00\x85R.',
2291        ]
2292        args = 2015, 11, 27, 20, 59, 1, 64**2
2293        expected = self.theclass(*args)
2294        for data in tests:
2295            for loads in pickle_loads:
2296                derived = loads(data, encoding='latin1')
2297                self.assertEqual(derived, expected)
2298
2299    def test_more_compare(self):
2300        # The test_compare() inherited from TestDate covers the error cases.
2301        # We just want to test lexicographic ordering on the members datetime
2302        # has that date lacks.
2303        args = [2000, 11, 29, 20, 58, 16, 999998]
2304        t1 = self.theclass(*args)
2305        t2 = self.theclass(*args)
2306        self.assertEqual(t1, t2)
2307        self.assertTrue(t1 <= t2)
2308        self.assertTrue(t1 >= t2)
2309        self.assertFalse(t1 != t2)
2310        self.assertFalse(t1 < t2)
2311        self.assertFalse(t1 > t2)
2312
2313        for i in range(len(args)):
2314            newargs = args[:]
2315            newargs[i] = args[i] + 1
2316            t2 = self.theclass(*newargs)   # this is larger than t1
2317            self.assertTrue(t1 < t2)
2318            self.assertTrue(t2 > t1)
2319            self.assertTrue(t1 <= t2)
2320            self.assertTrue(t2 >= t1)
2321            self.assertTrue(t1 != t2)
2322            self.assertTrue(t2 != t1)
2323            self.assertFalse(t1 == t2)
2324            self.assertFalse(t2 == t1)
2325            self.assertFalse(t1 > t2)
2326            self.assertFalse(t2 < t1)
2327            self.assertFalse(t1 >= t2)
2328            self.assertFalse(t2 <= t1)
2329
2330
2331    # A helper for timestamp constructor tests.
2332    def verify_field_equality(self, expected, got):
2333        self.assertEqual(expected.tm_year, got.year)
2334        self.assertEqual(expected.tm_mon, got.month)
2335        self.assertEqual(expected.tm_mday, got.day)
2336        self.assertEqual(expected.tm_hour, got.hour)
2337        self.assertEqual(expected.tm_min, got.minute)
2338        self.assertEqual(expected.tm_sec, got.second)
2339
2340    def test_fromtimestamp(self):
2341        import time
2342
2343        ts = time.time()
2344        expected = time.localtime(ts)
2345        got = self.theclass.fromtimestamp(ts)
2346        self.verify_field_equality(expected, got)
2347
2348    def test_utcfromtimestamp(self):
2349        import time
2350
2351        ts = time.time()
2352        expected = time.gmtime(ts)
2353        got = self.theclass.utcfromtimestamp(ts)
2354        self.verify_field_equality(expected, got)
2355
2356    # Run with US-style DST rules: DST begins 2 a.m. on second Sunday in
2357    # March (M3.2.0) and ends 2 a.m. on first Sunday in November (M11.1.0).
2358    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
2359    def test_timestamp_naive(self):
2360        t = self.theclass(1970, 1, 1)
2361        self.assertEqual(t.timestamp(), 18000.0)
2362        t = self.theclass(1970, 1, 1, 1, 2, 3, 4)
2363        self.assertEqual(t.timestamp(),
2364                         18000.0 + 3600 + 2*60 + 3 + 4*1e-6)
2365        # Missing hour
2366        t0 = self.theclass(2012, 3, 11, 2, 30)
2367        t1 = t0.replace(fold=1)
2368        self.assertEqual(self.theclass.fromtimestamp(t1.timestamp()),
2369                         t0 - timedelta(hours=1))
2370        self.assertEqual(self.theclass.fromtimestamp(t0.timestamp()),
2371                         t1 + timedelta(hours=1))
2372        # Ambiguous hour defaults to DST
2373        t = self.theclass(2012, 11, 4, 1, 30)
2374        self.assertEqual(self.theclass.fromtimestamp(t.timestamp()), t)
2375
2376        # Timestamp may raise an overflow error on some platforms
2377        # XXX: Do we care to support the first and last year?
2378        for t in [self.theclass(2,1,1), self.theclass(9998,12,12)]:
2379            try:
2380                s = t.timestamp()
2381            except OverflowError:
2382                pass
2383            else:
2384                self.assertEqual(self.theclass.fromtimestamp(s), t)
2385
2386    def test_timestamp_aware(self):
2387        t = self.theclass(1970, 1, 1, tzinfo=timezone.utc)
2388        self.assertEqual(t.timestamp(), 0.0)
2389        t = self.theclass(1970, 1, 1, 1, 2, 3, 4, tzinfo=timezone.utc)
2390        self.assertEqual(t.timestamp(),
2391                         3600 + 2*60 + 3 + 4*1e-6)
2392        t = self.theclass(1970, 1, 1, 1, 2, 3, 4,
2393                          tzinfo=timezone(timedelta(hours=-5), 'EST'))
2394        self.assertEqual(t.timestamp(),
2395                         18000 + 3600 + 2*60 + 3 + 4*1e-6)
2396
2397    @support.run_with_tz('MSK-03')  # Something east of Greenwich
2398    def test_microsecond_rounding(self):
2399        for fts in [self.theclass.fromtimestamp,
2400                    self.theclass.utcfromtimestamp]:
2401            zero = fts(0)
2402            self.assertEqual(zero.second, 0)
2403            self.assertEqual(zero.microsecond, 0)
2404            one = fts(1e-6)
2405            try:
2406                minus_one = fts(-1e-6)
2407            except OSError:
2408                # localtime(-1) and gmtime(-1) is not supported on Windows
2409                pass
2410            else:
2411                self.assertEqual(minus_one.second, 59)
2412                self.assertEqual(minus_one.microsecond, 999999)
2413
2414                t = fts(-1e-8)
2415                self.assertEqual(t, zero)
2416                t = fts(-9e-7)
2417                self.assertEqual(t, minus_one)
2418                t = fts(-1e-7)
2419                self.assertEqual(t, zero)
2420                t = fts(-1/2**7)
2421                self.assertEqual(t.second, 59)
2422                self.assertEqual(t.microsecond, 992188)
2423
2424            t = fts(1e-7)
2425            self.assertEqual(t, zero)
2426            t = fts(9e-7)
2427            self.assertEqual(t, one)
2428            t = fts(0.99999949)
2429            self.assertEqual(t.second, 0)
2430            self.assertEqual(t.microsecond, 999999)
2431            t = fts(0.9999999)
2432            self.assertEqual(t.second, 1)
2433            self.assertEqual(t.microsecond, 0)
2434            t = fts(1/2**7)
2435            self.assertEqual(t.second, 0)
2436            self.assertEqual(t.microsecond, 7812)
2437
2438    def test_timestamp_limits(self):
2439        # minimum timestamp
2440        min_dt = self.theclass.min.replace(tzinfo=timezone.utc)
2441        min_ts = min_dt.timestamp()
2442        try:
2443            # date 0001-01-01 00:00:00+00:00: timestamp=-62135596800
2444            self.assertEqual(self.theclass.fromtimestamp(min_ts, tz=timezone.utc),
2445                             min_dt)
2446        except (OverflowError, OSError) as exc:
2447            # the date 0001-01-01 doesn't fit into 32-bit time_t,
2448            # or platform doesn't support such very old date
2449            self.skipTest(str(exc))
2450
2451        # maximum timestamp: set seconds to zero to avoid rounding issues
2452        max_dt = self.theclass.max.replace(tzinfo=timezone.utc,
2453                                           second=0, microsecond=0)
2454        max_ts = max_dt.timestamp()
2455        # date 9999-12-31 23:59:00+00:00: timestamp 253402300740
2456        self.assertEqual(self.theclass.fromtimestamp(max_ts, tz=timezone.utc),
2457                         max_dt)
2458
2459        # number of seconds greater than 1 year: make sure that the new date
2460        # is not valid in datetime.datetime limits
2461        delta = 3600 * 24 * 400
2462
2463        # too small
2464        ts = min_ts - delta
2465        # converting a Python int to C time_t can raise a OverflowError,
2466        # especially on 32-bit platforms.
2467        with self.assertRaises((ValueError, OverflowError)):
2468            self.theclass.fromtimestamp(ts)
2469        with self.assertRaises((ValueError, OverflowError)):
2470            self.theclass.utcfromtimestamp(ts)
2471
2472        # too big
2473        ts = max_dt.timestamp() + delta
2474        with self.assertRaises((ValueError, OverflowError)):
2475            self.theclass.fromtimestamp(ts)
2476        with self.assertRaises((ValueError, OverflowError)):
2477            self.theclass.utcfromtimestamp(ts)
2478
2479    def test_insane_fromtimestamp(self):
2480        # It's possible that some platform maps time_t to double,
2481        # and that this test will fail there.  This test should
2482        # exempt such platforms (provided they return reasonable
2483        # results!).
2484        for insane in -1e200, 1e200:
2485            self.assertRaises(OverflowError, self.theclass.fromtimestamp,
2486                              insane)
2487
2488    def test_insane_utcfromtimestamp(self):
2489        # It's possible that some platform maps time_t to double,
2490        # and that this test will fail there.  This test should
2491        # exempt such platforms (provided they return reasonable
2492        # results!).
2493        for insane in -1e200, 1e200:
2494            self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
2495                              insane)
2496
2497    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2498    def test_negative_float_fromtimestamp(self):
2499        # The result is tz-dependent; at least test that this doesn't
2500        # fail (like it did before bug 1646728 was fixed).
2501        self.theclass.fromtimestamp(-1.05)
2502
2503    @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
2504    def test_negative_float_utcfromtimestamp(self):
2505        d = self.theclass.utcfromtimestamp(-1.05)
2506        self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000))
2507
2508    def test_utcnow(self):
2509        import time
2510
2511        # Call it a success if utcnow() and utcfromtimestamp() are within
2512        # a second of each other.
2513        tolerance = timedelta(seconds=1)
2514        for dummy in range(3):
2515            from_now = self.theclass.utcnow()
2516            from_timestamp = self.theclass.utcfromtimestamp(time.time())
2517            if abs(from_timestamp - from_now) <= tolerance:
2518                break
2519            # Else try again a few times.
2520        self.assertLessEqual(abs(from_timestamp - from_now), tolerance)
2521
2522    def test_strptime(self):
2523        string = '2004-12-01 13:02:47.197'
2524        format = '%Y-%m-%d %H:%M:%S.%f'
2525        expected = _strptime._strptime_datetime(self.theclass, string, format)
2526        got = self.theclass.strptime(string, format)
2527        self.assertEqual(expected, got)
2528        self.assertIs(type(expected), self.theclass)
2529        self.assertIs(type(got), self.theclass)
2530
2531        # bpo-34482: Check that surrogates are handled properly.
2532        inputs = [
2533            ('2004-12-01\ud80013:02:47.197', '%Y-%m-%d\ud800%H:%M:%S.%f'),
2534            ('2004\ud80012-01 13:02:47.197', '%Y\ud800%m-%d %H:%M:%S.%f'),
2535            ('2004-12-01 13:02\ud80047.197', '%Y-%m-%d %H:%M\ud800%S.%f'),
2536        ]
2537        for string, format in inputs:
2538            with self.subTest(string=string, format=format):
2539                expected = _strptime._strptime_datetime(self.theclass, string,
2540                                                        format)
2541                got = self.theclass.strptime(string, format)
2542                self.assertEqual(expected, got)
2543
2544        strptime = self.theclass.strptime
2545
2546        self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
2547        self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
2548        self.assertEqual(
2549            strptime("-00:02:01.000003", "%z").utcoffset(),
2550            -timedelta(minutes=2, seconds=1, microseconds=3)
2551        )
2552        # Only local timezone and UTC are supported
2553        for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
2554                                 (-_time.timezone, _time.tzname[0])):
2555            if tzseconds < 0:
2556                sign = '-'
2557                seconds = -tzseconds
2558            else:
2559                sign ='+'
2560                seconds = tzseconds
2561            hours, minutes = divmod(seconds//60, 60)
2562            dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
2563            dt = strptime(dtstr, "%z %Z")
2564            self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
2565            self.assertEqual(dt.tzname(), tzname)
2566        # Can produce inconsistent datetime
2567        dtstr, fmt = "+1234 UTC", "%z %Z"
2568        dt = strptime(dtstr, fmt)
2569        self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
2570        self.assertEqual(dt.tzname(), 'UTC')
2571        # yet will roundtrip
2572        self.assertEqual(dt.strftime(fmt), dtstr)
2573
2574        # Produce naive datetime if no %z is provided
2575        self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
2576
2577        with self.assertRaises(ValueError): strptime("-2400", "%z")
2578        with self.assertRaises(ValueError): strptime("-000", "%z")
2579
2580    def test_strptime_single_digit(self):
2581        # bpo-34903: Check that single digit dates and times are allowed.
2582
2583        strptime = self.theclass.strptime
2584
2585        with self.assertRaises(ValueError):
2586            # %y does require two digits.
2587            newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2588        dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2589        dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2590        dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2591        dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2592        inputs = [
2593            ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2594            ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2595            ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2596            ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2597            ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2598            ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2599            ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2600            ('%w', '6/04/03', '%w/%U/%y', dt3),
2601            # %u requires a single digit.
2602            ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2603            ('%V', '6/4/2003', '%u/%V/%G', dt4),
2604        ]
2605        for reason, string, format, target in inputs:
2606            reason = 'test single digit ' + reason
2607            with self.subTest(reason=reason,
2608                              string=string,
2609                              format=format,
2610                              target=target):
2611                newdate = strptime(string, format)
2612                self.assertEqual(newdate, target, msg=reason)
2613
2614    def test_more_timetuple(self):
2615        # This tests fields beyond those tested by the TestDate.test_timetuple.
2616        t = self.theclass(2004, 12, 31, 6, 22, 33)
2617        self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2618        self.assertEqual(t.timetuple(),
2619                         (t.year, t.month, t.day,
2620                          t.hour, t.minute, t.second,
2621                          t.weekday(),
2622                          t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2623                          -1))
2624        tt = t.timetuple()
2625        self.assertEqual(tt.tm_year, t.year)
2626        self.assertEqual(tt.tm_mon, t.month)
2627        self.assertEqual(tt.tm_mday, t.day)
2628        self.assertEqual(tt.tm_hour, t.hour)
2629        self.assertEqual(tt.tm_min, t.minute)
2630        self.assertEqual(tt.tm_sec, t.second)
2631        self.assertEqual(tt.tm_wday, t.weekday())
2632        self.assertEqual(tt.tm_yday, t.toordinal() -
2633                                     date(t.year, 1, 1).toordinal() + 1)
2634        self.assertEqual(tt.tm_isdst, -1)
2635
2636    def test_more_strftime(self):
2637        # This tests fields beyond those tested by the TestDate.test_strftime.
2638        t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2639        self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2640                                    "12 31 04 000047 33 22 06 366")
2641        for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2642            tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2643            t = t.replace(tzinfo=tz)
2644            self.assertEqual(t.strftime("%z"), "-0200" + z)
2645
2646        # bpo-34482: Check that surrogates don't cause a crash.
2647        try:
2648            t.strftime('%y\ud800%m %H\ud800%M')
2649        except UnicodeEncodeError:
2650            pass
2651
2652    def test_extract(self):
2653        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2654        self.assertEqual(dt.date(), date(2002, 3, 4))
2655        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2656
2657    def test_combine(self):
2658        d = date(2002, 3, 4)
2659        t = time(18, 45, 3, 1234)
2660        expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2661        combine = self.theclass.combine
2662        dt = combine(d, t)
2663        self.assertEqual(dt, expected)
2664
2665        dt = combine(time=t, date=d)
2666        self.assertEqual(dt, expected)
2667
2668        self.assertEqual(d, dt.date())
2669        self.assertEqual(t, dt.time())
2670        self.assertEqual(dt, combine(dt.date(), dt.time()))
2671
2672        self.assertRaises(TypeError, combine) # need an arg
2673        self.assertRaises(TypeError, combine, d) # need two args
2674        self.assertRaises(TypeError, combine, t, d) # args reversed
2675        self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2676        self.assertRaises(TypeError, combine, d, t, 1, 2)  # too many args
2677        self.assertRaises(TypeError, combine, "date", "time") # wrong types
2678        self.assertRaises(TypeError, combine, d, "time") # wrong type
2679        self.assertRaises(TypeError, combine, "date", t) # wrong type
2680
2681        # tzinfo= argument
2682        dt = combine(d, t, timezone.utc)
2683        self.assertIs(dt.tzinfo, timezone.utc)
2684        dt = combine(d, t, tzinfo=timezone.utc)
2685        self.assertIs(dt.tzinfo, timezone.utc)
2686        t = time()
2687        dt = combine(dt, t)
2688        self.assertEqual(dt.date(), d)
2689        self.assertEqual(dt.time(), t)
2690
2691    def test_replace(self):
2692        cls = self.theclass
2693        args = [1, 2, 3, 4, 5, 6, 7]
2694        base = cls(*args)
2695        self.assertEqual(base, base.replace())
2696
2697        i = 0
2698        for name, newval in (("year", 2),
2699                             ("month", 3),
2700                             ("day", 4),
2701                             ("hour", 5),
2702                             ("minute", 6),
2703                             ("second", 7),
2704                             ("microsecond", 8)):
2705            newargs = args[:]
2706            newargs[i] = newval
2707            expected = cls(*newargs)
2708            got = base.replace(**{name: newval})
2709            self.assertEqual(expected, got)
2710            i += 1
2711
2712        # Out of bounds.
2713        base = cls(2000, 2, 29)
2714        self.assertRaises(ValueError, base.replace, year=2001)
2715
2716    @support.run_with_tz('EDT4')
2717    def test_astimezone(self):
2718        dt = self.theclass.now()
2719        f = FixedOffset(44, "0044")
2720        dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2721        self.assertEqual(dt.astimezone(), dt_utc) # naive
2722        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2723        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2724        dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2725        self.assertEqual(dt.astimezone(f), dt_f) # naive
2726        self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
2727
2728        class Bogus(tzinfo):
2729            def utcoffset(self, dt): return None
2730            def dst(self, dt): return timedelta(0)
2731        bog = Bogus()
2732        self.assertRaises(ValueError, dt.astimezone, bog)   # naive
2733        self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
2734
2735        class AlsoBogus(tzinfo):
2736            def utcoffset(self, dt): return timedelta(0)
2737            def dst(self, dt): return None
2738        alsobog = AlsoBogus()
2739        self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2740
2741        class Broken(tzinfo):
2742            def utcoffset(self, dt): return 1
2743            def dst(self, dt): return 1
2744        broken = Broken()
2745        dt_broken = dt.replace(tzinfo=broken)
2746        with self.assertRaises(TypeError):
2747            dt_broken.astimezone()
2748
2749    def test_subclass_datetime(self):
2750
2751        class C(self.theclass):
2752            theAnswer = 42
2753
2754            def __new__(cls, *args, **kws):
2755                temp = kws.copy()
2756                extra = temp.pop('extra')
2757                result = self.theclass.__new__(cls, *args, **temp)
2758                result.extra = extra
2759                return result
2760
2761            def newmeth(self, start):
2762                return start + self.year + self.month + self.second
2763
2764        args = 2003, 4, 14, 12, 13, 41
2765
2766        dt1 = self.theclass(*args)
2767        dt2 = C(*args, **{'extra': 7})
2768
2769        self.assertEqual(dt2.__class__, C)
2770        self.assertEqual(dt2.theAnswer, 42)
2771        self.assertEqual(dt2.extra, 7)
2772        self.assertEqual(dt1.toordinal(), dt2.toordinal())
2773        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2774                                          dt1.second - 7)
2775
2776    def test_subclass_alternate_constructors_datetime(self):
2777        # Test that alternate constructors call the constructor
2778        class DateTimeSubclass(self.theclass):
2779            def __new__(cls, *args, **kwargs):
2780                result = self.theclass.__new__(cls, *args, **kwargs)
2781                result.extra = 7
2782
2783                return result
2784
2785        args = (2003, 4, 14, 12, 30, 15, 123456)
2786        d_isoformat = '2003-04-14T12:30:15.123456'      # Equivalent isoformat()
2787        utc_ts = 1050323415.123456                      # UTC timestamp
2788
2789        base_d = DateTimeSubclass(*args)
2790        self.assertIsInstance(base_d, DateTimeSubclass)
2791        self.assertEqual(base_d.extra, 7)
2792
2793        # Timestamp depends on time zone, so we'll calculate the equivalent here
2794        ts = base_d.timestamp()
2795
2796        test_cases = [
2797            ('fromtimestamp', (ts,), base_d),
2798            # See https://bugs.python.org/issue32417
2799            ('fromtimestamp', (ts, timezone.utc),
2800                               base_d.astimezone(timezone.utc)),
2801            ('utcfromtimestamp', (utc_ts,), base_d),
2802            ('fromisoformat', (d_isoformat,), base_d),
2803            ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2804            ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
2805        ]
2806
2807        for constr_name, constr_args, expected in test_cases:
2808            for base_obj in (DateTimeSubclass, base_d):
2809                # Test both the classmethod and method
2810                with self.subTest(base_obj_type=type(base_obj),
2811                                  constr_name=constr_name):
2812                    constructor = getattr(base_obj, constr_name)
2813
2814                    dt = constructor(*constr_args)
2815
2816                    # Test that it creates the right subclass
2817                    self.assertIsInstance(dt, DateTimeSubclass)
2818
2819                    # Test that it's equal to the base object
2820                    self.assertEqual(dt, expected)
2821
2822                    # Test that it called the constructor
2823                    self.assertEqual(dt.extra, 7)
2824
2825    def test_subclass_now(self):
2826        # Test that alternate constructors call the constructor
2827        class DateTimeSubclass(self.theclass):
2828            def __new__(cls, *args, **kwargs):
2829                result = self.theclass.__new__(cls, *args, **kwargs)
2830                result.extra = 7
2831
2832                return result
2833
2834        test_cases = [
2835            ('now', 'now', {}),
2836            ('utcnow', 'utcnow', {}),
2837            ('now_utc', 'now', {'tz': timezone.utc}),
2838            ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2839        ]
2840
2841        for name, meth_name, kwargs in test_cases:
2842            with self.subTest(name):
2843                constr = getattr(DateTimeSubclass, meth_name)
2844                dt = constr(**kwargs)
2845
2846                self.assertIsInstance(dt, DateTimeSubclass)
2847                self.assertEqual(dt.extra, 7)
2848
2849    def test_fromisoformat_datetime(self):
2850        # Test that isoformat() is reversible
2851        base_dates = [
2852            (1, 1, 1),
2853            (1900, 1, 1),
2854            (2004, 11, 12),
2855            (2017, 5, 30)
2856        ]
2857
2858        base_times = [
2859            (0, 0, 0, 0),
2860            (0, 0, 0, 241000),
2861            (0, 0, 0, 234567),
2862            (12, 30, 45, 234567)
2863        ]
2864
2865        separators = [' ', 'T']
2866
2867        tzinfos = [None, timezone.utc,
2868                   timezone(timedelta(hours=-5)),
2869                   timezone(timedelta(hours=2))]
2870
2871        dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2872               for date_tuple in base_dates
2873               for time_tuple in base_times
2874               for tzi in tzinfos]
2875
2876        for dt in dts:
2877            for sep in separators:
2878                dtstr = dt.isoformat(sep=sep)
2879
2880                with self.subTest(dtstr=dtstr):
2881                    dt_rt = self.theclass.fromisoformat(dtstr)
2882                    self.assertEqual(dt, dt_rt)
2883
2884    def test_fromisoformat_timezone(self):
2885        base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2886
2887        tzoffsets = [
2888            timedelta(hours=5), timedelta(hours=2),
2889            timedelta(hours=6, minutes=27),
2890            timedelta(hours=12, minutes=32, seconds=30),
2891            timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2892        ]
2893
2894        tzoffsets += [-1 * td for td in tzoffsets]
2895
2896        tzinfos = [None, timezone.utc,
2897                   timezone(timedelta(hours=0))]
2898
2899        tzinfos += [timezone(td) for td in tzoffsets]
2900
2901        for tzi in tzinfos:
2902            dt = base_dt.replace(tzinfo=tzi)
2903            dtstr = dt.isoformat()
2904
2905            with self.subTest(tstr=dtstr):
2906                dt_rt = self.theclass.fromisoformat(dtstr)
2907                assert dt == dt_rt, dt_rt
2908
2909    def test_fromisoformat_separators(self):
2910        separators = [
2911            ' ', 'T', '\u007f',     # 1-bit widths
2912            '\u0080', 'ʁ',          # 2-bit widths
2913            'ᛇ', '時',               # 3-bit widths
2914            '��',                    # 4-bit widths
2915            '\ud800',               # bpo-34454: Surrogate code point
2916        ]
2917
2918        for sep in separators:
2919            dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2920            dtstr = dt.isoformat(sep=sep)
2921
2922            with self.subTest(dtstr=dtstr):
2923                dt_rt = self.theclass.fromisoformat(dtstr)
2924                self.assertEqual(dt, dt_rt)
2925
2926    def test_fromisoformat_ambiguous(self):
2927        # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2928        separators = ['+', '-']
2929        for sep in separators:
2930            dt = self.theclass(2018, 1, 31, 12, 15)
2931            dtstr = dt.isoformat(sep=sep)
2932
2933            with self.subTest(dtstr=dtstr):
2934                dt_rt = self.theclass.fromisoformat(dtstr)
2935                self.assertEqual(dt, dt_rt)
2936
2937    def test_fromisoformat_timespecs(self):
2938        datetime_bases = [
2939            (2009, 12, 4, 8, 17, 45, 123456),
2940            (2009, 12, 4, 8, 17, 45, 0)]
2941
2942        tzinfos = [None, timezone.utc,
2943                   timezone(timedelta(hours=-5)),
2944                   timezone(timedelta(hours=2)),
2945                   timezone(timedelta(hours=6, minutes=27))]
2946
2947        timespecs = ['hours', 'minutes', 'seconds',
2948                     'milliseconds', 'microseconds']
2949
2950        for ip, ts in enumerate(timespecs):
2951            for tzi in tzinfos:
2952                for dt_tuple in datetime_bases:
2953                    if ts == 'milliseconds':
2954                        new_microseconds = 1000 * (dt_tuple[6] // 1000)
2955                        dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2956
2957                    dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2958                    dtstr = dt.isoformat(timespec=ts)
2959                    with self.subTest(dtstr=dtstr):
2960                        dt_rt = self.theclass.fromisoformat(dtstr)
2961                        self.assertEqual(dt, dt_rt)
2962
2963    def test_fromisoformat_fails_datetime(self):
2964        # Test that fromisoformat() fails on invalid values
2965        bad_strs = [
2966            '',                             # Empty string
2967            '\ud800',                       # bpo-34454: Surrogate code point
2968            '2009.04-19T03',                # Wrong first separator
2969            '2009-04.19T03',                # Wrong second separator
2970            '2009-04-19T0a',                # Invalid hours
2971            '2009-04-19T03:1a:45',          # Invalid minutes
2972            '2009-04-19T03:15:4a',          # Invalid seconds
2973            '2009-04-19T03;15:45',          # Bad first time separator
2974            '2009-04-19T03:15;45',          # Bad second time separator
2975            '2009-04-19T03:15:4500:00',     # Bad time zone separator
2976            '2009-04-19T03:15:45.2345',     # Too many digits for milliseconds
2977            '2009-04-19T03:15:45.1234567',  # Too many digits for microseconds
2978            '2009-04-19T03:15:45.123456+24:30',    # Invalid time zone offset
2979            '2009-04-19T03:15:45.123456-24:30',    # Invalid negative offset
2980            '2009-04-10ᛇᛇᛇᛇᛇ12:15',         # Too many unicode separators
2981            '2009-04\ud80010T12:15',        # Surrogate char in date
2982            '2009-04-10T12\ud80015',        # Surrogate char in time
2983            '2009-04-19T1',                 # Incomplete hours
2984            '2009-04-19T12:3',              # Incomplete minutes
2985            '2009-04-19T12:30:4',           # Incomplete seconds
2986            '2009-04-19T12:',               # Ends with time separator
2987            '2009-04-19T12:30:',            # Ends with time separator
2988            '2009-04-19T12:30:45.',         # Ends with time separator
2989            '2009-04-19T12:30:45.123456+',  # Ends with timzone separator
2990            '2009-04-19T12:30:45.123456-',  # Ends with timzone separator
2991            '2009-04-19T12:30:45.123456-05:00a',    # Extra text
2992            '2009-04-19T12:30:45.123-05:00a',       # Extra text
2993            '2009-04-19T12:30:45-05:00a',           # Extra text
2994        ]
2995
2996        for bad_str in bad_strs:
2997            with self.subTest(bad_str=bad_str):
2998                with self.assertRaises(ValueError):
2999                    self.theclass.fromisoformat(bad_str)
3000
3001    def test_fromisoformat_fails_surrogate(self):
3002        # Test that when fromisoformat() fails with a surrogate character as
3003        # the separator, the error message contains the original string
3004        dtstr = "2018-01-03\ud80001:0113"
3005
3006        with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
3007            self.theclass.fromisoformat(dtstr)
3008
3009    def test_fromisoformat_utc(self):
3010        dt_str = '2014-04-19T13:21:13+00:00'
3011        dt = self.theclass.fromisoformat(dt_str)
3012
3013        self.assertIs(dt.tzinfo, timezone.utc)
3014
3015    def test_fromisoformat_subclass(self):
3016        class DateTimeSubclass(self.theclass):
3017            pass
3018
3019        dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
3020                              tzinfo=timezone(timedelta(hours=10, minutes=45)))
3021
3022        dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
3023
3024        self.assertEqual(dt, dt_rt)
3025        self.assertIsInstance(dt_rt, DateTimeSubclass)
3026
3027
3028class TestSubclassDateTime(TestDateTime):
3029    theclass = SubclassDatetime
3030    # Override tests not designed for subclass
3031    @unittest.skip('not appropriate for subclasses')
3032    def test_roundtrip(self):
3033        pass
3034
3035class SubclassTime(time):
3036    sub_var = 1
3037
3038class TestTime(HarmlessMixedComparison, unittest.TestCase):
3039
3040    theclass = time
3041
3042    def test_basic_attributes(self):
3043        t = self.theclass(12, 0)
3044        self.assertEqual(t.hour, 12)
3045        self.assertEqual(t.minute, 0)
3046        self.assertEqual(t.second, 0)
3047        self.assertEqual(t.microsecond, 0)
3048
3049    def test_basic_attributes_nonzero(self):
3050        # Make sure all attributes are non-zero so bugs in
3051        # bit-shifting access show up.
3052        t = self.theclass(12, 59, 59, 8000)
3053        self.assertEqual(t.hour, 12)
3054        self.assertEqual(t.minute, 59)
3055        self.assertEqual(t.second, 59)
3056        self.assertEqual(t.microsecond, 8000)
3057
3058    def test_roundtrip(self):
3059        t = self.theclass(1, 2, 3, 4)
3060
3061        # Verify t -> string -> time identity.
3062        s = repr(t)
3063        self.assertTrue(s.startswith('datetime.'))
3064        s = s[9:]
3065        t2 = eval(s)
3066        self.assertEqual(t, t2)
3067
3068        # Verify identity via reconstructing from pieces.
3069        t2 = self.theclass(t.hour, t.minute, t.second,
3070                           t.microsecond)
3071        self.assertEqual(t, t2)
3072
3073    def test_comparing(self):
3074        args = [1, 2, 3, 4]
3075        t1 = self.theclass(*args)
3076        t2 = self.theclass(*args)
3077        self.assertEqual(t1, t2)
3078        self.assertTrue(t1 <= t2)
3079        self.assertTrue(t1 >= t2)
3080        self.assertFalse(t1 != t2)
3081        self.assertFalse(t1 < t2)
3082        self.assertFalse(t1 > t2)
3083
3084        for i in range(len(args)):
3085            newargs = args[:]
3086            newargs[i] = args[i] + 1
3087            t2 = self.theclass(*newargs)   # this is larger than t1
3088            self.assertTrue(t1 < t2)
3089            self.assertTrue(t2 > t1)
3090            self.assertTrue(t1 <= t2)
3091            self.assertTrue(t2 >= t1)
3092            self.assertTrue(t1 != t2)
3093            self.assertTrue(t2 != t1)
3094            self.assertFalse(t1 == t2)
3095            self.assertFalse(t2 == t1)
3096            self.assertFalse(t1 > t2)
3097            self.assertFalse(t2 < t1)
3098            self.assertFalse(t1 >= t2)
3099            self.assertFalse(t2 <= t1)
3100
3101        for badarg in OTHERSTUFF:
3102            self.assertEqual(t1 == badarg, False)
3103            self.assertEqual(t1 != badarg, True)
3104            self.assertEqual(badarg == t1, False)
3105            self.assertEqual(badarg != t1, True)
3106
3107            self.assertRaises(TypeError, lambda: t1 <= badarg)
3108            self.assertRaises(TypeError, lambda: t1 < badarg)
3109            self.assertRaises(TypeError, lambda: t1 > badarg)
3110            self.assertRaises(TypeError, lambda: t1 >= badarg)
3111            self.assertRaises(TypeError, lambda: badarg <= t1)
3112            self.assertRaises(TypeError, lambda: badarg < t1)
3113            self.assertRaises(TypeError, lambda: badarg > t1)
3114            self.assertRaises(TypeError, lambda: badarg >= t1)
3115
3116    def test_bad_constructor_arguments(self):
3117        # bad hours
3118        self.theclass(0, 0)    # no exception
3119        self.theclass(23, 0)   # no exception
3120        self.assertRaises(ValueError, self.theclass, -1, 0)
3121        self.assertRaises(ValueError, self.theclass, 24, 0)
3122        # bad minutes
3123        self.theclass(23, 0)    # no exception
3124        self.theclass(23, 59)   # no exception
3125        self.assertRaises(ValueError, self.theclass, 23, -1)
3126        self.assertRaises(ValueError, self.theclass, 23, 60)
3127        # bad seconds
3128        self.theclass(23, 59, 0)    # no exception
3129        self.theclass(23, 59, 59)   # no exception
3130        self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3131        self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3132        # bad microseconds
3133        self.theclass(23, 59, 59, 0)        # no exception
3134        self.theclass(23, 59, 59, 999999)   # no exception
3135        self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3136        self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3137
3138    def test_hash_equality(self):
3139        d = self.theclass(23, 30, 17)
3140        e = self.theclass(23, 30, 17)
3141        self.assertEqual(d, e)
3142        self.assertEqual(hash(d), hash(e))
3143
3144        dic = {d: 1}
3145        dic[e] = 2
3146        self.assertEqual(len(dic), 1)
3147        self.assertEqual(dic[d], 2)
3148        self.assertEqual(dic[e], 2)
3149
3150        d = self.theclass(0,  5, 17)
3151        e = self.theclass(0,  5, 17)
3152        self.assertEqual(d, e)
3153        self.assertEqual(hash(d), hash(e))
3154
3155        dic = {d: 1}
3156        dic[e] = 2
3157        self.assertEqual(len(dic), 1)
3158        self.assertEqual(dic[d], 2)
3159        self.assertEqual(dic[e], 2)
3160
3161    def test_isoformat(self):
3162        t = self.theclass(4, 5, 1, 123)
3163        self.assertEqual(t.isoformat(), "04:05:01.000123")
3164        self.assertEqual(t.isoformat(), str(t))
3165
3166        t = self.theclass()
3167        self.assertEqual(t.isoformat(), "00:00:00")
3168        self.assertEqual(t.isoformat(), str(t))
3169
3170        t = self.theclass(microsecond=1)
3171        self.assertEqual(t.isoformat(), "00:00:00.000001")
3172        self.assertEqual(t.isoformat(), str(t))
3173
3174        t = self.theclass(microsecond=10)
3175        self.assertEqual(t.isoformat(), "00:00:00.000010")
3176        self.assertEqual(t.isoformat(), str(t))
3177
3178        t = self.theclass(microsecond=100)
3179        self.assertEqual(t.isoformat(), "00:00:00.000100")
3180        self.assertEqual(t.isoformat(), str(t))
3181
3182        t = self.theclass(microsecond=1000)
3183        self.assertEqual(t.isoformat(), "00:00:00.001000")
3184        self.assertEqual(t.isoformat(), str(t))
3185
3186        t = self.theclass(microsecond=10000)
3187        self.assertEqual(t.isoformat(), "00:00:00.010000")
3188        self.assertEqual(t.isoformat(), str(t))
3189
3190        t = self.theclass(microsecond=100000)
3191        self.assertEqual(t.isoformat(), "00:00:00.100000")
3192        self.assertEqual(t.isoformat(), str(t))
3193
3194        t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3195        self.assertEqual(t.isoformat(timespec='hours'), "12")
3196        self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3197        self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3198        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3199        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3200        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3201        self.assertRaises(ValueError, t.isoformat, timespec='monkey')
3202        # bpo-34482: Check that surrogates are handled properly.
3203        self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
3204
3205        t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3206        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3207
3208        t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3209        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3210        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3211        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3212
3213    def test_isoformat_timezone(self):
3214        tzoffsets = [
3215            ('05:00', timedelta(hours=5)),
3216            ('02:00', timedelta(hours=2)),
3217            ('06:27', timedelta(hours=6, minutes=27)),
3218            ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3219            ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3220        ]
3221
3222        tzinfos = [
3223            ('', None),
3224            ('+00:00', timezone.utc),
3225            ('+00:00', timezone(timedelta(0))),
3226        ]
3227
3228        tzinfos += [
3229            (prefix + expected, timezone(sign * td))
3230            for expected, td in tzoffsets
3231            for prefix, sign in [('-', -1), ('+', 1)]
3232        ]
3233
3234        t_base = self.theclass(12, 37, 9)
3235        exp_base = '12:37:09'
3236
3237        for exp_tz, tzi in tzinfos:
3238            t = t_base.replace(tzinfo=tzi)
3239            exp = exp_base + exp_tz
3240            with self.subTest(tzi=tzi):
3241                assert t.isoformat() == exp
3242
3243    def test_1653736(self):
3244        # verify it doesn't accept extra keyword arguments
3245        t = self.theclass(second=1)
3246        self.assertRaises(TypeError, t.isoformat, foo=3)
3247
3248    def test_strftime(self):
3249        t = self.theclass(1, 2, 3, 4)
3250        self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3251        # A naive object replaces %z and %Z with empty strings.
3252        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3253
3254        # bpo-34482: Check that surrogates don't cause a crash.
3255        try:
3256            t.strftime('%H\ud800%M')
3257        except UnicodeEncodeError:
3258            pass
3259
3260    def test_format(self):
3261        t = self.theclass(1, 2, 3, 4)
3262        self.assertEqual(t.__format__(''), str(t))
3263
3264        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
3265            t.__format__(123)
3266
3267        # check that a derived class's __str__() gets called
3268        class A(self.theclass):
3269            def __str__(self):
3270                return 'A'
3271        a = A(1, 2, 3, 4)
3272        self.assertEqual(a.__format__(''), 'A')
3273
3274        # check that a derived class's strftime gets called
3275        class B(self.theclass):
3276            def strftime(self, format_spec):
3277                return 'B'
3278        b = B(1, 2, 3, 4)
3279        self.assertEqual(b.__format__(''), str(t))
3280
3281        for fmt in ['%H %M %S',
3282                    ]:
3283            self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3284            self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3285            self.assertEqual(b.__format__(fmt), 'B')
3286
3287    def test_str(self):
3288        self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3289        self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3290        self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3291        self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3292        self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3293
3294    def test_repr(self):
3295        name = 'datetime.' + self.theclass.__name__
3296        self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3297                         "%s(1, 2, 3, 4)" % name)
3298        self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3299                         "%s(10, 2, 3, 4000)" % name)
3300        self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3301                         "%s(0, 2, 3, 400000)" % name)
3302        self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3303                         "%s(12, 2, 3)" % name)
3304        self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3305                         "%s(23, 15)" % name)
3306
3307    def test_resolution_info(self):
3308        self.assertIsInstance(self.theclass.min, self.theclass)
3309        self.assertIsInstance(self.theclass.max, self.theclass)
3310        self.assertIsInstance(self.theclass.resolution, timedelta)
3311        self.assertTrue(self.theclass.max > self.theclass.min)
3312
3313    def test_pickling(self):
3314        args = 20, 59, 16, 64**2
3315        orig = self.theclass(*args)
3316        for pickler, unpickler, proto in pickle_choices:
3317            green = pickler.dumps(orig, proto)
3318            derived = unpickler.loads(green)
3319            self.assertEqual(orig, derived)
3320        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3321
3322    def test_pickling_subclass_time(self):
3323        args = 20, 59, 16, 64**2
3324        orig = SubclassTime(*args)
3325        for pickler, unpickler, proto in pickle_choices:
3326            green = pickler.dumps(orig, proto)
3327            derived = unpickler.loads(green)
3328            self.assertEqual(orig, derived)
3329
3330    def test_compat_unpickle(self):
3331        tests = [
3332            (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3333             (20, 59, 16, 64**2)),
3334            (b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3335             (20, 59, 16, 64**2)),
3336            (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3337             (20, 59, 16, 64**2)),
3338            (b"cdatetime\ntime\n(S'\\x14;\\x19\\x00\\x10\\x00'\ntR.",
3339             (20, 59, 25, 64**2)),
3340            (b'cdatetime\ntime\n(U\x06\x14;\x19\x00\x10\x00tR.',
3341             (20, 59, 25, 64**2)),
3342            (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x19\x00\x10\x00\x85R.',
3343             (20, 59, 25, 64**2)),
3344        ]
3345        for i, (data, args) in enumerate(tests):
3346            with self.subTest(i=i):
3347                expected = self.theclass(*args)
3348                for loads in pickle_loads:
3349                    derived = loads(data, encoding='latin1')
3350                    self.assertEqual(derived, expected)
3351
3352    def test_bool(self):
3353        # time is always True.
3354        cls = self.theclass
3355        self.assertTrue(cls(1))
3356        self.assertTrue(cls(0, 1))
3357        self.assertTrue(cls(0, 0, 1))
3358        self.assertTrue(cls(0, 0, 0, 1))
3359        self.assertTrue(cls(0))
3360        self.assertTrue(cls())
3361
3362    def test_replace(self):
3363        cls = self.theclass
3364        args = [1, 2, 3, 4]
3365        base = cls(*args)
3366        self.assertEqual(base, base.replace())
3367
3368        i = 0
3369        for name, newval in (("hour", 5),
3370                             ("minute", 6),
3371                             ("second", 7),
3372                             ("microsecond", 8)):
3373            newargs = args[:]
3374            newargs[i] = newval
3375            expected = cls(*newargs)
3376            got = base.replace(**{name: newval})
3377            self.assertEqual(expected, got)
3378            i += 1
3379
3380        # Out of bounds.
3381        base = cls(1)
3382        self.assertRaises(ValueError, base.replace, hour=24)
3383        self.assertRaises(ValueError, base.replace, minute=-1)
3384        self.assertRaises(ValueError, base.replace, second=100)
3385        self.assertRaises(ValueError, base.replace, microsecond=1000000)
3386
3387    def test_subclass_replace(self):
3388        class TimeSubclass(self.theclass):
3389            pass
3390
3391        ctime = TimeSubclass(12, 30)
3392        self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3393
3394    def test_subclass_time(self):
3395
3396        class C(self.theclass):
3397            theAnswer = 42
3398
3399            def __new__(cls, *args, **kws):
3400                temp = kws.copy()
3401                extra = temp.pop('extra')
3402                result = self.theclass.__new__(cls, *args, **temp)
3403                result.extra = extra
3404                return result
3405
3406            def newmeth(self, start):
3407                return start + self.hour + self.second
3408
3409        args = 4, 5, 6
3410
3411        dt1 = self.theclass(*args)
3412        dt2 = C(*args, **{'extra': 7})
3413
3414        self.assertEqual(dt2.__class__, C)
3415        self.assertEqual(dt2.theAnswer, 42)
3416        self.assertEqual(dt2.extra, 7)
3417        self.assertEqual(dt1.isoformat(), dt2.isoformat())
3418        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3419
3420    def test_backdoor_resistance(self):
3421        # see TestDate.test_backdoor_resistance().
3422        base = '2:59.0'
3423        for hour_byte in ' ', '9', chr(24), '\xff':
3424            self.assertRaises(TypeError, self.theclass,
3425                                         hour_byte + base[1:])
3426        # Good bytes, but bad tzinfo:
3427        with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3428            self.theclass(bytes([1] * len(base)), 'EST')
3429
3430# A mixin for classes with a tzinfo= argument.  Subclasses must define
3431# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
3432# must be legit (which is true for time and datetime).
3433class TZInfoBase:
3434
3435    def test_argument_passing(self):
3436        cls = self.theclass
3437        # A datetime passes itself on, a time passes None.
3438        class introspective(tzinfo):
3439            def tzname(self, dt):    return dt and "real" or "none"
3440            def utcoffset(self, dt):
3441                return timedelta(minutes = dt and 42 or -42)
3442            dst = utcoffset
3443
3444        obj = cls(1, 2, 3, tzinfo=introspective())
3445
3446        expected = cls is time and "none" or "real"
3447        self.assertEqual(obj.tzname(), expected)
3448
3449        expected = timedelta(minutes=(cls is time and -42 or 42))
3450        self.assertEqual(obj.utcoffset(), expected)
3451        self.assertEqual(obj.dst(), expected)
3452
3453    def test_bad_tzinfo_classes(self):
3454        cls = self.theclass
3455        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3456
3457        class NiceTry(object):
3458            def __init__(self): pass
3459            def utcoffset(self, dt): pass
3460        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3461
3462        class BetterTry(tzinfo):
3463            def __init__(self): pass
3464            def utcoffset(self, dt): pass
3465        b = BetterTry()
3466        t = cls(1, 1, 1, tzinfo=b)
3467        self.assertIs(t.tzinfo, b)
3468
3469    def test_utc_offset_out_of_bounds(self):
3470        class Edgy(tzinfo):
3471            def __init__(self, offset):
3472                self.offset = timedelta(minutes=offset)
3473            def utcoffset(self, dt):
3474                return self.offset
3475
3476        cls = self.theclass
3477        for offset, legit in ((-1440, False),
3478                              (-1439, True),
3479                              (1439, True),
3480                              (1440, False)):
3481            if cls is time:
3482                t = cls(1, 2, 3, tzinfo=Edgy(offset))
3483            elif cls is datetime:
3484                t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3485            else:
3486                assert 0, "impossible"
3487            if legit:
3488                aofs = abs(offset)
3489                h, m = divmod(aofs, 60)
3490                tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3491                if isinstance(t, datetime):
3492                    t = t.timetz()
3493                self.assertEqual(str(t), "01:02:03" + tag)
3494            else:
3495                self.assertRaises(ValueError, str, t)
3496
3497    def test_tzinfo_classes(self):
3498        cls = self.theclass
3499        class C1(tzinfo):
3500            def utcoffset(self, dt): return None
3501            def dst(self, dt): return None
3502            def tzname(self, dt): return None
3503        for t in (cls(1, 1, 1),
3504                  cls(1, 1, 1, tzinfo=None),
3505                  cls(1, 1, 1, tzinfo=C1())):
3506            self.assertIsNone(t.utcoffset())
3507            self.assertIsNone(t.dst())
3508            self.assertIsNone(t.tzname())
3509
3510        class C3(tzinfo):
3511            def utcoffset(self, dt): return timedelta(minutes=-1439)
3512            def dst(self, dt): return timedelta(minutes=1439)
3513            def tzname(self, dt): return "aname"
3514        t = cls(1, 1, 1, tzinfo=C3())
3515        self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3516        self.assertEqual(t.dst(), timedelta(minutes=1439))
3517        self.assertEqual(t.tzname(), "aname")
3518
3519        # Wrong types.
3520        class C4(tzinfo):
3521            def utcoffset(self, dt): return "aname"
3522            def dst(self, dt): return 7
3523            def tzname(self, dt): return 0
3524        t = cls(1, 1, 1, tzinfo=C4())
3525        self.assertRaises(TypeError, t.utcoffset)
3526        self.assertRaises(TypeError, t.dst)
3527        self.assertRaises(TypeError, t.tzname)
3528
3529        # Offset out of range.
3530        class C6(tzinfo):
3531            def utcoffset(self, dt): return timedelta(hours=-24)
3532            def dst(self, dt): return timedelta(hours=24)
3533        t = cls(1, 1, 1, tzinfo=C6())
3534        self.assertRaises(ValueError, t.utcoffset)
3535        self.assertRaises(ValueError, t.dst)
3536
3537        # Not a whole number of seconds.
3538        class C7(tzinfo):
3539            def utcoffset(self, dt): return timedelta(microseconds=61)
3540            def dst(self, dt): return timedelta(microseconds=-81)
3541        t = cls(1, 1, 1, tzinfo=C7())
3542        self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3543        self.assertEqual(t.dst(), timedelta(microseconds=-81))
3544
3545    def test_aware_compare(self):
3546        cls = self.theclass
3547
3548        # Ensure that utcoffset() gets ignored if the comparands have
3549        # the same tzinfo member.
3550        class OperandDependentOffset(tzinfo):
3551            def utcoffset(self, t):
3552                if t.minute < 10:
3553                    # d0 and d1 equal after adjustment
3554                    return timedelta(minutes=t.minute)
3555                else:
3556                    # d2 off in the weeds
3557                    return timedelta(minutes=59)
3558
3559        base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3560        d0 = base.replace(minute=3)
3561        d1 = base.replace(minute=9)
3562        d2 = base.replace(minute=11)
3563        for x in d0, d1, d2:
3564            for y in d0, d1, d2:
3565                for op in lt, le, gt, ge, eq, ne:
3566                    got = op(x, y)
3567                    expected = op(x.minute, y.minute)
3568                    self.assertEqual(got, expected)
3569
3570        # However, if they're different members, uctoffset is not ignored.
3571        # Note that a time can't actually have an operand-dependent offset,
3572        # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3573        # so skip this test for time.
3574        if cls is not time:
3575            d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3576            d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3577            d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3578            for x in d0, d1, d2:
3579                for y in d0, d1, d2:
3580                    got = (x > y) - (x < y)
3581                    if (x is d0 or x is d1) and (y is d0 or y is d1):
3582                        expected = 0
3583                    elif x is y is d2:
3584                        expected = 0
3585                    elif x is d2:
3586                        expected = -1
3587                    else:
3588                        assert y is d2
3589                        expected = 1
3590                    self.assertEqual(got, expected)
3591
3592
3593# Testing time objects with a non-None tzinfo.
3594class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3595    theclass = time
3596
3597    def test_empty(self):
3598        t = self.theclass()
3599        self.assertEqual(t.hour, 0)
3600        self.assertEqual(t.minute, 0)
3601        self.assertEqual(t.second, 0)
3602        self.assertEqual(t.microsecond, 0)
3603        self.assertIsNone(t.tzinfo)
3604
3605    def test_zones(self):
3606        est = FixedOffset(-300, "EST", 1)
3607        utc = FixedOffset(0, "UTC", -2)
3608        met = FixedOffset(60, "MET", 3)
3609        t1 = time( 7, 47, tzinfo=est)
3610        t2 = time(12, 47, tzinfo=utc)
3611        t3 = time(13, 47, tzinfo=met)
3612        t4 = time(microsecond=40)
3613        t5 = time(microsecond=40, tzinfo=utc)
3614
3615        self.assertEqual(t1.tzinfo, est)
3616        self.assertEqual(t2.tzinfo, utc)
3617        self.assertEqual(t3.tzinfo, met)
3618        self.assertIsNone(t4.tzinfo)
3619        self.assertEqual(t5.tzinfo, utc)
3620
3621        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3622        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3623        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3624        self.assertIsNone(t4.utcoffset())
3625        self.assertRaises(TypeError, t1.utcoffset, "no args")
3626
3627        self.assertEqual(t1.tzname(), "EST")
3628        self.assertEqual(t2.tzname(), "UTC")
3629        self.assertEqual(t3.tzname(), "MET")
3630        self.assertIsNone(t4.tzname())
3631        self.assertRaises(TypeError, t1.tzname, "no args")
3632
3633        self.assertEqual(t1.dst(), timedelta(minutes=1))
3634        self.assertEqual(t2.dst(), timedelta(minutes=-2))
3635        self.assertEqual(t3.dst(), timedelta(minutes=3))
3636        self.assertIsNone(t4.dst())
3637        self.assertRaises(TypeError, t1.dst, "no args")
3638
3639        self.assertEqual(hash(t1), hash(t2))
3640        self.assertEqual(hash(t1), hash(t3))
3641        self.assertEqual(hash(t2), hash(t3))
3642
3643        self.assertEqual(t1, t2)
3644        self.assertEqual(t1, t3)
3645        self.assertEqual(t2, t3)
3646        self.assertNotEqual(t4, t5) # mixed tz-aware & naive
3647        self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3648        self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3649
3650        self.assertEqual(str(t1), "07:47:00-05:00")
3651        self.assertEqual(str(t2), "12:47:00+00:00")
3652        self.assertEqual(str(t3), "13:47:00+01:00")
3653        self.assertEqual(str(t4), "00:00:00.000040")
3654        self.assertEqual(str(t5), "00:00:00.000040+00:00")
3655
3656        self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3657        self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3658        self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3659        self.assertEqual(t4.isoformat(), "00:00:00.000040")
3660        self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3661
3662        d = 'datetime.time'
3663        self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3664        self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3665        self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3666        self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3667        self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3668
3669        self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3670                                     "07:47:00 %Z=EST %z=-0500")
3671        self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3672        self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3673
3674        yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3675        t1 = time(23, 59, tzinfo=yuck)
3676        self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3677                                     "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3678
3679        # Check that an invalid tzname result raises an exception.
3680        class Badtzname(tzinfo):
3681            tz = 42
3682            def tzname(self, dt): return self.tz
3683        t = time(2, 3, 4, tzinfo=Badtzname())
3684        self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3685        self.assertRaises(TypeError, t.strftime, "%Z")
3686
3687        # Issue #6697:
3688        if '_Fast' in self.__class__.__name__:
3689            Badtzname.tz = '\ud800'
3690            self.assertRaises(ValueError, t.strftime, "%Z")
3691
3692    def test_hash_edge_cases(self):
3693        # Offsets that overflow a basic time.
3694        t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3695        t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3696        self.assertEqual(hash(t1), hash(t2))
3697
3698        t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3699        t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3700        self.assertEqual(hash(t1), hash(t2))
3701
3702    def test_pickling(self):
3703        # Try one without a tzinfo.
3704        args = 20, 59, 16, 64**2
3705        orig = self.theclass(*args)
3706        for pickler, unpickler, proto in pickle_choices:
3707            green = pickler.dumps(orig, proto)
3708            derived = unpickler.loads(green)
3709            self.assertEqual(orig, derived)
3710        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3711
3712        # Try one with a tzinfo.
3713        tinfo = PicklableFixedOffset(-300, 'cookie')
3714        orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3715        for pickler, unpickler, proto in pickle_choices:
3716            green = pickler.dumps(orig, proto)
3717            derived = unpickler.loads(green)
3718            self.assertEqual(orig, derived)
3719            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3720            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3721            self.assertEqual(derived.tzname(), 'cookie')
3722        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3723
3724    def test_compat_unpickle(self):
3725        tests = [
3726            b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3727            b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3728            b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3729            b"(I-1\nI68400\nI0\ntRs"
3730            b"S'_FixedOffset__dstoffset'\nNs"
3731            b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3732
3733            b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3734            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3735            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3736            b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3737            b'U\x17_FixedOffset__dstoffsetN'
3738            b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3739
3740            b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3741            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3742            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3743            b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3744            b'U\x17_FixedOffset__dstoffsetN'
3745            b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3746        ]
3747
3748        tinfo = PicklableFixedOffset(-300, 'cookie')
3749        expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3750        for data in tests:
3751            for loads in pickle_loads:
3752                derived = loads(data, encoding='latin1')
3753                self.assertEqual(derived, expected, repr(data))
3754                self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3755                self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3756                self.assertEqual(derived.tzname(), 'cookie')
3757
3758    def test_more_bool(self):
3759        # time is always True.
3760        cls = self.theclass
3761
3762        t = cls(0, tzinfo=FixedOffset(-300, ""))
3763        self.assertTrue(t)
3764
3765        t = cls(5, tzinfo=FixedOffset(-300, ""))
3766        self.assertTrue(t)
3767
3768        t = cls(5, tzinfo=FixedOffset(300, ""))
3769        self.assertTrue(t)
3770
3771        t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3772        self.assertTrue(t)
3773
3774    def test_replace(self):
3775        cls = self.theclass
3776        z100 = FixedOffset(100, "+100")
3777        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3778        args = [1, 2, 3, 4, z100]
3779        base = cls(*args)
3780        self.assertEqual(base, base.replace())
3781
3782        i = 0
3783        for name, newval in (("hour", 5),
3784                             ("minute", 6),
3785                             ("second", 7),
3786                             ("microsecond", 8),
3787                             ("tzinfo", zm200)):
3788            newargs = args[:]
3789            newargs[i] = newval
3790            expected = cls(*newargs)
3791            got = base.replace(**{name: newval})
3792            self.assertEqual(expected, got)
3793            i += 1
3794
3795        # Ensure we can get rid of a tzinfo.
3796        self.assertEqual(base.tzname(), "+100")
3797        base2 = base.replace(tzinfo=None)
3798        self.assertIsNone(base2.tzinfo)
3799        self.assertIsNone(base2.tzname())
3800
3801        # Ensure we can add one.
3802        base3 = base2.replace(tzinfo=z100)
3803        self.assertEqual(base, base3)
3804        self.assertIs(base.tzinfo, base3.tzinfo)
3805
3806        # Out of bounds.
3807        base = cls(1)
3808        self.assertRaises(ValueError, base.replace, hour=24)
3809        self.assertRaises(ValueError, base.replace, minute=-1)
3810        self.assertRaises(ValueError, base.replace, second=100)
3811        self.assertRaises(ValueError, base.replace, microsecond=1000000)
3812
3813    def test_mixed_compare(self):
3814        t1 = self.theclass(1, 2, 3)
3815        t2 = self.theclass(1, 2, 3)
3816        self.assertEqual(t1, t2)
3817        t2 = t2.replace(tzinfo=None)
3818        self.assertEqual(t1, t2)
3819        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3820        self.assertEqual(t1, t2)
3821        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3822        self.assertNotEqual(t1, t2)
3823
3824        # In time w/ identical tzinfo objects, utcoffset is ignored.
3825        class Varies(tzinfo):
3826            def __init__(self):
3827                self.offset = timedelta(minutes=22)
3828            def utcoffset(self, t):
3829                self.offset += timedelta(minutes=1)
3830                return self.offset
3831
3832        v = Varies()
3833        t1 = t2.replace(tzinfo=v)
3834        t2 = t2.replace(tzinfo=v)
3835        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3836        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3837        self.assertEqual(t1, t2)
3838
3839        # But if they're not identical, it isn't ignored.
3840        t2 = t2.replace(tzinfo=Varies())
3841        self.assertTrue(t1 < t2)  # t1's offset counter still going up
3842
3843    def test_fromisoformat(self):
3844        time_examples = [
3845            (0, 0, 0, 0),
3846            (23, 59, 59, 999999),
3847        ]
3848
3849        hh = (9, 12, 20)
3850        mm = (5, 30)
3851        ss = (4, 45)
3852        usec = (0, 245000, 678901)
3853
3854        time_examples += list(itertools.product(hh, mm, ss, usec))
3855
3856        tzinfos = [None, timezone.utc,
3857                   timezone(timedelta(hours=2)),
3858                   timezone(timedelta(hours=6, minutes=27))]
3859
3860        for ttup in time_examples:
3861            for tzi in tzinfos:
3862                t = self.theclass(*ttup, tzinfo=tzi)
3863                tstr = t.isoformat()
3864
3865                with self.subTest(tstr=tstr):
3866                    t_rt = self.theclass.fromisoformat(tstr)
3867                    self.assertEqual(t, t_rt)
3868
3869    def test_fromisoformat_timezone(self):
3870        base_time = self.theclass(12, 30, 45, 217456)
3871
3872        tzoffsets = [
3873            timedelta(hours=5), timedelta(hours=2),
3874            timedelta(hours=6, minutes=27),
3875            timedelta(hours=12, minutes=32, seconds=30),
3876            timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3877        ]
3878
3879        tzoffsets += [-1 * td for td in tzoffsets]
3880
3881        tzinfos = [None, timezone.utc,
3882                   timezone(timedelta(hours=0))]
3883
3884        tzinfos += [timezone(td) for td in tzoffsets]
3885
3886        for tzi in tzinfos:
3887            t = base_time.replace(tzinfo=tzi)
3888            tstr = t.isoformat()
3889
3890            with self.subTest(tstr=tstr):
3891                t_rt = self.theclass.fromisoformat(tstr)
3892                assert t == t_rt, t_rt
3893
3894    def test_fromisoformat_timespecs(self):
3895        time_bases = [
3896            (8, 17, 45, 123456),
3897            (8, 17, 45, 0)
3898        ]
3899
3900        tzinfos = [None, timezone.utc,
3901                   timezone(timedelta(hours=-5)),
3902                   timezone(timedelta(hours=2)),
3903                   timezone(timedelta(hours=6, minutes=27))]
3904
3905        timespecs = ['hours', 'minutes', 'seconds',
3906                     'milliseconds', 'microseconds']
3907
3908        for ip, ts in enumerate(timespecs):
3909            for tzi in tzinfos:
3910                for t_tuple in time_bases:
3911                    if ts == 'milliseconds':
3912                        new_microseconds = 1000 * (t_tuple[-1] // 1000)
3913                        t_tuple = t_tuple[0:-1] + (new_microseconds,)
3914
3915                    t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3916                    tstr = t.isoformat(timespec=ts)
3917                    with self.subTest(tstr=tstr):
3918                        t_rt = self.theclass.fromisoformat(tstr)
3919                        self.assertEqual(t, t_rt)
3920
3921    def test_fromisoformat_fails(self):
3922        bad_strs = [
3923            '',                         # Empty string
3924            '12\ud80000',               # Invalid separator - surrogate char
3925            '12:',                      # Ends on a separator
3926            '12:30:',                   # Ends on a separator
3927            '12:30:15.',                # Ends on a separator
3928            '1',                        # Incomplete hours
3929            '12:3',                     # Incomplete minutes
3930            '12:30:1',                  # Incomplete seconds
3931            '1a:30:45.334034',          # Invalid character in hours
3932            '12:a0:45.334034',          # Invalid character in minutes
3933            '12:30:a5.334034',          # Invalid character in seconds
3934            '12:30:45.1234',            # Too many digits for milliseconds
3935            '12:30:45.1234567',         # Too many digits for microseconds
3936            '12:30:45.123456+24:30',    # Invalid time zone offset
3937            '12:30:45.123456-24:30',    # Invalid negative offset
3938            '12:30:45',                 # Uses full-width unicode colons
3939            '12:30:45․123456',          # Uses \u2024 in place of decimal point
3940            '12:30:45a',                # Extra at tend of basic time
3941            '12:30:45.123a',            # Extra at end of millisecond time
3942            '12:30:45.123456a',         # Extra at end of microsecond time
3943            '12:30:45.123456+12:00:30a',    # Extra at end of full time
3944        ]
3945
3946        for bad_str in bad_strs:
3947            with self.subTest(bad_str=bad_str):
3948                with self.assertRaises(ValueError):
3949                    self.theclass.fromisoformat(bad_str)
3950
3951    def test_fromisoformat_fails_typeerror(self):
3952        # Test the fromisoformat fails when passed the wrong type
3953        import io
3954
3955        bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3956
3957        for bad_type in bad_types:
3958            with self.assertRaises(TypeError):
3959                self.theclass.fromisoformat(bad_type)
3960
3961    def test_fromisoformat_subclass(self):
3962        class TimeSubclass(self.theclass):
3963            pass
3964
3965        tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
3966        tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
3967
3968        self.assertEqual(tsc, tsc_rt)
3969        self.assertIsInstance(tsc_rt, TimeSubclass)
3970
3971    def test_subclass_timetz(self):
3972
3973        class C(self.theclass):
3974            theAnswer = 42
3975
3976            def __new__(cls, *args, **kws):
3977                temp = kws.copy()
3978                extra = temp.pop('extra')
3979                result = self.theclass.__new__(cls, *args, **temp)
3980                result.extra = extra
3981                return result
3982
3983            def newmeth(self, start):
3984                return start + self.hour + self.second
3985
3986        args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
3987
3988        dt1 = self.theclass(*args)
3989        dt2 = C(*args, **{'extra': 7})
3990
3991        self.assertEqual(dt2.__class__, C)
3992        self.assertEqual(dt2.theAnswer, 42)
3993        self.assertEqual(dt2.extra, 7)
3994        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
3995        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3996
3997
3998# Testing datetime objects with a non-None tzinfo.
3999
4000class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
4001    theclass = datetime
4002
4003    def test_trivial(self):
4004        dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
4005        self.assertEqual(dt.year, 1)
4006        self.assertEqual(dt.month, 2)
4007        self.assertEqual(dt.day, 3)
4008        self.assertEqual(dt.hour, 4)
4009        self.assertEqual(dt.minute, 5)
4010        self.assertEqual(dt.second, 6)
4011        self.assertEqual(dt.microsecond, 7)
4012        self.assertEqual(dt.tzinfo, None)
4013
4014    def test_even_more_compare(self):
4015        # The test_compare() and test_more_compare() inherited from TestDate
4016        # and TestDateTime covered non-tzinfo cases.
4017
4018        # Smallest possible after UTC adjustment.
4019        t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4020        # Largest possible after UTC adjustment.
4021        t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4022                           tzinfo=FixedOffset(-1439, ""))
4023
4024        # Make sure those compare correctly, and w/o overflow.
4025        self.assertTrue(t1 < t2)
4026        self.assertTrue(t1 != t2)
4027        self.assertTrue(t2 > t1)
4028
4029        self.assertEqual(t1, t1)
4030        self.assertEqual(t2, t2)
4031
4032        # Equal afer adjustment.
4033        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4034        t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4035        self.assertEqual(t1, t2)
4036
4037        # Change t1 not to subtract a minute, and t1 should be larger.
4038        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4039        self.assertTrue(t1 > t2)
4040
4041        # Change t1 to subtract 2 minutes, and t1 should be smaller.
4042        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4043        self.assertTrue(t1 < t2)
4044
4045        # Back to the original t1, but make seconds resolve it.
4046        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4047                           second=1)
4048        self.assertTrue(t1 > t2)
4049
4050        # Likewise, but make microseconds resolve it.
4051        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4052                           microsecond=1)
4053        self.assertTrue(t1 > t2)
4054
4055        # Make t2 naive and it should differ.
4056        t2 = self.theclass.min
4057        self.assertNotEqual(t1, t2)
4058        self.assertEqual(t2, t2)
4059        # and > comparison should fail
4060        with self.assertRaises(TypeError):
4061            t1 > t2
4062
4063        # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4064        class Naive(tzinfo):
4065            def utcoffset(self, dt): return None
4066        t2 = self.theclass(5, 6, 7, tzinfo=Naive())
4067        self.assertNotEqual(t1, t2)
4068        self.assertEqual(t2, t2)
4069
4070        # OTOH, it's OK to compare two of these mixing the two ways of being
4071        # naive.
4072        t1 = self.theclass(5, 6, 7)
4073        self.assertEqual(t1, t2)
4074
4075        # Try a bogus uctoffset.
4076        class Bogus(tzinfo):
4077            def utcoffset(self, dt):
4078                return timedelta(minutes=1440) # out of bounds
4079        t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4080        t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4081        self.assertRaises(ValueError, lambda: t1 == t2)
4082
4083    def test_pickling(self):
4084        # Try one without a tzinfo.
4085        args = 6, 7, 23, 20, 59, 1, 64**2
4086        orig = self.theclass(*args)
4087        for pickler, unpickler, proto in pickle_choices:
4088            green = pickler.dumps(orig, proto)
4089            derived = unpickler.loads(green)
4090            self.assertEqual(orig, derived)
4091        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
4092
4093        # Try one with a tzinfo.
4094        tinfo = PicklableFixedOffset(-300, 'cookie')
4095        orig = self.theclass(*args, **{'tzinfo': tinfo})
4096        derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4097        for pickler, unpickler, proto in pickle_choices:
4098            green = pickler.dumps(orig, proto)
4099            derived = unpickler.loads(green)
4100            self.assertEqual(orig, derived)
4101            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4102            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4103            self.assertEqual(derived.tzname(), 'cookie')
4104        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
4105
4106    def test_compat_unpickle(self):
4107        tests = [
4108            b'cdatetime\ndatetime\n'
4109            b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4110            b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4111            b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4112            b'(I-1\nI68400\nI0\ntRs'
4113            b"S'_FixedOffset__dstoffset'\nNs"
4114            b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4115
4116            b'cdatetime\ndatetime\n'
4117            b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4118            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4119            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4120            b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4121            b'U\x17_FixedOffset__dstoffsetN'
4122            b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4123
4124            b'\x80\x02cdatetime\ndatetime\n'
4125            b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4126            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4127            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4128            b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4129            b'U\x17_FixedOffset__dstoffsetN'
4130            b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4131        ]
4132        args = 2015, 11, 27, 20, 59, 1, 123456
4133        tinfo = PicklableFixedOffset(-300, 'cookie')
4134        expected = self.theclass(*args, **{'tzinfo': tinfo})
4135        for data in tests:
4136            for loads in pickle_loads:
4137                derived = loads(data, encoding='latin1')
4138                self.assertEqual(derived, expected)
4139                self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4140                self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4141                self.assertEqual(derived.tzname(), 'cookie')
4142
4143    def test_extreme_hashes(self):
4144        # If an attempt is made to hash these via subtracting the offset
4145        # then hashing a datetime object, OverflowError results.  The
4146        # Python implementation used to blow up here.
4147        t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4148        hash(t)
4149        t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4150                          tzinfo=FixedOffset(-1439, ""))
4151        hash(t)
4152
4153        # OTOH, an OOB offset should blow up.
4154        t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4155        self.assertRaises(ValueError, hash, t)
4156
4157    def test_zones(self):
4158        est = FixedOffset(-300, "EST")
4159        utc = FixedOffset(0, "UTC")
4160        met = FixedOffset(60, "MET")
4161        t1 = datetime(2002, 3, 19,  7, 47, tzinfo=est)
4162        t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4163        t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4164        self.assertEqual(t1.tzinfo, est)
4165        self.assertEqual(t2.tzinfo, utc)
4166        self.assertEqual(t3.tzinfo, met)
4167        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4168        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4169        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4170        self.assertEqual(t1.tzname(), "EST")
4171        self.assertEqual(t2.tzname(), "UTC")
4172        self.assertEqual(t3.tzname(), "MET")
4173        self.assertEqual(hash(t1), hash(t2))
4174        self.assertEqual(hash(t1), hash(t3))
4175        self.assertEqual(hash(t2), hash(t3))
4176        self.assertEqual(t1, t2)
4177        self.assertEqual(t1, t3)
4178        self.assertEqual(t2, t3)
4179        self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4180        self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4181        self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4182        d = 'datetime.datetime(2002, 3, 19, '
4183        self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4184        self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4185        self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4186
4187    def test_combine(self):
4188        met = FixedOffset(60, "MET")
4189        d = date(2002, 3, 4)
4190        tz = time(18, 45, 3, 1234, tzinfo=met)
4191        dt = datetime.combine(d, tz)
4192        self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4193                                        tzinfo=met))
4194
4195    def test_extract(self):
4196        met = FixedOffset(60, "MET")
4197        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4198        self.assertEqual(dt.date(), date(2002, 3, 4))
4199        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4200        self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4201
4202    def test_tz_aware_arithmetic(self):
4203        now = self.theclass.now()
4204        tz55 = FixedOffset(-330, "west 5:30")
4205        timeaware = now.time().replace(tzinfo=tz55)
4206        nowaware = self.theclass.combine(now.date(), timeaware)
4207        self.assertIs(nowaware.tzinfo, tz55)
4208        self.assertEqual(nowaware.timetz(), timeaware)
4209
4210        # Can't mix aware and non-aware.
4211        self.assertRaises(TypeError, lambda: now - nowaware)
4212        self.assertRaises(TypeError, lambda: nowaware - now)
4213
4214        # And adding datetime's doesn't make sense, aware or not.
4215        self.assertRaises(TypeError, lambda: now + nowaware)
4216        self.assertRaises(TypeError, lambda: nowaware + now)
4217        self.assertRaises(TypeError, lambda: nowaware + nowaware)
4218
4219        # Subtracting should yield 0.
4220        self.assertEqual(now - now, timedelta(0))
4221        self.assertEqual(nowaware - nowaware, timedelta(0))
4222
4223        # Adding a delta should preserve tzinfo.
4224        delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4225        nowawareplus = nowaware + delta
4226        self.assertIs(nowaware.tzinfo, tz55)
4227        nowawareplus2 = delta + nowaware
4228        self.assertIs(nowawareplus2.tzinfo, tz55)
4229        self.assertEqual(nowawareplus, nowawareplus2)
4230
4231        # that - delta should be what we started with, and that - what we
4232        # started with should be delta.
4233        diff = nowawareplus - delta
4234        self.assertIs(diff.tzinfo, tz55)
4235        self.assertEqual(nowaware, diff)
4236        self.assertRaises(TypeError, lambda: delta - nowawareplus)
4237        self.assertEqual(nowawareplus - nowaware, delta)
4238
4239        # Make up a random timezone.
4240        tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4241        # Attach it to nowawareplus.
4242        nowawareplus = nowawareplus.replace(tzinfo=tzr)
4243        self.assertIs(nowawareplus.tzinfo, tzr)
4244        # Make sure the difference takes the timezone adjustments into account.
4245        got = nowaware - nowawareplus
4246        # Expected:  (nowaware base - nowaware offset) -
4247        #            (nowawareplus base - nowawareplus offset) =
4248        #            (nowaware base - nowawareplus base) +
4249        #            (nowawareplus offset - nowaware offset) =
4250        #            -delta + nowawareplus offset - nowaware offset
4251        expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4252        self.assertEqual(got, expected)
4253
4254        # Try max possible difference.
4255        min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4256        max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4257                            tzinfo=FixedOffset(-1439, "max"))
4258        maxdiff = max - min
4259        self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4260                                  timedelta(minutes=2*1439))
4261        # Different tzinfo, but the same offset
4262        tza = timezone(HOUR, 'A')
4263        tzb = timezone(HOUR, 'B')
4264        delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4265        self.assertEqual(delta, self.theclass.min - self.theclass.max)
4266
4267    def test_tzinfo_now(self):
4268        meth = self.theclass.now
4269        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4270        base = meth()
4271        # Try with and without naming the keyword.
4272        off42 = FixedOffset(42, "42")
4273        another = meth(off42)
4274        again = meth(tz=off42)
4275        self.assertIs(another.tzinfo, again.tzinfo)
4276        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4277        # Bad argument with and w/o naming the keyword.
4278        self.assertRaises(TypeError, meth, 16)
4279        self.assertRaises(TypeError, meth, tzinfo=16)
4280        # Bad keyword name.
4281        self.assertRaises(TypeError, meth, tinfo=off42)
4282        # Too many args.
4283        self.assertRaises(TypeError, meth, off42, off42)
4284
4285        # We don't know which time zone we're in, and don't have a tzinfo
4286        # class to represent it, so seeing whether a tz argument actually
4287        # does a conversion is tricky.
4288        utc = FixedOffset(0, "utc", 0)
4289        for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4290                        timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4291            for dummy in range(3):
4292                now = datetime.now(weirdtz)
4293                self.assertIs(now.tzinfo, weirdtz)
4294                utcnow = datetime.utcnow().replace(tzinfo=utc)
4295                now2 = utcnow.astimezone(weirdtz)
4296                if abs(now - now2) < timedelta(seconds=30):
4297                    break
4298                # Else the code is broken, or more than 30 seconds passed between
4299                # calls; assuming the latter, just try again.
4300            else:
4301                # Three strikes and we're out.
4302                self.fail("utcnow(), now(tz), or astimezone() may be broken")
4303
4304    def test_tzinfo_fromtimestamp(self):
4305        import time
4306        meth = self.theclass.fromtimestamp
4307        ts = time.time()
4308        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4309        base = meth(ts)
4310        # Try with and without naming the keyword.
4311        off42 = FixedOffset(42, "42")
4312        another = meth(ts, off42)
4313        again = meth(ts, tz=off42)
4314        self.assertIs(another.tzinfo, again.tzinfo)
4315        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4316        # Bad argument with and w/o naming the keyword.
4317        self.assertRaises(TypeError, meth, ts, 16)
4318        self.assertRaises(TypeError, meth, ts, tzinfo=16)
4319        # Bad keyword name.
4320        self.assertRaises(TypeError, meth, ts, tinfo=off42)
4321        # Too many args.
4322        self.assertRaises(TypeError, meth, ts, off42, off42)
4323        # Too few args.
4324        self.assertRaises(TypeError, meth)
4325
4326        # Try to make sure tz= actually does some conversion.
4327        timestamp = 1000000000
4328        utcdatetime = datetime.utcfromtimestamp(timestamp)
4329        # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4330        # But on some flavor of Mac, it's nowhere near that.  So we can't have
4331        # any idea here what time that actually is, we can only test that
4332        # relative changes match.
4333        utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4334        tz = FixedOffset(utcoffset, "tz", 0)
4335        expected = utcdatetime + utcoffset
4336        got = datetime.fromtimestamp(timestamp, tz)
4337        self.assertEqual(expected, got.replace(tzinfo=None))
4338
4339    def test_tzinfo_utcnow(self):
4340        meth = self.theclass.utcnow
4341        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4342        base = meth()
4343        # Try with and without naming the keyword; for whatever reason,
4344        # utcnow() doesn't accept a tzinfo argument.
4345        off42 = FixedOffset(42, "42")
4346        self.assertRaises(TypeError, meth, off42)
4347        self.assertRaises(TypeError, meth, tzinfo=off42)
4348
4349    def test_tzinfo_utcfromtimestamp(self):
4350        import time
4351        meth = self.theclass.utcfromtimestamp
4352        ts = time.time()
4353        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4354        base = meth(ts)
4355        # Try with and without naming the keyword; for whatever reason,
4356        # utcfromtimestamp() doesn't accept a tzinfo argument.
4357        off42 = FixedOffset(42, "42")
4358        self.assertRaises(TypeError, meth, ts, off42)
4359        self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4360
4361    def test_tzinfo_timetuple(self):
4362        # TestDateTime tested most of this.  datetime adds a twist to the
4363        # DST flag.
4364        class DST(tzinfo):
4365            def __init__(self, dstvalue):
4366                if isinstance(dstvalue, int):
4367                    dstvalue = timedelta(minutes=dstvalue)
4368                self.dstvalue = dstvalue
4369            def dst(self, dt):
4370                return self.dstvalue
4371
4372        cls = self.theclass
4373        for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4374            d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4375            t = d.timetuple()
4376            self.assertEqual(1, t.tm_year)
4377            self.assertEqual(1, t.tm_mon)
4378            self.assertEqual(1, t.tm_mday)
4379            self.assertEqual(10, t.tm_hour)
4380            self.assertEqual(20, t.tm_min)
4381            self.assertEqual(30, t.tm_sec)
4382            self.assertEqual(0, t.tm_wday)
4383            self.assertEqual(1, t.tm_yday)
4384            self.assertEqual(flag, t.tm_isdst)
4385
4386        # dst() returns wrong type.
4387        self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4388
4389        # dst() at the edge.
4390        self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4391        self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4392
4393        # dst() out of range.
4394        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4395        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4396
4397    def test_utctimetuple(self):
4398        class DST(tzinfo):
4399            def __init__(self, dstvalue=0):
4400                if isinstance(dstvalue, int):
4401                    dstvalue = timedelta(minutes=dstvalue)
4402                self.dstvalue = dstvalue
4403            def dst(self, dt):
4404                return self.dstvalue
4405
4406        cls = self.theclass
4407        # This can't work:  DST didn't implement utcoffset.
4408        self.assertRaises(NotImplementedError,
4409                          cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4410
4411        class UOFS(DST):
4412            def __init__(self, uofs, dofs=None):
4413                DST.__init__(self, dofs)
4414                self.uofs = timedelta(minutes=uofs)
4415            def utcoffset(self, dt):
4416                return self.uofs
4417
4418        for dstvalue in -33, 33, 0, None:
4419            d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4420            t = d.utctimetuple()
4421            self.assertEqual(d.year, t.tm_year)
4422            self.assertEqual(d.month, t.tm_mon)
4423            self.assertEqual(d.day, t.tm_mday)
4424            self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4425            self.assertEqual(13, t.tm_min)
4426            self.assertEqual(d.second, t.tm_sec)
4427            self.assertEqual(d.weekday(), t.tm_wday)
4428            self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4429                             t.tm_yday)
4430            # Ensure tm_isdst is 0 regardless of what dst() says: DST
4431            # is never in effect for a UTC time.
4432            self.assertEqual(0, t.tm_isdst)
4433
4434        # For naive datetime, utctimetuple == timetuple except for isdst
4435        d = cls(1, 2, 3, 10, 20, 30, 40)
4436        t = d.utctimetuple()
4437        self.assertEqual(t[:-1], d.timetuple()[:-1])
4438        self.assertEqual(0, t.tm_isdst)
4439        # Same if utcoffset is None
4440        class NOFS(DST):
4441            def utcoffset(self, dt):
4442                return None
4443        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4444        t = d.utctimetuple()
4445        self.assertEqual(t[:-1], d.timetuple()[:-1])
4446        self.assertEqual(0, t.tm_isdst)
4447        # Check that bad tzinfo is detected
4448        class BOFS(DST):
4449            def utcoffset(self, dt):
4450                return "EST"
4451        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4452        self.assertRaises(TypeError, d.utctimetuple)
4453
4454        # Check that utctimetuple() is the same as
4455        # astimezone(utc).timetuple()
4456        d = cls(2010, 11, 13, 14, 15, 16, 171819)
4457        for tz in [timezone.min, timezone.utc, timezone.max]:
4458            dtz = d.replace(tzinfo=tz)
4459            self.assertEqual(dtz.utctimetuple()[:-1],
4460                             dtz.astimezone(timezone.utc).timetuple()[:-1])
4461        # At the edges, UTC adjustment can produce years out-of-range
4462        # for a datetime object.  Ensure that an OverflowError is
4463        # raised.
4464        tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4465        # That goes back 1 minute less than a full day.
4466        self.assertRaises(OverflowError, tiny.utctimetuple)
4467
4468        huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4469        # That goes forward 1 minute less than a full day.
4470        self.assertRaises(OverflowError, huge.utctimetuple)
4471        # More overflow cases
4472        tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4473        self.assertRaises(OverflowError, tiny.utctimetuple)
4474        huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4475        self.assertRaises(OverflowError, huge.utctimetuple)
4476
4477    def test_tzinfo_isoformat(self):
4478        zero = FixedOffset(0, "+00:00")
4479        plus = FixedOffset(220, "+03:40")
4480        minus = FixedOffset(-231, "-03:51")
4481        unknown = FixedOffset(None, "")
4482
4483        cls = self.theclass
4484        datestr = '0001-02-03'
4485        for ofs in None, zero, plus, minus, unknown:
4486            for us in 0, 987001:
4487                d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4488                timestr = '04:05:59' + (us and '.987001' or '')
4489                ofsstr = ofs is not None and d.tzname() or ''
4490                tailstr = timestr + ofsstr
4491                iso = d.isoformat()
4492                self.assertEqual(iso, datestr + 'T' + tailstr)
4493                self.assertEqual(iso, d.isoformat('T'))
4494                self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4495                self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4496                self.assertEqual(str(d), datestr + ' ' + tailstr)
4497
4498    def test_replace(self):
4499        cls = self.theclass
4500        z100 = FixedOffset(100, "+100")
4501        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4502        args = [1, 2, 3, 4, 5, 6, 7, z100]
4503        base = cls(*args)
4504        self.assertEqual(base, base.replace())
4505
4506        i = 0
4507        for name, newval in (("year", 2),
4508                             ("month", 3),
4509                             ("day", 4),
4510                             ("hour", 5),
4511                             ("minute", 6),
4512                             ("second", 7),
4513                             ("microsecond", 8),
4514                             ("tzinfo", zm200)):
4515            newargs = args[:]
4516            newargs[i] = newval
4517            expected = cls(*newargs)
4518            got = base.replace(**{name: newval})
4519            self.assertEqual(expected, got)
4520            i += 1
4521
4522        # Ensure we can get rid of a tzinfo.
4523        self.assertEqual(base.tzname(), "+100")
4524        base2 = base.replace(tzinfo=None)
4525        self.assertIsNone(base2.tzinfo)
4526        self.assertIsNone(base2.tzname())
4527
4528        # Ensure we can add one.
4529        base3 = base2.replace(tzinfo=z100)
4530        self.assertEqual(base, base3)
4531        self.assertIs(base.tzinfo, base3.tzinfo)
4532
4533        # Out of bounds.
4534        base = cls(2000, 2, 29)
4535        self.assertRaises(ValueError, base.replace, year=2001)
4536
4537    def test_more_astimezone(self):
4538        # The inherited test_astimezone covered some trivial and error cases.
4539        fnone = FixedOffset(None, "None")
4540        f44m = FixedOffset(44, "44")
4541        fm5h = FixedOffset(-timedelta(hours=5), "m300")
4542
4543        dt = self.theclass.now(tz=f44m)
4544        self.assertIs(dt.tzinfo, f44m)
4545        # Replacing with degenerate tzinfo raises an exception.
4546        self.assertRaises(ValueError, dt.astimezone, fnone)
4547        # Replacing with same tzinfo makes no change.
4548        x = dt.astimezone(dt.tzinfo)
4549        self.assertIs(x.tzinfo, f44m)
4550        self.assertEqual(x.date(), dt.date())
4551        self.assertEqual(x.time(), dt.time())
4552
4553        # Replacing with different tzinfo does adjust.
4554        got = dt.astimezone(fm5h)
4555        self.assertIs(got.tzinfo, fm5h)
4556        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4557        expected = dt - dt.utcoffset()  # in effect, convert to UTC
4558        expected += fm5h.utcoffset(dt)  # and from there to local time
4559        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4560        self.assertEqual(got.date(), expected.date())
4561        self.assertEqual(got.time(), expected.time())
4562        self.assertEqual(got.timetz(), expected.timetz())
4563        self.assertIs(got.tzinfo, expected.tzinfo)
4564        self.assertEqual(got, expected)
4565
4566    @support.run_with_tz('UTC')
4567    def test_astimezone_default_utc(self):
4568        dt = self.theclass.now(timezone.utc)
4569        self.assertEqual(dt.astimezone(None), dt)
4570        self.assertEqual(dt.astimezone(), dt)
4571
4572    # Note that offset in TZ variable has the opposite sign to that
4573    # produced by %z directive.
4574    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4575    def test_astimezone_default_eastern(self):
4576        dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4577        local = dt.astimezone()
4578        self.assertEqual(dt, local)
4579        self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
4580        dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4581        local = dt.astimezone()
4582        self.assertEqual(dt, local)
4583        self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
4584
4585    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4586    def test_astimezone_default_near_fold(self):
4587        # Issue #26616.
4588        u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4589        t = u.astimezone()
4590        s = t.astimezone()
4591        self.assertEqual(t.tzinfo, s.tzinfo)
4592
4593    def test_aware_subtract(self):
4594        cls = self.theclass
4595
4596        # Ensure that utcoffset() is ignored when the operands have the
4597        # same tzinfo member.
4598        class OperandDependentOffset(tzinfo):
4599            def utcoffset(self, t):
4600                if t.minute < 10:
4601                    # d0 and d1 equal after adjustment
4602                    return timedelta(minutes=t.minute)
4603                else:
4604                    # d2 off in the weeds
4605                    return timedelta(minutes=59)
4606
4607        base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4608        d0 = base.replace(minute=3)
4609        d1 = base.replace(minute=9)
4610        d2 = base.replace(minute=11)
4611        for x in d0, d1, d2:
4612            for y in d0, d1, d2:
4613                got = x - y
4614                expected = timedelta(minutes=x.minute - y.minute)
4615                self.assertEqual(got, expected)
4616
4617        # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4618        # ignored.
4619        base = cls(8, 9, 10, 11, 12, 13, 14)
4620        d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4621        d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4622        d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4623        for x in d0, d1, d2:
4624            for y in d0, d1, d2:
4625                got = x - y
4626                if (x is d0 or x is d1) and (y is d0 or y is d1):
4627                    expected = timedelta(0)
4628                elif x is y is d2:
4629                    expected = timedelta(0)
4630                elif x is d2:
4631                    expected = timedelta(minutes=(11-59)-0)
4632                else:
4633                    assert y is d2
4634                    expected = timedelta(minutes=0-(11-59))
4635                self.assertEqual(got, expected)
4636
4637    def test_mixed_compare(self):
4638        t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4639        t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4640        self.assertEqual(t1, t2)
4641        t2 = t2.replace(tzinfo=None)
4642        self.assertEqual(t1, t2)
4643        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4644        self.assertEqual(t1, t2)
4645        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
4646        self.assertNotEqual(t1, t2)
4647
4648        # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4649        class Varies(tzinfo):
4650            def __init__(self):
4651                self.offset = timedelta(minutes=22)
4652            def utcoffset(self, t):
4653                self.offset += timedelta(minutes=1)
4654                return self.offset
4655
4656        v = Varies()
4657        t1 = t2.replace(tzinfo=v)
4658        t2 = t2.replace(tzinfo=v)
4659        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4660        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4661        self.assertEqual(t1, t2)
4662
4663        # But if they're not identical, it isn't ignored.
4664        t2 = t2.replace(tzinfo=Varies())
4665        self.assertTrue(t1 < t2)  # t1's offset counter still going up
4666
4667    def test_subclass_datetimetz(self):
4668
4669        class C(self.theclass):
4670            theAnswer = 42
4671
4672            def __new__(cls, *args, **kws):
4673                temp = kws.copy()
4674                extra = temp.pop('extra')
4675                result = self.theclass.__new__(cls, *args, **temp)
4676                result.extra = extra
4677                return result
4678
4679            def newmeth(self, start):
4680                return start + self.hour + self.year
4681
4682        args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4683
4684        dt1 = self.theclass(*args)
4685        dt2 = C(*args, **{'extra': 7})
4686
4687        self.assertEqual(dt2.__class__, C)
4688        self.assertEqual(dt2.theAnswer, 42)
4689        self.assertEqual(dt2.extra, 7)
4690        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4691        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4692
4693# Pain to set up DST-aware tzinfo classes.
4694
4695def first_sunday_on_or_after(dt):
4696    days_to_go = 6 - dt.weekday()
4697    if days_to_go:
4698        dt += timedelta(days_to_go)
4699    return dt
4700
4701ZERO = timedelta(0)
4702MINUTE = timedelta(minutes=1)
4703HOUR = timedelta(hours=1)
4704DAY = timedelta(days=1)
4705# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4706DSTSTART = datetime(1, 4, 1, 2)
4707# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4708# which is the first Sunday on or after Oct 25.  Because we view 1:MM as
4709# being standard time on that day, there is no spelling in local time of
4710# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4711DSTEND = datetime(1, 10, 25, 1)
4712
4713class USTimeZone(tzinfo):
4714
4715    def __init__(self, hours, reprname, stdname, dstname):
4716        self.stdoffset = timedelta(hours=hours)
4717        self.reprname = reprname
4718        self.stdname = stdname
4719        self.dstname = dstname
4720
4721    def __repr__(self):
4722        return self.reprname
4723
4724    def tzname(self, dt):
4725        if self.dst(dt):
4726            return self.dstname
4727        else:
4728            return self.stdname
4729
4730    def utcoffset(self, dt):
4731        return self.stdoffset + self.dst(dt)
4732
4733    def dst(self, dt):
4734        if dt is None or dt.tzinfo is None:
4735            # An exception instead may be sensible here, in one or more of
4736            # the cases.
4737            return ZERO
4738        assert dt.tzinfo is self
4739
4740        # Find first Sunday in April.
4741        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4742        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4743
4744        # Find last Sunday in October.
4745        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4746        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4747
4748        # Can't compare naive to aware objects, so strip the timezone from
4749        # dt first.
4750        if start <= dt.replace(tzinfo=None) < end:
4751            return HOUR
4752        else:
4753            return ZERO
4754
4755Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
4756Central  = USTimeZone(-6, "Central",  "CST", "CDT")
4757Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4758Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
4759utc_real = FixedOffset(0, "UTC", 0)
4760# For better test coverage, we want another flavor of UTC that's west of
4761# the Eastern and Pacific timezones.
4762utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4763
4764class TestTimezoneConversions(unittest.TestCase):
4765    # The DST switch times for 2002, in std time.
4766    dston = datetime(2002, 4, 7, 2)
4767    dstoff = datetime(2002, 10, 27, 1)
4768
4769    theclass = datetime
4770
4771    # Check a time that's inside DST.
4772    def checkinside(self, dt, tz, utc, dston, dstoff):
4773        self.assertEqual(dt.dst(), HOUR)
4774
4775        # Conversion to our own timezone is always an identity.
4776        self.assertEqual(dt.astimezone(tz), dt)
4777
4778        asutc = dt.astimezone(utc)
4779        there_and_back = asutc.astimezone(tz)
4780
4781        # Conversion to UTC and back isn't always an identity here,
4782        # because there are redundant spellings (in local time) of
4783        # UTC time when DST begins:  the clock jumps from 1:59:59
4784        # to 3:00:00, and a local time of 2:MM:SS doesn't really
4785        # make sense then.  The classes above treat 2:MM:SS as
4786        # daylight time then (it's "after 2am"), really an alias
4787        # for 1:MM:SS standard time.  The latter form is what
4788        # conversion back from UTC produces.
4789        if dt.date() == dston.date() and dt.hour == 2:
4790            # We're in the redundant hour, and coming back from
4791            # UTC gives the 1:MM:SS standard-time spelling.
4792            self.assertEqual(there_and_back + HOUR, dt)
4793            # Although during was considered to be in daylight
4794            # time, there_and_back is not.
4795            self.assertEqual(there_and_back.dst(), ZERO)
4796            # They're the same times in UTC.
4797            self.assertEqual(there_and_back.astimezone(utc),
4798                             dt.astimezone(utc))
4799        else:
4800            # We're not in the redundant hour.
4801            self.assertEqual(dt, there_and_back)
4802
4803        # Because we have a redundant spelling when DST begins, there is
4804        # (unfortunately) an hour when DST ends that can't be spelled at all in
4805        # local time.  When DST ends, the clock jumps from 1:59 back to 1:00
4806        # again.  The hour 1:MM DST has no spelling then:  1:MM is taken to be
4807        # standard time.  1:MM DST == 0:MM EST, but 0:MM is taken to be
4808        # daylight time.  The hour 1:MM daylight == 0:MM standard can't be
4809        # expressed in local time.  Nevertheless, we want conversion back
4810        # from UTC to mimic the local clock's "repeat an hour" behavior.
4811        nexthour_utc = asutc + HOUR
4812        nexthour_tz = nexthour_utc.astimezone(tz)
4813        if dt.date() == dstoff.date() and dt.hour == 0:
4814            # We're in the hour before the last DST hour.  The last DST hour
4815            # is ineffable.  We want the conversion back to repeat 1:MM.
4816            self.assertEqual(nexthour_tz, dt.replace(hour=1))
4817            nexthour_utc += HOUR
4818            nexthour_tz = nexthour_utc.astimezone(tz)
4819            self.assertEqual(nexthour_tz, dt.replace(hour=1))
4820        else:
4821            self.assertEqual(nexthour_tz - dt, HOUR)
4822
4823    # Check a time that's outside DST.
4824    def checkoutside(self, dt, tz, utc):
4825        self.assertEqual(dt.dst(), ZERO)
4826
4827        # Conversion to our own timezone is always an identity.
4828        self.assertEqual(dt.astimezone(tz), dt)
4829
4830        # Converting to UTC and back is an identity too.
4831        asutc = dt.astimezone(utc)
4832        there_and_back = asutc.astimezone(tz)
4833        self.assertEqual(dt, there_and_back)
4834
4835    def convert_between_tz_and_utc(self, tz, utc):
4836        dston = self.dston.replace(tzinfo=tz)
4837        # Because 1:MM on the day DST ends is taken as being standard time,
4838        # there is no spelling in tz for the last hour of daylight time.
4839        # For purposes of the test, the last hour of DST is 0:MM, which is
4840        # taken as being daylight time (and 1:MM is taken as being standard
4841        # time).
4842        dstoff = self.dstoff.replace(tzinfo=tz)
4843        for delta in (timedelta(weeks=13),
4844                      DAY,
4845                      HOUR,
4846                      timedelta(minutes=1),
4847                      timedelta(microseconds=1)):
4848
4849            self.checkinside(dston, tz, utc, dston, dstoff)
4850            for during in dston + delta, dstoff - delta:
4851                self.checkinside(during, tz, utc, dston, dstoff)
4852
4853            self.checkoutside(dstoff, tz, utc)
4854            for outside in dston - delta, dstoff + delta:
4855                self.checkoutside(outside, tz, utc)
4856
4857    def test_easy(self):
4858        # Despite the name of this test, the endcases are excruciating.
4859        self.convert_between_tz_and_utc(Eastern, utc_real)
4860        self.convert_between_tz_and_utc(Pacific, utc_real)
4861        self.convert_between_tz_and_utc(Eastern, utc_fake)
4862        self.convert_between_tz_and_utc(Pacific, utc_fake)
4863        # The next is really dancing near the edge.  It works because
4864        # Pacific and Eastern are far enough apart that their "problem
4865        # hours" don't overlap.
4866        self.convert_between_tz_and_utc(Eastern, Pacific)
4867        self.convert_between_tz_and_utc(Pacific, Eastern)
4868        # OTOH, these fail!  Don't enable them.  The difficulty is that
4869        # the edge case tests assume that every hour is representable in
4870        # the "utc" class.  This is always true for a fixed-offset tzinfo
4871        # class (lke utc_real and utc_fake), but not for Eastern or Central.
4872        # For these adjacent DST-aware time zones, the range of time offsets
4873        # tested ends up creating hours in the one that aren't representable
4874        # in the other.  For the same reason, we would see failures in the
4875        # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4876        # offset deltas in convert_between_tz_and_utc().
4877        #
4878        # self.convert_between_tz_and_utc(Eastern, Central)  # can't work
4879        # self.convert_between_tz_and_utc(Central, Eastern)  # can't work
4880
4881    def test_tricky(self):
4882        # 22:00 on day before daylight starts.
4883        fourback = self.dston - timedelta(hours=4)
4884        ninewest = FixedOffset(-9*60, "-0900", 0)
4885        fourback = fourback.replace(tzinfo=ninewest)
4886        # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST.  Since it's "after
4887        # 2", we should get the 3 spelling.
4888        # If we plug 22:00 the day before into Eastern, it "looks like std
4889        # time", so its offset is returned as -5, and -5 - -9 = 4.  Adding 4
4890        # to 22:00 lands on 2:00, which makes no sense in local time (the
4891        # local clock jumps from 1 to 3).  The point here is to make sure we
4892        # get the 3 spelling.
4893        expected = self.dston.replace(hour=3)
4894        got = fourback.astimezone(Eastern).replace(tzinfo=None)
4895        self.assertEqual(expected, got)
4896
4897        # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST.  In that
4898        # case we want the 1:00 spelling.
4899        sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4900        # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4901        # and adding -4-0 == -4 gives the 2:00 spelling.  We want the 1:00 EST
4902        # spelling.
4903        expected = self.dston.replace(hour=1)
4904        got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4905        self.assertEqual(expected, got)
4906
4907        # Now on the day DST ends, we want "repeat an hour" behavior.
4908        #  UTC  4:MM  5:MM  6:MM  7:MM  checking these
4909        #  EST 23:MM  0:MM  1:MM  2:MM
4910        #  EDT  0:MM  1:MM  2:MM  3:MM
4911        # wall  0:MM  1:MM  1:MM  2:MM  against these
4912        for utc in utc_real, utc_fake:
4913            for tz in Eastern, Pacific:
4914                first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4915                # Convert that to UTC.
4916                first_std_hour -= tz.utcoffset(None)
4917                # Adjust for possibly fake UTC.
4918                asutc = first_std_hour + utc.utcoffset(None)
4919                # First UTC hour to convert; this is 4:00 when utc=utc_real &
4920                # tz=Eastern.
4921                asutcbase = asutc.replace(tzinfo=utc)
4922                for tzhour in (0, 1, 1, 2):
4923                    expectedbase = self.dstoff.replace(hour=tzhour)
4924                    for minute in 0, 30, 59:
4925                        expected = expectedbase.replace(minute=minute)
4926                        asutc = asutcbase.replace(minute=minute)
4927                        astz = asutc.astimezone(tz)
4928                        self.assertEqual(astz.replace(tzinfo=None), expected)
4929                    asutcbase += HOUR
4930
4931
4932    def test_bogus_dst(self):
4933        class ok(tzinfo):
4934            def utcoffset(self, dt): return HOUR
4935            def dst(self, dt): return HOUR
4936
4937        now = self.theclass.now().replace(tzinfo=utc_real)
4938        # Doesn't blow up.
4939        now.astimezone(ok())
4940
4941        # Does blow up.
4942        class notok(ok):
4943            def dst(self, dt): return None
4944        self.assertRaises(ValueError, now.astimezone, notok())
4945
4946        # Sometimes blow up. In the following, tzinfo.dst()
4947        # implementation may return None or not None depending on
4948        # whether DST is assumed to be in effect.  In this situation,
4949        # a ValueError should be raised by astimezone().
4950        class tricky_notok(ok):
4951            def dst(self, dt):
4952                if dt.year == 2000:
4953                    return None
4954                else:
4955                    return 10*HOUR
4956        dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4957        self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4958
4959    def test_fromutc(self):
4960        self.assertRaises(TypeError, Eastern.fromutc)   # not enough args
4961        now = datetime.utcnow().replace(tzinfo=utc_real)
4962        self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4963        now = now.replace(tzinfo=Eastern)   # insert correct tzinfo
4964        enow = Eastern.fromutc(now)         # doesn't blow up
4965        self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
4966        self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
4967        self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
4968
4969        # Always converts UTC to standard time.
4970        class FauxUSTimeZone(USTimeZone):
4971            def fromutc(self, dt):
4972                return dt + self.stdoffset
4973        FEastern  = FauxUSTimeZone(-5, "FEastern",  "FEST", "FEDT")
4974
4975        #  UTC  4:MM  5:MM  6:MM  7:MM  8:MM  9:MM
4976        #  EST 23:MM  0:MM  1:MM  2:MM  3:MM  4:MM
4977        #  EDT  0:MM  1:MM  2:MM  3:MM  4:MM  5:MM
4978
4979        # Check around DST start.
4980        start = self.dston.replace(hour=4, tzinfo=Eastern)
4981        fstart = start.replace(tzinfo=FEastern)
4982        for wall in 23, 0, 1, 3, 4, 5:
4983            expected = start.replace(hour=wall)
4984            if wall == 23:
4985                expected -= timedelta(days=1)
4986            got = Eastern.fromutc(start)
4987            self.assertEqual(expected, got)
4988
4989            expected = fstart + FEastern.stdoffset
4990            got = FEastern.fromutc(fstart)
4991            self.assertEqual(expected, got)
4992
4993            # Ensure astimezone() calls fromutc() too.
4994            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
4995            self.assertEqual(expected, got)
4996
4997            start += HOUR
4998            fstart += HOUR
4999
5000        # Check around DST end.
5001        start = self.dstoff.replace(hour=4, tzinfo=Eastern)
5002        fstart = start.replace(tzinfo=FEastern)
5003        for wall in 0, 1, 1, 2, 3, 4:
5004            expected = start.replace(hour=wall)
5005            got = Eastern.fromutc(start)
5006            self.assertEqual(expected, got)
5007
5008            expected = fstart + FEastern.stdoffset
5009            got = FEastern.fromutc(fstart)
5010            self.assertEqual(expected, got)
5011
5012            # Ensure astimezone() calls fromutc() too.
5013            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5014            self.assertEqual(expected, got)
5015
5016            start += HOUR
5017            fstart += HOUR
5018
5019
5020#############################################################################
5021# oddballs
5022
5023class Oddballs(unittest.TestCase):
5024
5025    def test_bug_1028306(self):
5026        # Trying to compare a date to a datetime should act like a mixed-
5027        # type comparison, despite that datetime is a subclass of date.
5028        as_date = date.today()
5029        as_datetime = datetime.combine(as_date, time())
5030        self.assertTrue(as_date != as_datetime)
5031        self.assertTrue(as_datetime != as_date)
5032        self.assertFalse(as_date == as_datetime)
5033        self.assertFalse(as_datetime == as_date)
5034        self.assertRaises(TypeError, lambda: as_date < as_datetime)
5035        self.assertRaises(TypeError, lambda: as_datetime < as_date)
5036        self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5037        self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5038        self.assertRaises(TypeError, lambda: as_date > as_datetime)
5039        self.assertRaises(TypeError, lambda: as_datetime > as_date)
5040        self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5041        self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5042
5043        # Nevertheless, comparison should work with the base-class (date)
5044        # projection if use of a date method is forced.
5045        self.assertEqual(as_date.__eq__(as_datetime), True)
5046        different_day = (as_date.day + 1) % 20 + 1
5047        as_different = as_datetime.replace(day= different_day)
5048        self.assertEqual(as_date.__eq__(as_different), False)
5049
5050        # And date should compare with other subclasses of date.  If a
5051        # subclass wants to stop this, it's up to the subclass to do so.
5052        date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5053        self.assertEqual(as_date, date_sc)
5054        self.assertEqual(date_sc, as_date)
5055
5056        # Ditto for datetimes.
5057        datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5058                                       as_date.day, 0, 0, 0)
5059        self.assertEqual(as_datetime, datetime_sc)
5060        self.assertEqual(datetime_sc, as_datetime)
5061
5062    def test_extra_attributes(self):
5063        for x in [date.today(),
5064                  time(),
5065                  datetime.utcnow(),
5066                  timedelta(),
5067                  tzinfo(),
5068                  timezone(timedelta())]:
5069            with self.assertRaises(AttributeError):
5070                x.abc = 1
5071
5072    def test_check_arg_types(self):
5073        class Number:
5074            def __init__(self, value):
5075                self.value = value
5076            def __int__(self):
5077                return self.value
5078
5079        for xx in [decimal.Decimal(10),
5080                   decimal.Decimal('10.9'),
5081                   Number(10)]:
5082            with self.assertWarns(DeprecationWarning):
5083                self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5084                                 datetime(xx, xx, xx, xx, xx, xx, xx))
5085
5086        with self.assertRaisesRegex(TypeError, '^an integer is required '
5087                                              r'\(got type str\)$'):
5088            datetime(10, 10, '10')
5089
5090        f10 = Number(10.9)
5091        with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
5092                                               r'\(type float\)$'):
5093            datetime(10, 10, f10)
5094
5095        class Float(float):
5096            pass
5097        s10 = Float(10.9)
5098        with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5099                                               'got float$'):
5100            datetime(10, 10, s10)
5101
5102        with self.assertRaises(TypeError):
5103            datetime(10., 10, 10)
5104        with self.assertRaises(TypeError):
5105            datetime(10, 10., 10)
5106        with self.assertRaises(TypeError):
5107            datetime(10, 10, 10.)
5108        with self.assertRaises(TypeError):
5109            datetime(10, 10, 10, 10.)
5110        with self.assertRaises(TypeError):
5111            datetime(10, 10, 10, 10, 10.)
5112        with self.assertRaises(TypeError):
5113            datetime(10, 10, 10, 10, 10, 10.)
5114        with self.assertRaises(TypeError):
5115            datetime(10, 10, 10, 10, 10, 10, 10.)
5116
5117#############################################################################
5118# Local Time Disambiguation
5119
5120# An experimental reimplementation of fromutc that respects the "fold" flag.
5121
5122class tzinfo2(tzinfo):
5123
5124    def fromutc(self, dt):
5125        "datetime in UTC -> datetime in local time."
5126
5127        if not isinstance(dt, datetime):
5128            raise TypeError("fromutc() requires a datetime argument")
5129        if dt.tzinfo is not self:
5130            raise ValueError("dt.tzinfo is not self")
5131        # Returned value satisfies
5132        #          dt + ldt.utcoffset() = ldt
5133        off0 = dt.replace(fold=0).utcoffset()
5134        off1 = dt.replace(fold=1).utcoffset()
5135        if off0 is None or off1 is None or dt.dst() is None:
5136            raise ValueError
5137        if off0 == off1:
5138            ldt = dt + off0
5139            off1 = ldt.utcoffset()
5140            if off0 == off1:
5141                return ldt
5142        # Now, we discovered both possible offsets, so
5143        # we can just try four possible solutions:
5144        for off in [off0, off1]:
5145            ldt = dt + off
5146            if ldt.utcoffset() == off:
5147                return ldt
5148            ldt = ldt.replace(fold=1)
5149            if ldt.utcoffset() == off:
5150                return ldt
5151
5152        raise ValueError("No suitable local time found")
5153
5154# Reimplementing simplified US timezones to respect the "fold" flag:
5155
5156class USTimeZone2(tzinfo2):
5157
5158    def __init__(self, hours, reprname, stdname, dstname):
5159        self.stdoffset = timedelta(hours=hours)
5160        self.reprname = reprname
5161        self.stdname = stdname
5162        self.dstname = dstname
5163
5164    def __repr__(self):
5165        return self.reprname
5166
5167    def tzname(self, dt):
5168        if self.dst(dt):
5169            return self.dstname
5170        else:
5171            return self.stdname
5172
5173    def utcoffset(self, dt):
5174        return self.stdoffset + self.dst(dt)
5175
5176    def dst(self, dt):
5177        if dt is None or dt.tzinfo is None:
5178            # An exception instead may be sensible here, in one or more of
5179            # the cases.
5180            return ZERO
5181        assert dt.tzinfo is self
5182
5183        # Find first Sunday in April.
5184        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5185        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5186
5187        # Find last Sunday in October.
5188        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5189        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5190
5191        # Can't compare naive to aware objects, so strip the timezone from
5192        # dt first.
5193        dt = dt.replace(tzinfo=None)
5194        if start + HOUR <= dt < end:
5195            # DST is in effect.
5196            return HOUR
5197        elif end <= dt < end + HOUR:
5198            # Fold (an ambiguous hour): use dt.fold to disambiguate.
5199            return ZERO if dt.fold else HOUR
5200        elif start <= dt < start + HOUR:
5201            # Gap (a non-existent hour): reverse the fold rule.
5202            return HOUR if dt.fold else ZERO
5203        else:
5204            # DST is off.
5205            return ZERO
5206
5207Eastern2  = USTimeZone2(-5, "Eastern2",  "EST", "EDT")
5208Central2  = USTimeZone2(-6, "Central2",  "CST", "CDT")
5209Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5210Pacific2  = USTimeZone2(-8, "Pacific2",  "PST", "PDT")
5211
5212# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5213# 1941 transition from Olson's tzdist:
5214#
5215# Zone NAME           GMTOFF RULES  FORMAT [UNTIL]
5216# ZoneEurope/Vilnius  1:00   -      CET    1940 Aug  3
5217#                     3:00   -      MSK    1941 Jun 24
5218#                     1:00   C-Eur  CE%sT  1944 Aug
5219#
5220# $ zdump -v Europe/Vilnius | grep 1941
5221# Europe/Vilnius  Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5222# Europe/Vilnius  Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5223
5224class Europe_Vilnius_1941(tzinfo):
5225    def _utc_fold(self):
5226        return [datetime(1941, 6, 23, 21, tzinfo=self),  # Mon Jun 23 21:00:00 1941 UTC
5227                datetime(1941, 6, 23, 22, tzinfo=self)]  # Mon Jun 23 22:00:00 1941 UTC
5228
5229    def _loc_fold(self):
5230        return [datetime(1941, 6, 23, 23, tzinfo=self),  # Mon Jun 23 23:00:00 1941 MSK / CEST
5231                datetime(1941, 6, 24, 0, tzinfo=self)]   # Mon Jun 24 00:00:00 1941 CEST
5232
5233    def utcoffset(self, dt):
5234        fold_start, fold_stop = self._loc_fold()
5235        if dt < fold_start:
5236            return 3 * HOUR
5237        if dt < fold_stop:
5238            return (2 if dt.fold else 3) * HOUR
5239        # if dt >= fold_stop
5240        return 2 * HOUR
5241
5242    def dst(self, dt):
5243        fold_start, fold_stop = self._loc_fold()
5244        if dt < fold_start:
5245            return 0 * HOUR
5246        if dt < fold_stop:
5247            return (1 if dt.fold else 0) * HOUR
5248        # if dt >= fold_stop
5249        return 1 * HOUR
5250
5251    def tzname(self, dt):
5252        fold_start, fold_stop = self._loc_fold()
5253        if dt < fold_start:
5254            return 'MSK'
5255        if dt < fold_stop:
5256            return ('MSK', 'CEST')[dt.fold]
5257        # if dt >= fold_stop
5258        return 'CEST'
5259
5260    def fromutc(self, dt):
5261        assert dt.fold == 0
5262        assert dt.tzinfo is self
5263        if dt.year != 1941:
5264            raise NotImplementedError
5265        fold_start, fold_stop = self._utc_fold()
5266        if dt < fold_start:
5267            return dt + 3 * HOUR
5268        if dt < fold_stop:
5269            return (dt + 2 * HOUR).replace(fold=1)
5270        # if dt >= fold_stop
5271        return dt + 2 * HOUR
5272
5273
5274class TestLocalTimeDisambiguation(unittest.TestCase):
5275
5276    def test_vilnius_1941_fromutc(self):
5277        Vilnius = Europe_Vilnius_1941()
5278
5279        gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5280        ldt = gdt.astimezone(Vilnius)
5281        self.assertEqual(ldt.strftime("%c %Z%z"),
5282                         'Mon Jun 23 23:59:59 1941 MSK+0300')
5283        self.assertEqual(ldt.fold, 0)
5284        self.assertFalse(ldt.dst())
5285
5286        gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5287        ldt = gdt.astimezone(Vilnius)
5288        self.assertEqual(ldt.strftime("%c %Z%z"),
5289                         'Mon Jun 23 23:00:00 1941 CEST+0200')
5290        self.assertEqual(ldt.fold, 1)
5291        self.assertTrue(ldt.dst())
5292
5293        gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5294        ldt = gdt.astimezone(Vilnius)
5295        self.assertEqual(ldt.strftime("%c %Z%z"),
5296                         'Tue Jun 24 00:00:00 1941 CEST+0200')
5297        self.assertEqual(ldt.fold, 0)
5298        self.assertTrue(ldt.dst())
5299
5300    def test_vilnius_1941_toutc(self):
5301        Vilnius = Europe_Vilnius_1941()
5302
5303        ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5304        gdt = ldt.astimezone(timezone.utc)
5305        self.assertEqual(gdt.strftime("%c %Z"),
5306                         'Mon Jun 23 19:59:59 1941 UTC')
5307
5308        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5309        gdt = ldt.astimezone(timezone.utc)
5310        self.assertEqual(gdt.strftime("%c %Z"),
5311                         'Mon Jun 23 20:59:59 1941 UTC')
5312
5313        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5314        gdt = ldt.astimezone(timezone.utc)
5315        self.assertEqual(gdt.strftime("%c %Z"),
5316                         'Mon Jun 23 21:59:59 1941 UTC')
5317
5318        ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5319        gdt = ldt.astimezone(timezone.utc)
5320        self.assertEqual(gdt.strftime("%c %Z"),
5321                         'Mon Jun 23 22:00:00 1941 UTC')
5322
5323    def test_constructors(self):
5324        t = time(0, fold=1)
5325        dt = datetime(1, 1, 1, fold=1)
5326        self.assertEqual(t.fold, 1)
5327        self.assertEqual(dt.fold, 1)
5328        with self.assertRaises(TypeError):
5329            time(0, 0, 0, 0, None, 0)
5330
5331    def test_member(self):
5332        dt = datetime(1, 1, 1, fold=1)
5333        t = dt.time()
5334        self.assertEqual(t.fold, 1)
5335        t = dt.timetz()
5336        self.assertEqual(t.fold, 1)
5337
5338    def test_replace(self):
5339        t = time(0)
5340        dt = datetime(1, 1, 1)
5341        self.assertEqual(t.replace(fold=1).fold, 1)
5342        self.assertEqual(dt.replace(fold=1).fold, 1)
5343        self.assertEqual(t.replace(fold=0).fold, 0)
5344        self.assertEqual(dt.replace(fold=0).fold, 0)
5345        # Check that replacement of other fields does not change "fold".
5346        t = t.replace(fold=1, tzinfo=Eastern)
5347        dt = dt.replace(fold=1, tzinfo=Eastern)
5348        self.assertEqual(t.replace(tzinfo=None).fold, 1)
5349        self.assertEqual(dt.replace(tzinfo=None).fold, 1)
5350        # Out of bounds.
5351        with self.assertRaises(ValueError):
5352            t.replace(fold=2)
5353        with self.assertRaises(ValueError):
5354            dt.replace(fold=2)
5355        # Check that fold is a keyword-only argument
5356        with self.assertRaises(TypeError):
5357            t.replace(1, 1, 1, None, 1)
5358        with self.assertRaises(TypeError):
5359            dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
5360
5361    def test_comparison(self):
5362        t = time(0)
5363        dt = datetime(1, 1, 1)
5364        self.assertEqual(t, t.replace(fold=1))
5365        self.assertEqual(dt, dt.replace(fold=1))
5366
5367    def test_hash(self):
5368        t = time(0)
5369        dt = datetime(1, 1, 1)
5370        self.assertEqual(hash(t), hash(t.replace(fold=1)))
5371        self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5372
5373    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5374    def test_fromtimestamp(self):
5375        s = 1414906200
5376        dt0 = datetime.fromtimestamp(s)
5377        dt1 = datetime.fromtimestamp(s + 3600)
5378        self.assertEqual(dt0.fold, 0)
5379        self.assertEqual(dt1.fold, 1)
5380
5381    @support.run_with_tz('Australia/Lord_Howe')
5382    def test_fromtimestamp_lord_howe(self):
5383        tm = _time.localtime(1.4e9)
5384        if _time.strftime('%Z%z', tm) != 'LHST+1030':
5385            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5386        # $ TZ=Australia/Lord_Howe date -r 1428158700
5387        # Sun Apr  5 01:45:00 LHDT 2015
5388        # $ TZ=Australia/Lord_Howe date -r 1428160500
5389        # Sun Apr  5 01:45:00 LHST 2015
5390        s = 1428158700
5391        t0 = datetime.fromtimestamp(s)
5392        t1 = datetime.fromtimestamp(s + 1800)
5393        self.assertEqual(t0, t1)
5394        self.assertEqual(t0.fold, 0)
5395        self.assertEqual(t1.fold, 1)
5396
5397    def test_fromtimestamp_low_fold_detection(self):
5398        # Ensure that fold detection doesn't cause an
5399        # OSError for really low values, see bpo-29097
5400        self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5401
5402    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5403    def test_timestamp(self):
5404        dt0 = datetime(2014, 11, 2, 1, 30)
5405        dt1 = dt0.replace(fold=1)
5406        self.assertEqual(dt0.timestamp() + 3600,
5407                         dt1.timestamp())
5408
5409    @support.run_with_tz('Australia/Lord_Howe')
5410    def test_timestamp_lord_howe(self):
5411        tm = _time.localtime(1.4e9)
5412        if _time.strftime('%Z%z', tm) != 'LHST+1030':
5413            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5414        t = datetime(2015, 4, 5, 1, 45)
5415        s0 = t.replace(fold=0).timestamp()
5416        s1 = t.replace(fold=1).timestamp()
5417        self.assertEqual(s0 + 1800, s1)
5418
5419    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5420    def test_astimezone(self):
5421        dt0 = datetime(2014, 11, 2, 1, 30)
5422        dt1 = dt0.replace(fold=1)
5423        # Convert both naive instances to aware.
5424        adt0 = dt0.astimezone()
5425        adt1 = dt1.astimezone()
5426        # Check that the first instance in DST zone and the second in STD
5427        self.assertEqual(adt0.tzname(), 'EDT')
5428        self.assertEqual(adt1.tzname(), 'EST')
5429        self.assertEqual(adt0 + HOUR, adt1)
5430        # Aware instances with fixed offset tzinfo's always have fold=0
5431        self.assertEqual(adt0.fold, 0)
5432        self.assertEqual(adt1.fold, 0)
5433
5434    def test_pickle_fold(self):
5435        t = time(fold=1)
5436        dt = datetime(1, 1, 1, fold=1)
5437        for pickler, unpickler, proto in pickle_choices:
5438            for x in [t, dt]:
5439                s = pickler.dumps(x, proto)
5440                y = unpickler.loads(s)
5441                self.assertEqual(x, y)
5442                self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5443
5444    def test_repr(self):
5445        t = time(fold=1)
5446        dt = datetime(1, 1, 1, fold=1)
5447        self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5448        self.assertEqual(repr(dt),
5449                         'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5450
5451    def test_dst(self):
5452        # Let's first establish that things work in regular times.
5453        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5454        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5455        self.assertEqual(dt_summer.dst(), HOUR)
5456        self.assertEqual(dt_winter.dst(), ZERO)
5457        # The disambiguation flag is ignored
5458        self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5459        self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5460
5461        # Pick local time in the fold.
5462        for minute in [0, 30, 59]:
5463            dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5464            # With fold=0 (the default) it is in DST.
5465            self.assertEqual(dt.dst(), HOUR)
5466            # With fold=1 it is in STD.
5467            self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5468
5469        # Pick local time in the gap.
5470        for minute in [0, 30, 59]:
5471            dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5472            # With fold=0 (the default) it is in STD.
5473            self.assertEqual(dt.dst(), ZERO)
5474            # With fold=1 it is in DST.
5475            self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5476
5477
5478    def test_utcoffset(self):
5479        # Let's first establish that things work in regular times.
5480        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5481        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5482        self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5483        self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5484        # The disambiguation flag is ignored
5485        self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5486        self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5487
5488    def test_fromutc(self):
5489        # Let's first establish that things work in regular times.
5490        u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5491        u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5492        t_summer = Eastern2.fromutc(u_summer)
5493        t_winter = Eastern2.fromutc(u_winter)
5494        self.assertEqual(t_summer, u_summer - 4 * HOUR)
5495        self.assertEqual(t_winter, u_winter - 5 * HOUR)
5496        self.assertEqual(t_summer.fold, 0)
5497        self.assertEqual(t_winter.fold, 0)
5498
5499        # What happens in the fall-back fold?
5500        u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5501        t0 = Eastern2.fromutc(u)
5502        u += HOUR
5503        t1 = Eastern2.fromutc(u)
5504        self.assertEqual(t0, t1)
5505        self.assertEqual(t0.fold, 0)
5506        self.assertEqual(t1.fold, 1)
5507        # The tricky part is when u is in the local fold:
5508        u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5509        t = Eastern2.fromutc(u)
5510        self.assertEqual((t.day, t.hour), (26, 21))
5511        # .. or gets into the local fold after a standard time adjustment
5512        u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5513        t = Eastern2.fromutc(u)
5514        self.assertEqual((t.day, t.hour), (27, 1))
5515
5516        # What happens in the spring-forward gap?
5517        u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5518        t = Eastern2.fromutc(u)
5519        self.assertEqual((t.day, t.hour), (6, 21))
5520
5521    def test_mixed_compare_regular(self):
5522        t = datetime(2000, 1, 1, tzinfo=Eastern2)
5523        self.assertEqual(t, t.astimezone(timezone.utc))
5524        t = datetime(2000, 6, 1, tzinfo=Eastern2)
5525        self.assertEqual(t, t.astimezone(timezone.utc))
5526
5527    def test_mixed_compare_fold(self):
5528        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5529        t_fold_utc = t_fold.astimezone(timezone.utc)
5530        self.assertNotEqual(t_fold, t_fold_utc)
5531        self.assertNotEqual(t_fold_utc, t_fold)
5532
5533    def test_mixed_compare_gap(self):
5534        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5535        t_gap_utc = t_gap.astimezone(timezone.utc)
5536        self.assertNotEqual(t_gap, t_gap_utc)
5537        self.assertNotEqual(t_gap_utc, t_gap)
5538
5539    def test_hash_aware(self):
5540        t = datetime(2000, 1, 1, tzinfo=Eastern2)
5541        self.assertEqual(hash(t), hash(t.replace(fold=1)))
5542        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5543        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5544        self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5545        self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5546
5547SEC = timedelta(0, 1)
5548
5549def pairs(iterable):
5550    a, b = itertools.tee(iterable)
5551    next(b, None)
5552    return zip(a, b)
5553
5554class ZoneInfo(tzinfo):
5555    zoneroot = '/usr/share/zoneinfo'
5556    def __init__(self, ut, ti):
5557        """
5558
5559        :param ut: array
5560            Array of transition point timestamps
5561        :param ti: list
5562            A list of (offset, isdst, abbr) tuples
5563        :return: None
5564        """
5565        self.ut = ut
5566        self.ti = ti
5567        self.lt = self.invert(ut, ti)
5568
5569    @staticmethod
5570    def invert(ut, ti):
5571        lt = (array('q', ut), array('q', ut))
5572        if ut:
5573            offset = ti[0][0] // SEC
5574            lt[0][0] += offset
5575            lt[1][0] += offset
5576            for i in range(1, len(ut)):
5577                lt[0][i] += ti[i-1][0] // SEC
5578                lt[1][i] += ti[i][0] // SEC
5579        return lt
5580
5581    @classmethod
5582    def fromfile(cls, fileobj):
5583        if fileobj.read(4).decode() != "TZif":
5584            raise ValueError("not a zoneinfo file")
5585        fileobj.seek(32)
5586        counts = array('i')
5587        counts.fromfile(fileobj, 3)
5588        if sys.byteorder != 'big':
5589            counts.byteswap()
5590
5591        ut = array('i')
5592        ut.fromfile(fileobj, counts[0])
5593        if sys.byteorder != 'big':
5594            ut.byteswap()
5595
5596        type_indices = array('B')
5597        type_indices.fromfile(fileobj, counts[0])
5598
5599        ttis = []
5600        for i in range(counts[1]):
5601            ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5602
5603        abbrs = fileobj.read(counts[2])
5604
5605        # Convert ttis
5606        for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5607            abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5608            ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5609
5610        ti = [None] * len(ut)
5611        for i, idx in enumerate(type_indices):
5612            ti[i] = ttis[idx]
5613
5614        self = cls(ut, ti)
5615
5616        return self
5617
5618    @classmethod
5619    def fromname(cls, name):
5620        path = os.path.join(cls.zoneroot, name)
5621        with open(path, 'rb') as f:
5622            return cls.fromfile(f)
5623
5624    EPOCHORDINAL = date(1970, 1, 1).toordinal()
5625
5626    def fromutc(self, dt):
5627        """datetime in UTC -> datetime in local time."""
5628
5629        if not isinstance(dt, datetime):
5630            raise TypeError("fromutc() requires a datetime argument")
5631        if dt.tzinfo is not self:
5632            raise ValueError("dt.tzinfo is not self")
5633
5634        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5635                     + dt.hour * 3600
5636                     + dt.minute * 60
5637                     + dt.second)
5638
5639        if timestamp < self.ut[1]:
5640            tti = self.ti[0]
5641            fold = 0
5642        else:
5643            idx = bisect.bisect_right(self.ut, timestamp)
5644            assert self.ut[idx-1] <= timestamp
5645            assert idx == len(self.ut) or timestamp < self.ut[idx]
5646            tti_prev, tti = self.ti[idx-2:idx]
5647            # Detect fold
5648            shift = tti_prev[0] - tti[0]
5649            fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5650        dt += tti[0]
5651        if fold:
5652            return dt.replace(fold=1)
5653        else:
5654            return dt
5655
5656    def _find_ti(self, dt, i):
5657        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5658             + dt.hour * 3600
5659             + dt.minute * 60
5660             + dt.second)
5661        lt = self.lt[dt.fold]
5662        idx = bisect.bisect_right(lt, timestamp)
5663
5664        return self.ti[max(0, idx - 1)][i]
5665
5666    def utcoffset(self, dt):
5667        return self._find_ti(dt, 0)
5668
5669    def dst(self, dt):
5670        isdst = self._find_ti(dt, 1)
5671        # XXX: We cannot accurately determine the "save" value,
5672        # so let's return 1h whenever DST is in effect.  Since
5673        # we don't use dst() in fromutc(), it is unlikely that
5674        # it will be needed for anything more than bool(dst()).
5675        return ZERO if isdst else HOUR
5676
5677    def tzname(self, dt):
5678        return self._find_ti(dt, 2)
5679
5680    @classmethod
5681    def zonenames(cls, zonedir=None):
5682        if zonedir is None:
5683            zonedir = cls.zoneroot
5684        zone_tab = os.path.join(zonedir, 'zone.tab')
5685        try:
5686            f = open(zone_tab)
5687        except OSError:
5688            return
5689        with f:
5690            for line in f:
5691                line = line.strip()
5692                if line and not line.startswith('#'):
5693                    yield line.split()[2]
5694
5695    @classmethod
5696    def stats(cls, start_year=1):
5697        count = gap_count = fold_count = zeros_count = 0
5698        min_gap = min_fold = timedelta.max
5699        max_gap = max_fold = ZERO
5700        min_gap_datetime = max_gap_datetime = datetime.min
5701        min_gap_zone = max_gap_zone = None
5702        min_fold_datetime = max_fold_datetime = datetime.min
5703        min_fold_zone = max_fold_zone = None
5704        stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5705        for zonename in cls.zonenames():
5706            count += 1
5707            tz = cls.fromname(zonename)
5708            for dt, shift in tz.transitions():
5709                if dt < stats_since:
5710                    continue
5711                if shift > ZERO:
5712                    gap_count += 1
5713                    if (shift, dt) > (max_gap, max_gap_datetime):
5714                        max_gap = shift
5715                        max_gap_zone = zonename
5716                        max_gap_datetime = dt
5717                    if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5718                        min_gap = shift
5719                        min_gap_zone = zonename
5720                        min_gap_datetime = dt
5721                elif shift < ZERO:
5722                    fold_count += 1
5723                    shift = -shift
5724                    if (shift, dt) > (max_fold, max_fold_datetime):
5725                        max_fold = shift
5726                        max_fold_zone = zonename
5727                        max_fold_datetime = dt
5728                    if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5729                        min_fold = shift
5730                        min_fold_zone = zonename
5731                        min_fold_datetime = dt
5732                else:
5733                    zeros_count += 1
5734        trans_counts = (gap_count, fold_count, zeros_count)
5735        print("Number of zones:       %5d" % count)
5736        print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5737              ((sum(trans_counts),) + trans_counts))
5738        print("Min gap:         %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5739        print("Max gap:         %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5740        print("Min fold:        %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5741        print("Max fold:        %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5742
5743
5744    def transitions(self):
5745        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5746            shift = ti[0] - prev_ti[0]
5747            yield datetime.utcfromtimestamp(t), shift
5748
5749    def nondst_folds(self):
5750        """Find all folds with the same value of isdst on both sides of the transition."""
5751        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5752            shift = ti[0] - prev_ti[0]
5753            if shift < ZERO and ti[1] == prev_ti[1]:
5754                yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5755
5756    @classmethod
5757    def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5758        count = 0
5759        for zonename in cls.zonenames():
5760            tz = cls.fromname(zonename)
5761            for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5762                if dt.year < start_year or same_abbr and prev_abbr != abbr:
5763                    continue
5764                count += 1
5765                print("%3d) %-30s %s %10s %5s -> %s" %
5766                      (count, zonename, dt, shift, prev_abbr, abbr))
5767
5768    def folds(self):
5769        for t, shift in self.transitions():
5770            if shift < ZERO:
5771                yield t, -shift
5772
5773    def gaps(self):
5774        for t, shift in self.transitions():
5775            if shift > ZERO:
5776                yield t, shift
5777
5778    def zeros(self):
5779        for t, shift in self.transitions():
5780            if not shift:
5781                yield t
5782
5783
5784class ZoneInfoTest(unittest.TestCase):
5785    zonename = 'America/New_York'
5786
5787    def setUp(self):
5788        if sys.platform == "win32":
5789            self.skipTest("Skipping zoneinfo tests on Windows")
5790        try:
5791            self.tz = ZoneInfo.fromname(self.zonename)
5792        except FileNotFoundError as err:
5793            self.skipTest("Skipping %s: %s" % (self.zonename, err))
5794
5795    def assertEquivDatetimes(self, a, b):
5796        self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5797                         (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5798
5799    def test_folds(self):
5800        tz = self.tz
5801        for dt, shift in tz.folds():
5802            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5803                udt = dt + x
5804                ldt = tz.fromutc(udt.replace(tzinfo=tz))
5805                self.assertEqual(ldt.fold, 1)
5806                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5807                self.assertEquivDatetimes(adt, ldt)
5808                utcoffset = ldt.utcoffset()
5809                self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5810                # Round trip
5811                self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5812                                          udt.replace(tzinfo=timezone.utc))
5813
5814
5815            for x in [-timedelta.resolution, shift]:
5816                udt = dt + x
5817                udt = udt.replace(tzinfo=tz)
5818                ldt = tz.fromutc(udt)
5819                self.assertEqual(ldt.fold, 0)
5820
5821    def test_gaps(self):
5822        tz = self.tz
5823        for dt, shift in tz.gaps():
5824            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5825                udt = dt + x
5826                udt = udt.replace(tzinfo=tz)
5827                ldt = tz.fromutc(udt)
5828                self.assertEqual(ldt.fold, 0)
5829                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5830                self.assertEquivDatetimes(adt, ldt)
5831                utcoffset = ldt.utcoffset()
5832                self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5833                # Create a local time inside the gap
5834                ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5835                self.assertLess(ldt.replace(fold=1).utcoffset(),
5836                                ldt.replace(fold=0).utcoffset(),
5837                                "At %s." % ldt)
5838
5839            for x in [-timedelta.resolution, shift]:
5840                udt = dt + x
5841                ldt = tz.fromutc(udt.replace(tzinfo=tz))
5842                self.assertEqual(ldt.fold, 0)
5843
5844    def test_system_transitions(self):
5845        if ('Riyadh8' in self.zonename or
5846            # From tzdata NEWS file:
5847            # The files solar87, solar88, and solar89 are no longer distributed.
5848            # They were a negative experiment - that is, a demonstration that
5849            # tz data can represent solar time only with some difficulty and error.
5850            # Their presence in the distribution caused confusion, as Riyadh
5851            # civil time was generally not solar time in those years.
5852                self.zonename.startswith('right/')):
5853            self.skipTest("Skipping %s" % self.zonename)
5854        tz = self.tz
5855        TZ = os.environ.get('TZ')
5856        os.environ['TZ'] = self.zonename
5857        try:
5858            _time.tzset()
5859            for udt, shift in tz.transitions():
5860                if udt.year >= 2037:
5861                    # System support for times around the end of 32-bit time_t
5862                    # and later is flaky on many systems.
5863                    break
5864                s0 = (udt - datetime(1970, 1, 1)) // SEC
5865                ss = shift // SEC   # shift seconds
5866                for x in [-40 * 3600, -20*3600, -1, 0,
5867                          ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5868                    s = s0 + x
5869                    sdt = datetime.fromtimestamp(s)
5870                    tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5871                    self.assertEquivDatetimes(sdt, tzdt)
5872                    s1 = sdt.timestamp()
5873                    self.assertEqual(s, s1)
5874                if ss > 0:  # gap
5875                    # Create local time inside the gap
5876                    dt = datetime.fromtimestamp(s0) - shift / 2
5877                    ts0 = dt.timestamp()
5878                    ts1 = dt.replace(fold=1).timestamp()
5879                    self.assertEqual(ts0, s0 + ss / 2)
5880                    self.assertEqual(ts1, s0 - ss / 2)
5881        finally:
5882            if TZ is None:
5883                del os.environ['TZ']
5884            else:
5885                os.environ['TZ'] = TZ
5886            _time.tzset()
5887
5888
5889class ZoneInfoCompleteTest(unittest.TestSuite):
5890    def __init__(self):
5891        tests = []
5892        if is_resource_enabled('tzdata'):
5893            for name in ZoneInfo.zonenames():
5894                Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5895                Test.zonename = name
5896                for method in dir(Test):
5897                    if method.startswith('test_'):
5898                        tests.append(Test(method))
5899        super().__init__(tests)
5900
5901# Iran had a sub-minute UTC offset before 1946.
5902class IranTest(ZoneInfoTest):
5903    zonename = 'Asia/Tehran'
5904
5905
5906class CapiTest(unittest.TestCase):
5907    def setUp(self):
5908        # Since the C API is not present in the _Pure tests, skip all tests
5909        if self.__class__.__name__.endswith('Pure'):
5910            self.skipTest('Not relevant in pure Python')
5911
5912        # This *must* be called, and it must be called first, so until either
5913        # restriction is loosened, we'll call it as part of test setup
5914        _testcapi.test_datetime_capi()
5915
5916    def test_utc_capi(self):
5917        for use_macro in (True, False):
5918            capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5919
5920            with self.subTest(use_macro=use_macro):
5921                self.assertIs(capi_utc, timezone.utc)
5922
5923    def test_timezones_capi(self):
5924        est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5925
5926        exp_named = timezone(timedelta(hours=-5), "EST")
5927        exp_unnamed = timezone(timedelta(hours=-5))
5928
5929        cases = [
5930            ('est_capi', est_capi, exp_named),
5931            ('est_macro', est_macro, exp_named),
5932            ('est_macro_nn', est_macro_nn, exp_unnamed)
5933        ]
5934
5935        for name, tz_act, tz_exp in cases:
5936            with self.subTest(name=name):
5937                self.assertEqual(tz_act, tz_exp)
5938
5939                dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5940                dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5941
5942                self.assertEqual(dt1, dt2)
5943                self.assertEqual(dt1.tzname(), dt2.tzname())
5944
5945                dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5946
5947                self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5948
5949    def test_timezones_offset_zero(self):
5950        utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
5951
5952        with self.subTest(testname="utc0"):
5953            self.assertIs(utc0, timezone.utc)
5954
5955        with self.subTest(testname="utc1"):
5956            self.assertIs(utc1, timezone.utc)
5957
5958        with self.subTest(testname="non_utc"):
5959            self.assertIsNot(non_utc, timezone.utc)
5960
5961            non_utc_exp = timezone(timedelta(hours=0), "")
5962
5963            self.assertEqual(non_utc, non_utc_exp)
5964
5965            dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
5966            dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
5967
5968            self.assertEqual(dt1, dt2)
5969            self.assertEqual(dt1.tzname(), dt2.tzname())
5970
5971    def test_check_date(self):
5972        class DateSubclass(date):
5973            pass
5974
5975        d = date(2011, 1, 1)
5976        ds = DateSubclass(2011, 1, 1)
5977        dt = datetime(2011, 1, 1)
5978
5979        is_date = _testcapi.datetime_check_date
5980
5981        # Check the ones that should be valid
5982        self.assertTrue(is_date(d))
5983        self.assertTrue(is_date(dt))
5984        self.assertTrue(is_date(ds))
5985        self.assertTrue(is_date(d, True))
5986
5987        # Check that the subclasses do not match exactly
5988        self.assertFalse(is_date(dt, True))
5989        self.assertFalse(is_date(ds, True))
5990
5991        # Check that various other things are not dates at all
5992        args = [tuple(), list(), 1, '2011-01-01',
5993                timedelta(1), timezone.utc, time(12, 00)]
5994        for arg in args:
5995            for exact in (True, False):
5996                with self.subTest(arg=arg, exact=exact):
5997                    self.assertFalse(is_date(arg, exact))
5998
5999    def test_check_time(self):
6000        class TimeSubclass(time):
6001            pass
6002
6003        t = time(12, 30)
6004        ts = TimeSubclass(12, 30)
6005
6006        is_time = _testcapi.datetime_check_time
6007
6008        # Check the ones that should be valid
6009        self.assertTrue(is_time(t))
6010        self.assertTrue(is_time(ts))
6011        self.assertTrue(is_time(t, True))
6012
6013        # Check that the subclass does not match exactly
6014        self.assertFalse(is_time(ts, True))
6015
6016        # Check that various other things are not times
6017        args = [tuple(), list(), 1, '2011-01-01',
6018                timedelta(1), timezone.utc, date(2011, 1, 1)]
6019
6020        for arg in args:
6021            for exact in (True, False):
6022                with self.subTest(arg=arg, exact=exact):
6023                    self.assertFalse(is_time(arg, exact))
6024
6025    def test_check_datetime(self):
6026        class DateTimeSubclass(datetime):
6027            pass
6028
6029        dt = datetime(2011, 1, 1, 12, 30)
6030        dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6031
6032        is_datetime = _testcapi.datetime_check_datetime
6033
6034        # Check the ones that should be valid
6035        self.assertTrue(is_datetime(dt))
6036        self.assertTrue(is_datetime(dts))
6037        self.assertTrue(is_datetime(dt, True))
6038
6039        # Check that the subclass does not match exactly
6040        self.assertFalse(is_datetime(dts, True))
6041
6042        # Check that various other things are not datetimes
6043        args = [tuple(), list(), 1, '2011-01-01',
6044                timedelta(1), timezone.utc, date(2011, 1, 1)]
6045
6046        for arg in args:
6047            for exact in (True, False):
6048                with self.subTest(arg=arg, exact=exact):
6049                    self.assertFalse(is_datetime(arg, exact))
6050
6051    def test_check_delta(self):
6052        class TimeDeltaSubclass(timedelta):
6053            pass
6054
6055        td = timedelta(1)
6056        tds = TimeDeltaSubclass(1)
6057
6058        is_timedelta = _testcapi.datetime_check_delta
6059
6060        # Check the ones that should be valid
6061        self.assertTrue(is_timedelta(td))
6062        self.assertTrue(is_timedelta(tds))
6063        self.assertTrue(is_timedelta(td, True))
6064
6065        # Check that the subclass does not match exactly
6066        self.assertFalse(is_timedelta(tds, True))
6067
6068        # Check that various other things are not timedeltas
6069        args = [tuple(), list(), 1, '2011-01-01',
6070                timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6071
6072        for arg in args:
6073            for exact in (True, False):
6074                with self.subTest(arg=arg, exact=exact):
6075                    self.assertFalse(is_timedelta(arg, exact))
6076
6077    def test_check_tzinfo(self):
6078        class TZInfoSubclass(tzinfo):
6079            pass
6080
6081        tzi = tzinfo()
6082        tzis = TZInfoSubclass()
6083        tz = timezone(timedelta(hours=-5))
6084
6085        is_tzinfo = _testcapi.datetime_check_tzinfo
6086
6087        # Check the ones that should be valid
6088        self.assertTrue(is_tzinfo(tzi))
6089        self.assertTrue(is_tzinfo(tz))
6090        self.assertTrue(is_tzinfo(tzis))
6091        self.assertTrue(is_tzinfo(tzi, True))
6092
6093        # Check that the subclasses do not match exactly
6094        self.assertFalse(is_tzinfo(tz, True))
6095        self.assertFalse(is_tzinfo(tzis, True))
6096
6097        # Check that various other things are not tzinfos
6098        args = [tuple(), list(), 1, '2011-01-01',
6099                date(2011, 1, 1), datetime(2011, 1, 1)]
6100
6101        for arg in args:
6102            for exact in (True, False):
6103                with self.subTest(arg=arg, exact=exact):
6104                    self.assertFalse(is_tzinfo(arg, exact))
6105
6106    def test_date_from_date(self):
6107        exp_date = date(1993, 8, 26)
6108
6109        for macro in [0, 1]:
6110            with self.subTest(macro=macro):
6111                c_api_date = _testcapi.get_date_fromdate(
6112                    macro,
6113                    exp_date.year,
6114                    exp_date.month,
6115                    exp_date.day)
6116
6117                self.assertEqual(c_api_date, exp_date)
6118
6119    def test_datetime_from_dateandtime(self):
6120        exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6121
6122        for macro in [0, 1]:
6123            with self.subTest(macro=macro):
6124                c_api_date = _testcapi.get_datetime_fromdateandtime(
6125                    macro,
6126                    exp_date.year,
6127                    exp_date.month,
6128                    exp_date.day,
6129                    exp_date.hour,
6130                    exp_date.minute,
6131                    exp_date.second,
6132                    exp_date.microsecond)
6133
6134                self.assertEqual(c_api_date, exp_date)
6135
6136    def test_datetime_from_dateandtimeandfold(self):
6137        exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6138
6139        for fold in [0, 1]:
6140            for macro in [0, 1]:
6141                with self.subTest(macro=macro, fold=fold):
6142                    c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6143                        macro,
6144                        exp_date.year,
6145                        exp_date.month,
6146                        exp_date.day,
6147                        exp_date.hour,
6148                        exp_date.minute,
6149                        exp_date.second,
6150                        exp_date.microsecond,
6151                        exp_date.fold)
6152
6153                    self.assertEqual(c_api_date, exp_date)
6154                    self.assertEqual(c_api_date.fold, exp_date.fold)
6155
6156    def test_time_from_time(self):
6157        exp_time = time(22, 12, 55, 99999)
6158
6159        for macro in [0, 1]:
6160            with self.subTest(macro=macro):
6161                c_api_time = _testcapi.get_time_fromtime(
6162                    macro,
6163                    exp_time.hour,
6164                    exp_time.minute,
6165                    exp_time.second,
6166                    exp_time.microsecond)
6167
6168                self.assertEqual(c_api_time, exp_time)
6169
6170    def test_time_from_timeandfold(self):
6171        exp_time = time(22, 12, 55, 99999)
6172
6173        for fold in [0, 1]:
6174            for macro in [0, 1]:
6175                with self.subTest(macro=macro, fold=fold):
6176                    c_api_time = _testcapi.get_time_fromtimeandfold(
6177                        macro,
6178                        exp_time.hour,
6179                        exp_time.minute,
6180                        exp_time.second,
6181                        exp_time.microsecond,
6182                        exp_time.fold)
6183
6184                    self.assertEqual(c_api_time, exp_time)
6185                    self.assertEqual(c_api_time.fold, exp_time.fold)
6186
6187    def test_delta_from_dsu(self):
6188        exp_delta = timedelta(26, 55, 99999)
6189
6190        for macro in [0, 1]:
6191            with self.subTest(macro=macro):
6192                c_api_delta = _testcapi.get_delta_fromdsu(
6193                    macro,
6194                    exp_delta.days,
6195                    exp_delta.seconds,
6196                    exp_delta.microseconds)
6197
6198                self.assertEqual(c_api_delta, exp_delta)
6199
6200    def test_date_from_timestamp(self):
6201        ts = datetime(1995, 4, 12).timestamp()
6202
6203        for macro in [0, 1]:
6204            with self.subTest(macro=macro):
6205                d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6206
6207                self.assertEqual(d, date(1995, 4, 12))
6208
6209    def test_datetime_from_timestamp(self):
6210        cases = [
6211            ((1995, 4, 12), None, False),
6212            ((1995, 4, 12), None, True),
6213            ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6214            ((1995, 4, 12, 14, 30), None, False),
6215            ((1995, 4, 12, 14, 30), None, True),
6216            ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6217        ]
6218
6219        from_timestamp = _testcapi.get_datetime_fromtimestamp
6220        for case in cases:
6221            for macro in [0, 1]:
6222                with self.subTest(case=case, macro=macro):
6223                    dtup, tzinfo, usetz = case
6224                    dt_orig = datetime(*dtup, tzinfo=tzinfo)
6225                    ts = int(dt_orig.timestamp())
6226
6227                    dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6228
6229                    self.assertEqual(dt_orig, dt_rt)
6230
6231
6232def load_tests(loader, standard_tests, pattern):
6233    standard_tests.addTest(ZoneInfoCompleteTest())
6234    return standard_tests
6235
6236
6237if __name__ == "__main__":
6238    unittest.main()
6239