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