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