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