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