• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
2613    def test_strptime_single_digit(self):
2614        # bpo-34903: Check that single digit dates and times are allowed.
2615
2616        strptime = self.theclass.strptime
2617
2618        with self.assertRaises(ValueError):
2619            # %y does require two digits.
2620            newdate = strptime('01/02/3 04:05:06', '%d/%m/%y %H:%M:%S')
2621        dt1 = self.theclass(2003, 2, 1, 4, 5, 6)
2622        dt2 = self.theclass(2003, 1, 2, 4, 5, 6)
2623        dt3 = self.theclass(2003, 2, 1, 0, 0, 0)
2624        dt4 = self.theclass(2003, 1, 25, 0, 0, 0)
2625        inputs = [
2626            ('%d', '1/02/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2627            ('%m', '01/2/03 4:5:6', '%d/%m/%y %H:%M:%S', dt1),
2628            ('%H', '01/02/03 4:05:06', '%d/%m/%y %H:%M:%S', dt1),
2629            ('%M', '01/02/03 04:5:06', '%d/%m/%y %H:%M:%S', dt1),
2630            ('%S', '01/02/03 04:05:6', '%d/%m/%y %H:%M:%S', dt1),
2631            ('%j', '2/03 04am:05:06', '%j/%y %I%p:%M:%S',dt2),
2632            ('%I', '02/03 4am:05:06', '%j/%y %I%p:%M:%S',dt2),
2633            ('%w', '6/04/03', '%w/%U/%y', dt3),
2634            # %u requires a single digit.
2635            ('%W', '6/4/2003', '%u/%W/%Y', dt3),
2636            ('%V', '6/4/2003', '%u/%V/%G', dt4),
2637        ]
2638        for reason, string, format, target in inputs:
2639            reason = 'test single digit ' + reason
2640            with self.subTest(reason=reason,
2641                              string=string,
2642                              format=format,
2643                              target=target):
2644                newdate = strptime(string, format)
2645                self.assertEqual(newdate, target, msg=reason)
2646
2647    def test_more_timetuple(self):
2648        # This tests fields beyond those tested by the TestDate.test_timetuple.
2649        t = self.theclass(2004, 12, 31, 6, 22, 33)
2650        self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1))
2651        self.assertEqual(t.timetuple(),
2652                         (t.year, t.month, t.day,
2653                          t.hour, t.minute, t.second,
2654                          t.weekday(),
2655                          t.toordinal() - date(t.year, 1, 1).toordinal() + 1,
2656                          -1))
2657        tt = t.timetuple()
2658        self.assertEqual(tt.tm_year, t.year)
2659        self.assertEqual(tt.tm_mon, t.month)
2660        self.assertEqual(tt.tm_mday, t.day)
2661        self.assertEqual(tt.tm_hour, t.hour)
2662        self.assertEqual(tt.tm_min, t.minute)
2663        self.assertEqual(tt.tm_sec, t.second)
2664        self.assertEqual(tt.tm_wday, t.weekday())
2665        self.assertEqual(tt.tm_yday, t.toordinal() -
2666                                     date(t.year, 1, 1).toordinal() + 1)
2667        self.assertEqual(tt.tm_isdst, -1)
2668
2669    def test_more_strftime(self):
2670        # This tests fields beyond those tested by the TestDate.test_strftime.
2671        t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
2672        self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
2673                                    "12 31 04 000047 33 22 06 366")
2674        for (s, us), z in [((33, 123), "33.000123"), ((33, 0), "33"),]:
2675            tz = timezone(-timedelta(hours=2, seconds=s, microseconds=us))
2676            t = t.replace(tzinfo=tz)
2677            self.assertEqual(t.strftime("%z"), "-0200" + z)
2678
2679        # bpo-34482: Check that surrogates don't cause a crash.
2680        try:
2681            t.strftime('%y\ud800%m %H\ud800%M')
2682        except UnicodeEncodeError:
2683            pass
2684
2685    def test_extract(self):
2686        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2687        self.assertEqual(dt.date(), date(2002, 3, 4))
2688        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
2689
2690    def test_combine(self):
2691        d = date(2002, 3, 4)
2692        t = time(18, 45, 3, 1234)
2693        expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
2694        combine = self.theclass.combine
2695        dt = combine(d, t)
2696        self.assertEqual(dt, expected)
2697
2698        dt = combine(time=t, date=d)
2699        self.assertEqual(dt, expected)
2700
2701        self.assertEqual(d, dt.date())
2702        self.assertEqual(t, dt.time())
2703        self.assertEqual(dt, combine(dt.date(), dt.time()))
2704
2705        self.assertRaises(TypeError, combine) # need an arg
2706        self.assertRaises(TypeError, combine, d) # need two args
2707        self.assertRaises(TypeError, combine, t, d) # args reversed
2708        self.assertRaises(TypeError, combine, d, t, 1) # wrong tzinfo type
2709        self.assertRaises(TypeError, combine, d, t, 1, 2)  # too many args
2710        self.assertRaises(TypeError, combine, "date", "time") # wrong types
2711        self.assertRaises(TypeError, combine, d, "time") # wrong type
2712        self.assertRaises(TypeError, combine, "date", t) # wrong type
2713
2714        # tzinfo= argument
2715        dt = combine(d, t, timezone.utc)
2716        self.assertIs(dt.tzinfo, timezone.utc)
2717        dt = combine(d, t, tzinfo=timezone.utc)
2718        self.assertIs(dt.tzinfo, timezone.utc)
2719        t = time()
2720        dt = combine(dt, t)
2721        self.assertEqual(dt.date(), d)
2722        self.assertEqual(dt.time(), t)
2723
2724    def test_replace(self):
2725        cls = self.theclass
2726        args = [1, 2, 3, 4, 5, 6, 7]
2727        base = cls(*args)
2728        self.assertEqual(base, base.replace())
2729
2730        i = 0
2731        for name, newval in (("year", 2),
2732                             ("month", 3),
2733                             ("day", 4),
2734                             ("hour", 5),
2735                             ("minute", 6),
2736                             ("second", 7),
2737                             ("microsecond", 8)):
2738            newargs = args[:]
2739            newargs[i] = newval
2740            expected = cls(*newargs)
2741            got = base.replace(**{name: newval})
2742            self.assertEqual(expected, got)
2743            i += 1
2744
2745        # Out of bounds.
2746        base = cls(2000, 2, 29)
2747        self.assertRaises(ValueError, base.replace, year=2001)
2748
2749    @support.run_with_tz('EDT4')
2750    def test_astimezone(self):
2751        dt = self.theclass.now()
2752        f = FixedOffset(44, "0044")
2753        dt_utc = dt.replace(tzinfo=timezone(timedelta(hours=-4), 'EDT'))
2754        self.assertEqual(dt.astimezone(), dt_utc) # naive
2755        self.assertRaises(TypeError, dt.astimezone, f, f) # too many args
2756        self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type
2757        dt_f = dt.replace(tzinfo=f) + timedelta(hours=4, minutes=44)
2758        self.assertEqual(dt.astimezone(f), dt_f) # naive
2759        self.assertEqual(dt.astimezone(tz=f), dt_f) # naive
2760
2761        class Bogus(tzinfo):
2762            def utcoffset(self, dt): return None
2763            def dst(self, dt): return timedelta(0)
2764        bog = Bogus()
2765        self.assertRaises(ValueError, dt.astimezone, bog)   # naive
2766        self.assertEqual(dt.replace(tzinfo=bog).astimezone(f), dt_f)
2767
2768        class AlsoBogus(tzinfo):
2769            def utcoffset(self, dt): return timedelta(0)
2770            def dst(self, dt): return None
2771        alsobog = AlsoBogus()
2772        self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive
2773
2774        class Broken(tzinfo):
2775            def utcoffset(self, dt): return 1
2776            def dst(self, dt): return 1
2777        broken = Broken()
2778        dt_broken = dt.replace(tzinfo=broken)
2779        with self.assertRaises(TypeError):
2780            dt_broken.astimezone()
2781
2782    def test_subclass_datetime(self):
2783
2784        class C(self.theclass):
2785            theAnswer = 42
2786
2787            def __new__(cls, *args, **kws):
2788                temp = kws.copy()
2789                extra = temp.pop('extra')
2790                result = self.theclass.__new__(cls, *args, **temp)
2791                result.extra = extra
2792                return result
2793
2794            def newmeth(self, start):
2795                return start + self.year + self.month + self.second
2796
2797        args = 2003, 4, 14, 12, 13, 41
2798
2799        dt1 = self.theclass(*args)
2800        dt2 = C(*args, **{'extra': 7})
2801
2802        self.assertEqual(dt2.__class__, C)
2803        self.assertEqual(dt2.theAnswer, 42)
2804        self.assertEqual(dt2.extra, 7)
2805        self.assertEqual(dt1.toordinal(), dt2.toordinal())
2806        self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month +
2807                                          dt1.second - 7)
2808
2809    def test_subclass_alternate_constructors_datetime(self):
2810        # Test that alternate constructors call the constructor
2811        class DateTimeSubclass(self.theclass):
2812            def __new__(cls, *args, **kwargs):
2813                result = self.theclass.__new__(cls, *args, **kwargs)
2814                result.extra = 7
2815
2816                return result
2817
2818        args = (2003, 4, 14, 12, 30, 15, 123456)
2819        d_isoformat = '2003-04-14T12:30:15.123456'      # Equivalent isoformat()
2820        utc_ts = 1050323415.123456                      # UTC timestamp
2821
2822        base_d = DateTimeSubclass(*args)
2823        self.assertIsInstance(base_d, DateTimeSubclass)
2824        self.assertEqual(base_d.extra, 7)
2825
2826        # Timestamp depends on time zone, so we'll calculate the equivalent here
2827        ts = base_d.timestamp()
2828
2829        test_cases = [
2830            ('fromtimestamp', (ts,), base_d),
2831            # See https://bugs.python.org/issue32417
2832            ('fromtimestamp', (ts, timezone.utc),
2833                               base_d.astimezone(timezone.utc)),
2834            ('utcfromtimestamp', (utc_ts,), base_d),
2835            ('fromisoformat', (d_isoformat,), base_d),
2836            ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f'), base_d),
2837            ('combine', (date(*args[0:3]), time(*args[3:])), base_d),
2838        ]
2839
2840        for constr_name, constr_args, expected in test_cases:
2841            for base_obj in (DateTimeSubclass, base_d):
2842                # Test both the classmethod and method
2843                with self.subTest(base_obj_type=type(base_obj),
2844                                  constr_name=constr_name):
2845                    constructor = getattr(base_obj, constr_name)
2846
2847                    dt = constructor(*constr_args)
2848
2849                    # Test that it creates the right subclass
2850                    self.assertIsInstance(dt, DateTimeSubclass)
2851
2852                    # Test that it's equal to the base object
2853                    self.assertEqual(dt, expected)
2854
2855                    # Test that it called the constructor
2856                    self.assertEqual(dt.extra, 7)
2857
2858    def test_subclass_now(self):
2859        # Test that alternate constructors call the constructor
2860        class DateTimeSubclass(self.theclass):
2861            def __new__(cls, *args, **kwargs):
2862                result = self.theclass.__new__(cls, *args, **kwargs)
2863                result.extra = 7
2864
2865                return result
2866
2867        test_cases = [
2868            ('now', 'now', {}),
2869            ('utcnow', 'utcnow', {}),
2870            ('now_utc', 'now', {'tz': timezone.utc}),
2871            ('now_fixed', 'now', {'tz': timezone(timedelta(hours=-5), "EST")}),
2872        ]
2873
2874        for name, meth_name, kwargs in test_cases:
2875            with self.subTest(name):
2876                constr = getattr(DateTimeSubclass, meth_name)
2877                dt = constr(**kwargs)
2878
2879                self.assertIsInstance(dt, DateTimeSubclass)
2880                self.assertEqual(dt.extra, 7)
2881
2882    def test_fromisoformat_datetime(self):
2883        # Test that isoformat() is reversible
2884        base_dates = [
2885            (1, 1, 1),
2886            (1900, 1, 1),
2887            (2004, 11, 12),
2888            (2017, 5, 30)
2889        ]
2890
2891        base_times = [
2892            (0, 0, 0, 0),
2893            (0, 0, 0, 241000),
2894            (0, 0, 0, 234567),
2895            (12, 30, 45, 234567)
2896        ]
2897
2898        separators = [' ', 'T']
2899
2900        tzinfos = [None, timezone.utc,
2901                   timezone(timedelta(hours=-5)),
2902                   timezone(timedelta(hours=2))]
2903
2904        dts = [self.theclass(*date_tuple, *time_tuple, tzinfo=tzi)
2905               for date_tuple in base_dates
2906               for time_tuple in base_times
2907               for tzi in tzinfos]
2908
2909        for dt in dts:
2910            for sep in separators:
2911                dtstr = dt.isoformat(sep=sep)
2912
2913                with self.subTest(dtstr=dtstr):
2914                    dt_rt = self.theclass.fromisoformat(dtstr)
2915                    self.assertEqual(dt, dt_rt)
2916
2917    def test_fromisoformat_timezone(self):
2918        base_dt = self.theclass(2014, 12, 30, 12, 30, 45, 217456)
2919
2920        tzoffsets = [
2921            timedelta(hours=5), timedelta(hours=2),
2922            timedelta(hours=6, minutes=27),
2923            timedelta(hours=12, minutes=32, seconds=30),
2924            timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
2925        ]
2926
2927        tzoffsets += [-1 * td for td in tzoffsets]
2928
2929        tzinfos = [None, timezone.utc,
2930                   timezone(timedelta(hours=0))]
2931
2932        tzinfos += [timezone(td) for td in tzoffsets]
2933
2934        for tzi in tzinfos:
2935            dt = base_dt.replace(tzinfo=tzi)
2936            dtstr = dt.isoformat()
2937
2938            with self.subTest(tstr=dtstr):
2939                dt_rt = self.theclass.fromisoformat(dtstr)
2940                assert dt == dt_rt, dt_rt
2941
2942    def test_fromisoformat_separators(self):
2943        separators = [
2944            ' ', 'T', '\u007f',     # 1-bit widths
2945            '\u0080', 'ʁ',          # 2-bit widths
2946            'ᛇ', '時',               # 3-bit widths
2947            '��',                    # 4-bit widths
2948            '\ud800',               # bpo-34454: Surrogate code point
2949        ]
2950
2951        for sep in separators:
2952            dt = self.theclass(2018, 1, 31, 23, 59, 47, 124789)
2953            dtstr = dt.isoformat(sep=sep)
2954
2955            with self.subTest(dtstr=dtstr):
2956                dt_rt = self.theclass.fromisoformat(dtstr)
2957                self.assertEqual(dt, dt_rt)
2958
2959    def test_fromisoformat_ambiguous(self):
2960        # Test strings like 2018-01-31+12:15 (where +12:15 is not a time zone)
2961        separators = ['+', '-']
2962        for sep in separators:
2963            dt = self.theclass(2018, 1, 31, 12, 15)
2964            dtstr = dt.isoformat(sep=sep)
2965
2966            with self.subTest(dtstr=dtstr):
2967                dt_rt = self.theclass.fromisoformat(dtstr)
2968                self.assertEqual(dt, dt_rt)
2969
2970    def test_fromisoformat_timespecs(self):
2971        datetime_bases = [
2972            (2009, 12, 4, 8, 17, 45, 123456),
2973            (2009, 12, 4, 8, 17, 45, 0)]
2974
2975        tzinfos = [None, timezone.utc,
2976                   timezone(timedelta(hours=-5)),
2977                   timezone(timedelta(hours=2)),
2978                   timezone(timedelta(hours=6, minutes=27))]
2979
2980        timespecs = ['hours', 'minutes', 'seconds',
2981                     'milliseconds', 'microseconds']
2982
2983        for ip, ts in enumerate(timespecs):
2984            for tzi in tzinfos:
2985                for dt_tuple in datetime_bases:
2986                    if ts == 'milliseconds':
2987                        new_microseconds = 1000 * (dt_tuple[6] // 1000)
2988                        dt_tuple = dt_tuple[0:6] + (new_microseconds,)
2989
2990                    dt = self.theclass(*(dt_tuple[0:(4 + ip)]), tzinfo=tzi)
2991                    dtstr = dt.isoformat(timespec=ts)
2992                    with self.subTest(dtstr=dtstr):
2993                        dt_rt = self.theclass.fromisoformat(dtstr)
2994                        self.assertEqual(dt, dt_rt)
2995
2996    def test_fromisoformat_fails_datetime(self):
2997        # Test that fromisoformat() fails on invalid values
2998        bad_strs = [
2999            '',                             # Empty string
3000            '\ud800',                       # bpo-34454: Surrogate code point
3001            '2009.04-19T03',                # Wrong first separator
3002            '2009-04.19T03',                # Wrong second separator
3003            '2009-04-19T0a',                # Invalid hours
3004            '2009-04-19T03:1a:45',          # Invalid minutes
3005            '2009-04-19T03:15:4a',          # Invalid seconds
3006            '2009-04-19T03;15:45',          # Bad first time separator
3007            '2009-04-19T03:15;45',          # Bad second time separator
3008            '2009-04-19T03:15:4500:00',     # Bad time zone separator
3009            '2009-04-19T03:15:45.2345',     # Too many digits for milliseconds
3010            '2009-04-19T03:15:45.1234567',  # Too many digits for microseconds
3011            '2009-04-19T03:15:45.123456+24:30',    # Invalid time zone offset
3012            '2009-04-19T03:15:45.123456-24:30',    # Invalid negative offset
3013            '2009-04-10ᛇᛇᛇᛇᛇ12:15',         # Too many unicode separators
3014            '2009-04\ud80010T12:15',        # Surrogate char in date
3015            '2009-04-10T12\ud80015',        # Surrogate char in time
3016            '2009-04-19T1',                 # Incomplete hours
3017            '2009-04-19T12:3',              # Incomplete minutes
3018            '2009-04-19T12:30:4',           # Incomplete seconds
3019            '2009-04-19T12:',               # Ends with time separator
3020            '2009-04-19T12:30:',            # Ends with time separator
3021            '2009-04-19T12:30:45.',         # Ends with time separator
3022            '2009-04-19T12:30:45.123456+',  # Ends with timzone separator
3023            '2009-04-19T12:30:45.123456-',  # Ends with timzone separator
3024            '2009-04-19T12:30:45.123456-05:00a',    # Extra text
3025            '2009-04-19T12:30:45.123-05:00a',       # Extra text
3026            '2009-04-19T12:30:45-05:00a',           # Extra text
3027        ]
3028
3029        for bad_str in bad_strs:
3030            with self.subTest(bad_str=bad_str):
3031                with self.assertRaises(ValueError):
3032                    self.theclass.fromisoformat(bad_str)
3033
3034    def test_fromisoformat_fails_surrogate(self):
3035        # Test that when fromisoformat() fails with a surrogate character as
3036        # the separator, the error message contains the original string
3037        dtstr = "2018-01-03\ud80001:0113"
3038
3039        with self.assertRaisesRegex(ValueError, re.escape(repr(dtstr))):
3040            self.theclass.fromisoformat(dtstr)
3041
3042    def test_fromisoformat_utc(self):
3043        dt_str = '2014-04-19T13:21:13+00:00'
3044        dt = self.theclass.fromisoformat(dt_str)
3045
3046        self.assertIs(dt.tzinfo, timezone.utc)
3047
3048    def test_fromisoformat_subclass(self):
3049        class DateTimeSubclass(self.theclass):
3050            pass
3051
3052        dt = DateTimeSubclass(2014, 12, 14, 9, 30, 45, 457390,
3053                              tzinfo=timezone(timedelta(hours=10, minutes=45)))
3054
3055        dt_rt = DateTimeSubclass.fromisoformat(dt.isoformat())
3056
3057        self.assertEqual(dt, dt_rt)
3058        self.assertIsInstance(dt_rt, DateTimeSubclass)
3059
3060
3061class TestSubclassDateTime(TestDateTime):
3062    theclass = SubclassDatetime
3063    # Override tests not designed for subclass
3064    @unittest.skip('not appropriate for subclasses')
3065    def test_roundtrip(self):
3066        pass
3067
3068class SubclassTime(time):
3069    sub_var = 1
3070
3071class TestTime(HarmlessMixedComparison, unittest.TestCase):
3072
3073    theclass = time
3074
3075    def test_basic_attributes(self):
3076        t = self.theclass(12, 0)
3077        self.assertEqual(t.hour, 12)
3078        self.assertEqual(t.minute, 0)
3079        self.assertEqual(t.second, 0)
3080        self.assertEqual(t.microsecond, 0)
3081
3082    def test_basic_attributes_nonzero(self):
3083        # Make sure all attributes are non-zero so bugs in
3084        # bit-shifting access show up.
3085        t = self.theclass(12, 59, 59, 8000)
3086        self.assertEqual(t.hour, 12)
3087        self.assertEqual(t.minute, 59)
3088        self.assertEqual(t.second, 59)
3089        self.assertEqual(t.microsecond, 8000)
3090
3091    def test_roundtrip(self):
3092        t = self.theclass(1, 2, 3, 4)
3093
3094        # Verify t -> string -> time identity.
3095        s = repr(t)
3096        self.assertTrue(s.startswith('datetime.'))
3097        s = s[9:]
3098        t2 = eval(s)
3099        self.assertEqual(t, t2)
3100
3101        # Verify identity via reconstructing from pieces.
3102        t2 = self.theclass(t.hour, t.minute, t.second,
3103                           t.microsecond)
3104        self.assertEqual(t, t2)
3105
3106    def test_comparing(self):
3107        args = [1, 2, 3, 4]
3108        t1 = self.theclass(*args)
3109        t2 = self.theclass(*args)
3110        self.assertEqual(t1, t2)
3111        self.assertTrue(t1 <= t2)
3112        self.assertTrue(t1 >= t2)
3113        self.assertFalse(t1 != t2)
3114        self.assertFalse(t1 < t2)
3115        self.assertFalse(t1 > t2)
3116
3117        for i in range(len(args)):
3118            newargs = args[:]
3119            newargs[i] = args[i] + 1
3120            t2 = self.theclass(*newargs)   # this is larger than t1
3121            self.assertTrue(t1 < t2)
3122            self.assertTrue(t2 > t1)
3123            self.assertTrue(t1 <= t2)
3124            self.assertTrue(t2 >= t1)
3125            self.assertTrue(t1 != t2)
3126            self.assertTrue(t2 != t1)
3127            self.assertFalse(t1 == t2)
3128            self.assertFalse(t2 == t1)
3129            self.assertFalse(t1 > t2)
3130            self.assertFalse(t2 < t1)
3131            self.assertFalse(t1 >= t2)
3132            self.assertFalse(t2 <= t1)
3133
3134        for badarg in OTHERSTUFF:
3135            self.assertEqual(t1 == badarg, False)
3136            self.assertEqual(t1 != badarg, True)
3137            self.assertEqual(badarg == t1, False)
3138            self.assertEqual(badarg != t1, True)
3139
3140            self.assertRaises(TypeError, lambda: t1 <= badarg)
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: badarg <= t1)
3145            self.assertRaises(TypeError, lambda: badarg < t1)
3146            self.assertRaises(TypeError, lambda: badarg > t1)
3147            self.assertRaises(TypeError, lambda: badarg >= t1)
3148
3149    def test_bad_constructor_arguments(self):
3150        # bad hours
3151        self.theclass(0, 0)    # no exception
3152        self.theclass(23, 0)   # no exception
3153        self.assertRaises(ValueError, self.theclass, -1, 0)
3154        self.assertRaises(ValueError, self.theclass, 24, 0)
3155        # bad minutes
3156        self.theclass(23, 0)    # no exception
3157        self.theclass(23, 59)   # no exception
3158        self.assertRaises(ValueError, self.theclass, 23, -1)
3159        self.assertRaises(ValueError, self.theclass, 23, 60)
3160        # bad seconds
3161        self.theclass(23, 59, 0)    # no exception
3162        self.theclass(23, 59, 59)   # no exception
3163        self.assertRaises(ValueError, self.theclass, 23, 59, -1)
3164        self.assertRaises(ValueError, self.theclass, 23, 59, 60)
3165        # bad microseconds
3166        self.theclass(23, 59, 59, 0)        # no exception
3167        self.theclass(23, 59, 59, 999999)   # no exception
3168        self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1)
3169        self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000)
3170
3171    def test_hash_equality(self):
3172        d = self.theclass(23, 30, 17)
3173        e = self.theclass(23, 30, 17)
3174        self.assertEqual(d, e)
3175        self.assertEqual(hash(d), hash(e))
3176
3177        dic = {d: 1}
3178        dic[e] = 2
3179        self.assertEqual(len(dic), 1)
3180        self.assertEqual(dic[d], 2)
3181        self.assertEqual(dic[e], 2)
3182
3183        d = self.theclass(0,  5, 17)
3184        e = self.theclass(0,  5, 17)
3185        self.assertEqual(d, e)
3186        self.assertEqual(hash(d), hash(e))
3187
3188        dic = {d: 1}
3189        dic[e] = 2
3190        self.assertEqual(len(dic), 1)
3191        self.assertEqual(dic[d], 2)
3192        self.assertEqual(dic[e], 2)
3193
3194    def test_isoformat(self):
3195        t = self.theclass(4, 5, 1, 123)
3196        self.assertEqual(t.isoformat(), "04:05:01.000123")
3197        self.assertEqual(t.isoformat(), str(t))
3198
3199        t = self.theclass()
3200        self.assertEqual(t.isoformat(), "00:00:00")
3201        self.assertEqual(t.isoformat(), str(t))
3202
3203        t = self.theclass(microsecond=1)
3204        self.assertEqual(t.isoformat(), "00:00:00.000001")
3205        self.assertEqual(t.isoformat(), str(t))
3206
3207        t = self.theclass(microsecond=10)
3208        self.assertEqual(t.isoformat(), "00:00:00.000010")
3209        self.assertEqual(t.isoformat(), str(t))
3210
3211        t = self.theclass(microsecond=100)
3212        self.assertEqual(t.isoformat(), "00:00:00.000100")
3213        self.assertEqual(t.isoformat(), str(t))
3214
3215        t = self.theclass(microsecond=1000)
3216        self.assertEqual(t.isoformat(), "00:00:00.001000")
3217        self.assertEqual(t.isoformat(), str(t))
3218
3219        t = self.theclass(microsecond=10000)
3220        self.assertEqual(t.isoformat(), "00:00:00.010000")
3221        self.assertEqual(t.isoformat(), str(t))
3222
3223        t = self.theclass(microsecond=100000)
3224        self.assertEqual(t.isoformat(), "00:00:00.100000")
3225        self.assertEqual(t.isoformat(), str(t))
3226
3227        t = self.theclass(hour=12, minute=34, second=56, microsecond=123456)
3228        self.assertEqual(t.isoformat(timespec='hours'), "12")
3229        self.assertEqual(t.isoformat(timespec='minutes'), "12:34")
3230        self.assertEqual(t.isoformat(timespec='seconds'), "12:34:56")
3231        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.123")
3232        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.123456")
3233        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56.123456")
3234        self.assertRaises(ValueError, t.isoformat, timespec='monkey')
3235        # bpo-34482: Check that surrogates are handled properly.
3236        self.assertRaises(ValueError, t.isoformat, timespec='\ud800')
3237
3238        t = self.theclass(hour=12, minute=34, second=56, microsecond=999500)
3239        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.999")
3240
3241        t = self.theclass(hour=12, minute=34, second=56, microsecond=0)
3242        self.assertEqual(t.isoformat(timespec='milliseconds'), "12:34:56.000")
3243        self.assertEqual(t.isoformat(timespec='microseconds'), "12:34:56.000000")
3244        self.assertEqual(t.isoformat(timespec='auto'), "12:34:56")
3245
3246    def test_isoformat_timezone(self):
3247        tzoffsets = [
3248            ('05:00', timedelta(hours=5)),
3249            ('02:00', timedelta(hours=2)),
3250            ('06:27', timedelta(hours=6, minutes=27)),
3251            ('12:32:30', timedelta(hours=12, minutes=32, seconds=30)),
3252            ('02:04:09.123456', timedelta(hours=2, minutes=4, seconds=9, microseconds=123456))
3253        ]
3254
3255        tzinfos = [
3256            ('', None),
3257            ('+00:00', timezone.utc),
3258            ('+00:00', timezone(timedelta(0))),
3259        ]
3260
3261        tzinfos += [
3262            (prefix + expected, timezone(sign * td))
3263            for expected, td in tzoffsets
3264            for prefix, sign in [('-', -1), ('+', 1)]
3265        ]
3266
3267        t_base = self.theclass(12, 37, 9)
3268        exp_base = '12:37:09'
3269
3270        for exp_tz, tzi in tzinfos:
3271            t = t_base.replace(tzinfo=tzi)
3272            exp = exp_base + exp_tz
3273            with self.subTest(tzi=tzi):
3274                assert t.isoformat() == exp
3275
3276    def test_1653736(self):
3277        # verify it doesn't accept extra keyword arguments
3278        t = self.theclass(second=1)
3279        self.assertRaises(TypeError, t.isoformat, foo=3)
3280
3281    def test_strftime(self):
3282        t = self.theclass(1, 2, 3, 4)
3283        self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
3284        # A naive object replaces %z and %Z with empty strings.
3285        self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
3286
3287        # bpo-34482: Check that surrogates don't cause a crash.
3288        try:
3289            t.strftime('%H\ud800%M')
3290        except UnicodeEncodeError:
3291            pass
3292
3293    def test_format(self):
3294        t = self.theclass(1, 2, 3, 4)
3295        self.assertEqual(t.__format__(''), str(t))
3296
3297        with self.assertRaisesRegex(TypeError, 'must be str, not int'):
3298            t.__format__(123)
3299
3300        # check that a derived class's __str__() gets called
3301        class A(self.theclass):
3302            def __str__(self):
3303                return 'A'
3304        a = A(1, 2, 3, 4)
3305        self.assertEqual(a.__format__(''), 'A')
3306
3307        # check that a derived class's strftime gets called
3308        class B(self.theclass):
3309            def strftime(self, format_spec):
3310                return 'B'
3311        b = B(1, 2, 3, 4)
3312        self.assertEqual(b.__format__(''), str(t))
3313
3314        for fmt in ['%H %M %S',
3315                    ]:
3316            self.assertEqual(t.__format__(fmt), t.strftime(fmt))
3317            self.assertEqual(a.__format__(fmt), t.strftime(fmt))
3318            self.assertEqual(b.__format__(fmt), 'B')
3319
3320    def test_str(self):
3321        self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004")
3322        self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000")
3323        self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000")
3324        self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03")
3325        self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00")
3326
3327    def test_repr(self):
3328        name = 'datetime.' + self.theclass.__name__
3329        self.assertEqual(repr(self.theclass(1, 2, 3, 4)),
3330                         "%s(1, 2, 3, 4)" % name)
3331        self.assertEqual(repr(self.theclass(10, 2, 3, 4000)),
3332                         "%s(10, 2, 3, 4000)" % name)
3333        self.assertEqual(repr(self.theclass(0, 2, 3, 400000)),
3334                         "%s(0, 2, 3, 400000)" % name)
3335        self.assertEqual(repr(self.theclass(12, 2, 3, 0)),
3336                         "%s(12, 2, 3)" % name)
3337        self.assertEqual(repr(self.theclass(23, 15, 0, 0)),
3338                         "%s(23, 15)" % name)
3339
3340    def test_resolution_info(self):
3341        self.assertIsInstance(self.theclass.min, self.theclass)
3342        self.assertIsInstance(self.theclass.max, self.theclass)
3343        self.assertIsInstance(self.theclass.resolution, timedelta)
3344        self.assertTrue(self.theclass.max > self.theclass.min)
3345
3346    def test_pickling(self):
3347        args = 20, 59, 16, 64**2
3348        orig = self.theclass(*args)
3349        for pickler, unpickler, proto in pickle_choices:
3350            green = pickler.dumps(orig, proto)
3351            derived = unpickler.loads(green)
3352            self.assertEqual(orig, derived)
3353        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3354
3355    def test_pickling_subclass_time(self):
3356        args = 20, 59, 16, 64**2
3357        orig = SubclassTime(*args)
3358        for pickler, unpickler, proto in pickle_choices:
3359            green = pickler.dumps(orig, proto)
3360            derived = unpickler.loads(green)
3361            self.assertEqual(orig, derived)
3362            self.assertTrue(isinstance(derived, SubclassTime))
3363
3364    def test_compat_unpickle(self):
3365        tests = [
3366            (b"cdatetime\ntime\n(S'\\x14;\\x10\\x00\\x10\\x00'\ntR.",
3367             (20, 59, 16, 64**2)),
3368            (b'cdatetime\ntime\n(U\x06\x14;\x10\x00\x10\x00tR.',
3369             (20, 59, 16, 64**2)),
3370            (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x10\x00\x10\x00\x85R.',
3371             (20, 59, 16, 64**2)),
3372            (b"cdatetime\ntime\n(S'\\x14;\\x19\\x00\\x10\\x00'\ntR.",
3373             (20, 59, 25, 64**2)),
3374            (b'cdatetime\ntime\n(U\x06\x14;\x19\x00\x10\x00tR.',
3375             (20, 59, 25, 64**2)),
3376            (b'\x80\x02cdatetime\ntime\nU\x06\x14;\x19\x00\x10\x00\x85R.',
3377             (20, 59, 25, 64**2)),
3378        ]
3379        for i, (data, args) in enumerate(tests):
3380            with self.subTest(i=i):
3381                expected = self.theclass(*args)
3382                for loads in pickle_loads:
3383                    derived = loads(data, encoding='latin1')
3384                    self.assertEqual(derived, expected)
3385
3386    def test_bool(self):
3387        # time is always True.
3388        cls = self.theclass
3389        self.assertTrue(cls(1))
3390        self.assertTrue(cls(0, 1))
3391        self.assertTrue(cls(0, 0, 1))
3392        self.assertTrue(cls(0, 0, 0, 1))
3393        self.assertTrue(cls(0))
3394        self.assertTrue(cls())
3395
3396    def test_replace(self):
3397        cls = self.theclass
3398        args = [1, 2, 3, 4]
3399        base = cls(*args)
3400        self.assertEqual(base, base.replace())
3401
3402        i = 0
3403        for name, newval in (("hour", 5),
3404                             ("minute", 6),
3405                             ("second", 7),
3406                             ("microsecond", 8)):
3407            newargs = args[:]
3408            newargs[i] = newval
3409            expected = cls(*newargs)
3410            got = base.replace(**{name: newval})
3411            self.assertEqual(expected, got)
3412            i += 1
3413
3414        # Out of bounds.
3415        base = cls(1)
3416        self.assertRaises(ValueError, base.replace, hour=24)
3417        self.assertRaises(ValueError, base.replace, minute=-1)
3418        self.assertRaises(ValueError, base.replace, second=100)
3419        self.assertRaises(ValueError, base.replace, microsecond=1000000)
3420
3421    def test_subclass_replace(self):
3422        class TimeSubclass(self.theclass):
3423            pass
3424
3425        ctime = TimeSubclass(12, 30)
3426        self.assertIs(type(ctime.replace(hour=10)), TimeSubclass)
3427
3428    def test_subclass_time(self):
3429
3430        class C(self.theclass):
3431            theAnswer = 42
3432
3433            def __new__(cls, *args, **kws):
3434                temp = kws.copy()
3435                extra = temp.pop('extra')
3436                result = self.theclass.__new__(cls, *args, **temp)
3437                result.extra = extra
3438                return result
3439
3440            def newmeth(self, start):
3441                return start + self.hour + self.second
3442
3443        args = 4, 5, 6
3444
3445        dt1 = self.theclass(*args)
3446        dt2 = C(*args, **{'extra': 7})
3447
3448        self.assertEqual(dt2.__class__, C)
3449        self.assertEqual(dt2.theAnswer, 42)
3450        self.assertEqual(dt2.extra, 7)
3451        self.assertEqual(dt1.isoformat(), dt2.isoformat())
3452        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
3453
3454    def test_backdoor_resistance(self):
3455        # see TestDate.test_backdoor_resistance().
3456        base = '2:59.0'
3457        for hour_byte in ' ', '9', chr(24), '\xff':
3458            self.assertRaises(TypeError, self.theclass,
3459                                         hour_byte + base[1:])
3460        # Good bytes, but bad tzinfo:
3461        with self.assertRaisesRegex(TypeError, '^bad tzinfo state arg$'):
3462            self.theclass(bytes([1] * len(base)), 'EST')
3463
3464# A mixin for classes with a tzinfo= argument.  Subclasses must define
3465# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever)
3466# must be legit (which is true for time and datetime).
3467class TZInfoBase:
3468
3469    def test_argument_passing(self):
3470        cls = self.theclass
3471        # A datetime passes itself on, a time passes None.
3472        class introspective(tzinfo):
3473            def tzname(self, dt):    return dt and "real" or "none"
3474            def utcoffset(self, dt):
3475                return timedelta(minutes = dt and 42 or -42)
3476            dst = utcoffset
3477
3478        obj = cls(1, 2, 3, tzinfo=introspective())
3479
3480        expected = cls is time and "none" or "real"
3481        self.assertEqual(obj.tzname(), expected)
3482
3483        expected = timedelta(minutes=(cls is time and -42 or 42))
3484        self.assertEqual(obj.utcoffset(), expected)
3485        self.assertEqual(obj.dst(), expected)
3486
3487    def test_bad_tzinfo_classes(self):
3488        cls = self.theclass
3489        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
3490
3491        class NiceTry(object):
3492            def __init__(self): pass
3493            def utcoffset(self, dt): pass
3494        self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
3495
3496        class BetterTry(tzinfo):
3497            def __init__(self): pass
3498            def utcoffset(self, dt): pass
3499        b = BetterTry()
3500        t = cls(1, 1, 1, tzinfo=b)
3501        self.assertIs(t.tzinfo, b)
3502
3503    def test_utc_offset_out_of_bounds(self):
3504        class Edgy(tzinfo):
3505            def __init__(self, offset):
3506                self.offset = timedelta(minutes=offset)
3507            def utcoffset(self, dt):
3508                return self.offset
3509
3510        cls = self.theclass
3511        for offset, legit in ((-1440, False),
3512                              (-1439, True),
3513                              (1439, True),
3514                              (1440, False)):
3515            if cls is time:
3516                t = cls(1, 2, 3, tzinfo=Edgy(offset))
3517            elif cls is datetime:
3518                t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
3519            else:
3520                assert 0, "impossible"
3521            if legit:
3522                aofs = abs(offset)
3523                h, m = divmod(aofs, 60)
3524                tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
3525                if isinstance(t, datetime):
3526                    t = t.timetz()
3527                self.assertEqual(str(t), "01:02:03" + tag)
3528            else:
3529                self.assertRaises(ValueError, str, t)
3530
3531    def test_tzinfo_classes(self):
3532        cls = self.theclass
3533        class C1(tzinfo):
3534            def utcoffset(self, dt): return None
3535            def dst(self, dt): return None
3536            def tzname(self, dt): return None
3537        for t in (cls(1, 1, 1),
3538                  cls(1, 1, 1, tzinfo=None),
3539                  cls(1, 1, 1, tzinfo=C1())):
3540            self.assertIsNone(t.utcoffset())
3541            self.assertIsNone(t.dst())
3542            self.assertIsNone(t.tzname())
3543
3544        class C3(tzinfo):
3545            def utcoffset(self, dt): return timedelta(minutes=-1439)
3546            def dst(self, dt): return timedelta(minutes=1439)
3547            def tzname(self, dt): return "aname"
3548        t = cls(1, 1, 1, tzinfo=C3())
3549        self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
3550        self.assertEqual(t.dst(), timedelta(minutes=1439))
3551        self.assertEqual(t.tzname(), "aname")
3552
3553        # Wrong types.
3554        class C4(tzinfo):
3555            def utcoffset(self, dt): return "aname"
3556            def dst(self, dt): return 7
3557            def tzname(self, dt): return 0
3558        t = cls(1, 1, 1, tzinfo=C4())
3559        self.assertRaises(TypeError, t.utcoffset)
3560        self.assertRaises(TypeError, t.dst)
3561        self.assertRaises(TypeError, t.tzname)
3562
3563        # Offset out of range.
3564        class C6(tzinfo):
3565            def utcoffset(self, dt): return timedelta(hours=-24)
3566            def dst(self, dt): return timedelta(hours=24)
3567        t = cls(1, 1, 1, tzinfo=C6())
3568        self.assertRaises(ValueError, t.utcoffset)
3569        self.assertRaises(ValueError, t.dst)
3570
3571        # Not a whole number of seconds.
3572        class C7(tzinfo):
3573            def utcoffset(self, dt): return timedelta(microseconds=61)
3574            def dst(self, dt): return timedelta(microseconds=-81)
3575        t = cls(1, 1, 1, tzinfo=C7())
3576        self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
3577        self.assertEqual(t.dst(), timedelta(microseconds=-81))
3578
3579    def test_aware_compare(self):
3580        cls = self.theclass
3581
3582        # Ensure that utcoffset() gets ignored if the comparands have
3583        # the same tzinfo member.
3584        class OperandDependentOffset(tzinfo):
3585            def utcoffset(self, t):
3586                if t.minute < 10:
3587                    # d0 and d1 equal after adjustment
3588                    return timedelta(minutes=t.minute)
3589                else:
3590                    # d2 off in the weeds
3591                    return timedelta(minutes=59)
3592
3593        base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
3594        d0 = base.replace(minute=3)
3595        d1 = base.replace(minute=9)
3596        d2 = base.replace(minute=11)
3597        for x in d0, d1, d2:
3598            for y in d0, d1, d2:
3599                for op in lt, le, gt, ge, eq, ne:
3600                    got = op(x, y)
3601                    expected = op(x.minute, y.minute)
3602                    self.assertEqual(got, expected)
3603
3604        # However, if they're different members, uctoffset is not ignored.
3605        # Note that a time can't actually have an operand-dependent offset,
3606        # though (and time.utcoffset() passes None to tzinfo.utcoffset()),
3607        # so skip this test for time.
3608        if cls is not time:
3609            d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
3610            d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
3611            d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
3612            for x in d0, d1, d2:
3613                for y in d0, d1, d2:
3614                    got = (x > y) - (x < y)
3615                    if (x is d0 or x is d1) and (y is d0 or y is d1):
3616                        expected = 0
3617                    elif x is y is d2:
3618                        expected = 0
3619                    elif x is d2:
3620                        expected = -1
3621                    else:
3622                        assert y is d2
3623                        expected = 1
3624                    self.assertEqual(got, expected)
3625
3626
3627# Testing time objects with a non-None tzinfo.
3628class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase):
3629    theclass = time
3630
3631    def test_empty(self):
3632        t = self.theclass()
3633        self.assertEqual(t.hour, 0)
3634        self.assertEqual(t.minute, 0)
3635        self.assertEqual(t.second, 0)
3636        self.assertEqual(t.microsecond, 0)
3637        self.assertIsNone(t.tzinfo)
3638
3639    def test_zones(self):
3640        est = FixedOffset(-300, "EST", 1)
3641        utc = FixedOffset(0, "UTC", -2)
3642        met = FixedOffset(60, "MET", 3)
3643        t1 = time( 7, 47, tzinfo=est)
3644        t2 = time(12, 47, tzinfo=utc)
3645        t3 = time(13, 47, tzinfo=met)
3646        t4 = time(microsecond=40)
3647        t5 = time(microsecond=40, tzinfo=utc)
3648
3649        self.assertEqual(t1.tzinfo, est)
3650        self.assertEqual(t2.tzinfo, utc)
3651        self.assertEqual(t3.tzinfo, met)
3652        self.assertIsNone(t4.tzinfo)
3653        self.assertEqual(t5.tzinfo, utc)
3654
3655        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
3656        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
3657        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
3658        self.assertIsNone(t4.utcoffset())
3659        self.assertRaises(TypeError, t1.utcoffset, "no args")
3660
3661        self.assertEqual(t1.tzname(), "EST")
3662        self.assertEqual(t2.tzname(), "UTC")
3663        self.assertEqual(t3.tzname(), "MET")
3664        self.assertIsNone(t4.tzname())
3665        self.assertRaises(TypeError, t1.tzname, "no args")
3666
3667        self.assertEqual(t1.dst(), timedelta(minutes=1))
3668        self.assertEqual(t2.dst(), timedelta(minutes=-2))
3669        self.assertEqual(t3.dst(), timedelta(minutes=3))
3670        self.assertIsNone(t4.dst())
3671        self.assertRaises(TypeError, t1.dst, "no args")
3672
3673        self.assertEqual(hash(t1), hash(t2))
3674        self.assertEqual(hash(t1), hash(t3))
3675        self.assertEqual(hash(t2), hash(t3))
3676
3677        self.assertEqual(t1, t2)
3678        self.assertEqual(t1, t3)
3679        self.assertEqual(t2, t3)
3680        self.assertNotEqual(t4, t5) # mixed tz-aware & naive
3681        self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive
3682        self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive
3683
3684        self.assertEqual(str(t1), "07:47:00-05:00")
3685        self.assertEqual(str(t2), "12:47:00+00:00")
3686        self.assertEqual(str(t3), "13:47:00+01:00")
3687        self.assertEqual(str(t4), "00:00:00.000040")
3688        self.assertEqual(str(t5), "00:00:00.000040+00:00")
3689
3690        self.assertEqual(t1.isoformat(), "07:47:00-05:00")
3691        self.assertEqual(t2.isoformat(), "12:47:00+00:00")
3692        self.assertEqual(t3.isoformat(), "13:47:00+01:00")
3693        self.assertEqual(t4.isoformat(), "00:00:00.000040")
3694        self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00")
3695
3696        d = 'datetime.time'
3697        self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)")
3698        self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)")
3699        self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)")
3700        self.assertEqual(repr(t4), d + "(0, 0, 0, 40)")
3701        self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)")
3702
3703        self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"),
3704                                     "07:47:00 %Z=EST %z=-0500")
3705        self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000")
3706        self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100")
3707
3708        yuck = FixedOffset(-1439, "%z %Z %%z%%Z")
3709        t1 = time(23, 59, tzinfo=yuck)
3710        self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"),
3711                                     "23:59 %Z='%z %Z %%z%%Z' %z='-2359'")
3712
3713        # Check that an invalid tzname result raises an exception.
3714        class Badtzname(tzinfo):
3715            tz = 42
3716            def tzname(self, dt): return self.tz
3717        t = time(2, 3, 4, tzinfo=Badtzname())
3718        self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04")
3719        self.assertRaises(TypeError, t.strftime, "%Z")
3720
3721        # Issue #6697:
3722        if '_Fast' in self.__class__.__name__:
3723            Badtzname.tz = '\ud800'
3724            self.assertRaises(ValueError, t.strftime, "%Z")
3725
3726    def test_hash_edge_cases(self):
3727        # Offsets that overflow a basic time.
3728        t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, ""))
3729        t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, ""))
3730        self.assertEqual(hash(t1), hash(t2))
3731
3732        t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, ""))
3733        t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
3734        self.assertEqual(hash(t1), hash(t2))
3735
3736    def test_pickling(self):
3737        # Try one without a tzinfo.
3738        args = 20, 59, 16, 64**2
3739        orig = self.theclass(*args)
3740        for pickler, unpickler, proto in pickle_choices:
3741            green = pickler.dumps(orig, proto)
3742            derived = unpickler.loads(green)
3743            self.assertEqual(orig, derived)
3744        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3745
3746        # Try one with a tzinfo.
3747        tinfo = PicklableFixedOffset(-300, 'cookie')
3748        orig = self.theclass(5, 6, 7, tzinfo=tinfo)
3749        for pickler, unpickler, proto in pickle_choices:
3750            green = pickler.dumps(orig, proto)
3751            derived = unpickler.loads(green)
3752            self.assertEqual(orig, derived)
3753            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3754            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3755            self.assertEqual(derived.tzname(), 'cookie')
3756        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
3757
3758    def test_compat_unpickle(self):
3759        tests = [
3760            b"cdatetime\ntime\n(S'\\x05\\x06\\x07\\x01\\xe2@'\n"
3761            b"ctest.datetimetester\nPicklableFixedOffset\n(tR"
3762            b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
3763            b"(I-1\nI68400\nI0\ntRs"
3764            b"S'_FixedOffset__dstoffset'\nNs"
3765            b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
3766
3767            b'cdatetime\ntime\n(U\x06\x05\x06\x07\x01\xe2@'
3768            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3769            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3770            b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
3771            b'U\x17_FixedOffset__dstoffsetN'
3772            b'U\x12_FixedOffset__nameU\x06cookieubtR.',
3773
3774            b'\x80\x02cdatetime\ntime\nU\x06\x05\x06\x07\x01\xe2@'
3775            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
3776            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
3777            b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
3778            b'U\x17_FixedOffset__dstoffsetN'
3779            b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
3780        ]
3781
3782        tinfo = PicklableFixedOffset(-300, 'cookie')
3783        expected = self.theclass(5, 6, 7, 123456, tzinfo=tinfo)
3784        for data in tests:
3785            for loads in pickle_loads:
3786                derived = loads(data, encoding='latin1')
3787                self.assertEqual(derived, expected, repr(data))
3788                self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
3789                self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
3790                self.assertEqual(derived.tzname(), 'cookie')
3791
3792    def test_more_bool(self):
3793        # time is always True.
3794        cls = self.theclass
3795
3796        t = cls(0, tzinfo=FixedOffset(-300, ""))
3797        self.assertTrue(t)
3798
3799        t = cls(5, tzinfo=FixedOffset(-300, ""))
3800        self.assertTrue(t)
3801
3802        t = cls(5, tzinfo=FixedOffset(300, ""))
3803        self.assertTrue(t)
3804
3805        t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, ""))
3806        self.assertTrue(t)
3807
3808    def test_replace(self):
3809        cls = self.theclass
3810        z100 = FixedOffset(100, "+100")
3811        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
3812        args = [1, 2, 3, 4, z100]
3813        base = cls(*args)
3814        self.assertEqual(base, base.replace())
3815
3816        i = 0
3817        for name, newval in (("hour", 5),
3818                             ("minute", 6),
3819                             ("second", 7),
3820                             ("microsecond", 8),
3821                             ("tzinfo", zm200)):
3822            newargs = args[:]
3823            newargs[i] = newval
3824            expected = cls(*newargs)
3825            got = base.replace(**{name: newval})
3826            self.assertEqual(expected, got)
3827            i += 1
3828
3829        # Ensure we can get rid of a tzinfo.
3830        self.assertEqual(base.tzname(), "+100")
3831        base2 = base.replace(tzinfo=None)
3832        self.assertIsNone(base2.tzinfo)
3833        self.assertIsNone(base2.tzname())
3834
3835        # Ensure we can add one.
3836        base3 = base2.replace(tzinfo=z100)
3837        self.assertEqual(base, base3)
3838        self.assertIs(base.tzinfo, base3.tzinfo)
3839
3840        # Out of bounds.
3841        base = cls(1)
3842        self.assertRaises(ValueError, base.replace, hour=24)
3843        self.assertRaises(ValueError, base.replace, minute=-1)
3844        self.assertRaises(ValueError, base.replace, second=100)
3845        self.assertRaises(ValueError, base.replace, microsecond=1000000)
3846
3847    def test_mixed_compare(self):
3848        t1 = self.theclass(1, 2, 3)
3849        t2 = self.theclass(1, 2, 3)
3850        self.assertEqual(t1, t2)
3851        t2 = t2.replace(tzinfo=None)
3852        self.assertEqual(t1, t2)
3853        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
3854        self.assertEqual(t1, t2)
3855        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
3856        self.assertNotEqual(t1, t2)
3857
3858        # In time w/ identical tzinfo objects, utcoffset is ignored.
3859        class Varies(tzinfo):
3860            def __init__(self):
3861                self.offset = timedelta(minutes=22)
3862            def utcoffset(self, t):
3863                self.offset += timedelta(minutes=1)
3864                return self.offset
3865
3866        v = Varies()
3867        t1 = t2.replace(tzinfo=v)
3868        t2 = t2.replace(tzinfo=v)
3869        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
3870        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
3871        self.assertEqual(t1, t2)
3872
3873        # But if they're not identical, it isn't ignored.
3874        t2 = t2.replace(tzinfo=Varies())
3875        self.assertTrue(t1 < t2)  # t1's offset counter still going up
3876
3877    def test_fromisoformat(self):
3878        time_examples = [
3879            (0, 0, 0, 0),
3880            (23, 59, 59, 999999),
3881        ]
3882
3883        hh = (9, 12, 20)
3884        mm = (5, 30)
3885        ss = (4, 45)
3886        usec = (0, 245000, 678901)
3887
3888        time_examples += list(itertools.product(hh, mm, ss, usec))
3889
3890        tzinfos = [None, timezone.utc,
3891                   timezone(timedelta(hours=2)),
3892                   timezone(timedelta(hours=6, minutes=27))]
3893
3894        for ttup in time_examples:
3895            for tzi in tzinfos:
3896                t = self.theclass(*ttup, tzinfo=tzi)
3897                tstr = t.isoformat()
3898
3899                with self.subTest(tstr=tstr):
3900                    t_rt = self.theclass.fromisoformat(tstr)
3901                    self.assertEqual(t, t_rt)
3902
3903    def test_fromisoformat_timezone(self):
3904        base_time = self.theclass(12, 30, 45, 217456)
3905
3906        tzoffsets = [
3907            timedelta(hours=5), timedelta(hours=2),
3908            timedelta(hours=6, minutes=27),
3909            timedelta(hours=12, minutes=32, seconds=30),
3910            timedelta(hours=2, minutes=4, seconds=9, microseconds=123456)
3911        ]
3912
3913        tzoffsets += [-1 * td for td in tzoffsets]
3914
3915        tzinfos = [None, timezone.utc,
3916                   timezone(timedelta(hours=0))]
3917
3918        tzinfos += [timezone(td) for td in tzoffsets]
3919
3920        for tzi in tzinfos:
3921            t = base_time.replace(tzinfo=tzi)
3922            tstr = t.isoformat()
3923
3924            with self.subTest(tstr=tstr):
3925                t_rt = self.theclass.fromisoformat(tstr)
3926                assert t == t_rt, t_rt
3927
3928    def test_fromisoformat_timespecs(self):
3929        time_bases = [
3930            (8, 17, 45, 123456),
3931            (8, 17, 45, 0)
3932        ]
3933
3934        tzinfos = [None, timezone.utc,
3935                   timezone(timedelta(hours=-5)),
3936                   timezone(timedelta(hours=2)),
3937                   timezone(timedelta(hours=6, minutes=27))]
3938
3939        timespecs = ['hours', 'minutes', 'seconds',
3940                     'milliseconds', 'microseconds']
3941
3942        for ip, ts in enumerate(timespecs):
3943            for tzi in tzinfos:
3944                for t_tuple in time_bases:
3945                    if ts == 'milliseconds':
3946                        new_microseconds = 1000 * (t_tuple[-1] // 1000)
3947                        t_tuple = t_tuple[0:-1] + (new_microseconds,)
3948
3949                    t = self.theclass(*(t_tuple[0:(1 + ip)]), tzinfo=tzi)
3950                    tstr = t.isoformat(timespec=ts)
3951                    with self.subTest(tstr=tstr):
3952                        t_rt = self.theclass.fromisoformat(tstr)
3953                        self.assertEqual(t, t_rt)
3954
3955    def test_fromisoformat_fails(self):
3956        bad_strs = [
3957            '',                         # Empty string
3958            '12\ud80000',               # Invalid separator - surrogate char
3959            '12:',                      # Ends on a separator
3960            '12:30:',                   # Ends on a separator
3961            '12:30:15.',                # Ends on a separator
3962            '1',                        # Incomplete hours
3963            '12:3',                     # Incomplete minutes
3964            '12:30:1',                  # Incomplete seconds
3965            '1a:30:45.334034',          # Invalid character in hours
3966            '12:a0:45.334034',          # Invalid character in minutes
3967            '12:30:a5.334034',          # Invalid character in seconds
3968            '12:30:45.1234',            # Too many digits for milliseconds
3969            '12:30:45.1234567',         # Too many digits for microseconds
3970            '12:30:45.123456+24:30',    # Invalid time zone offset
3971            '12:30:45.123456-24:30',    # Invalid negative offset
3972            '12:30:45',                 # Uses full-width unicode colons
3973            '12:30:45․123456',          # Uses \u2024 in place of decimal point
3974            '12:30:45a',                # Extra at tend of basic time
3975            '12:30:45.123a',            # Extra at end of millisecond time
3976            '12:30:45.123456a',         # Extra at end of microsecond time
3977            '12:30:45.123456+12:00:30a',    # Extra at end of full time
3978        ]
3979
3980        for bad_str in bad_strs:
3981            with self.subTest(bad_str=bad_str):
3982                with self.assertRaises(ValueError):
3983                    self.theclass.fromisoformat(bad_str)
3984
3985    def test_fromisoformat_fails_typeerror(self):
3986        # Test the fromisoformat fails when passed the wrong type
3987        import io
3988
3989        bad_types = [b'12:30:45', None, io.StringIO('12:30:45')]
3990
3991        for bad_type in bad_types:
3992            with self.assertRaises(TypeError):
3993                self.theclass.fromisoformat(bad_type)
3994
3995    def test_fromisoformat_subclass(self):
3996        class TimeSubclass(self.theclass):
3997            pass
3998
3999        tsc = TimeSubclass(12, 14, 45, 203745, tzinfo=timezone.utc)
4000        tsc_rt = TimeSubclass.fromisoformat(tsc.isoformat())
4001
4002        self.assertEqual(tsc, tsc_rt)
4003        self.assertIsInstance(tsc_rt, TimeSubclass)
4004
4005    def test_subclass_timetz(self):
4006
4007        class C(self.theclass):
4008            theAnswer = 42
4009
4010            def __new__(cls, *args, **kws):
4011                temp = kws.copy()
4012                extra = temp.pop('extra')
4013                result = self.theclass.__new__(cls, *args, **temp)
4014                result.extra = extra
4015                return result
4016
4017            def newmeth(self, start):
4018                return start + self.hour + self.second
4019
4020        args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4021
4022        dt1 = self.theclass(*args)
4023        dt2 = C(*args, **{'extra': 7})
4024
4025        self.assertEqual(dt2.__class__, C)
4026        self.assertEqual(dt2.theAnswer, 42)
4027        self.assertEqual(dt2.extra, 7)
4028        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4029        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7)
4030
4031
4032# Testing datetime objects with a non-None tzinfo.
4033
4034class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
4035    theclass = datetime
4036
4037    def test_trivial(self):
4038        dt = self.theclass(1, 2, 3, 4, 5, 6, 7)
4039        self.assertEqual(dt.year, 1)
4040        self.assertEqual(dt.month, 2)
4041        self.assertEqual(dt.day, 3)
4042        self.assertEqual(dt.hour, 4)
4043        self.assertEqual(dt.minute, 5)
4044        self.assertEqual(dt.second, 6)
4045        self.assertEqual(dt.microsecond, 7)
4046        self.assertEqual(dt.tzinfo, None)
4047
4048    def test_even_more_compare(self):
4049        # The test_compare() and test_more_compare() inherited from TestDate
4050        # and TestDateTime covered non-tzinfo cases.
4051
4052        # Smallest possible after UTC adjustment.
4053        t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4054        # Largest possible after UTC adjustment.
4055        t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4056                           tzinfo=FixedOffset(-1439, ""))
4057
4058        # Make sure those compare correctly, and w/o overflow.
4059        self.assertTrue(t1 < t2)
4060        self.assertTrue(t1 != t2)
4061        self.assertTrue(t2 > t1)
4062
4063        self.assertEqual(t1, t1)
4064        self.assertEqual(t2, t2)
4065
4066        # Equal afer adjustment.
4067        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""))
4068        t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, ""))
4069        self.assertEqual(t1, t2)
4070
4071        # Change t1 not to subtract a minute, and t1 should be larger.
4072        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, ""))
4073        self.assertTrue(t1 > t2)
4074
4075        # Change t1 to subtract 2 minutes, and t1 should be smaller.
4076        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, ""))
4077        self.assertTrue(t1 < t2)
4078
4079        # Back to the original t1, but make seconds resolve it.
4080        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4081                           second=1)
4082        self.assertTrue(t1 > t2)
4083
4084        # Likewise, but make microseconds resolve it.
4085        t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""),
4086                           microsecond=1)
4087        self.assertTrue(t1 > t2)
4088
4089        # Make t2 naive and it should differ.
4090        t2 = self.theclass.min
4091        self.assertNotEqual(t1, t2)
4092        self.assertEqual(t2, t2)
4093        # and > comparison should fail
4094        with self.assertRaises(TypeError):
4095            t1 > t2
4096
4097        # It's also naive if it has tzinfo but tzinfo.utcoffset() is None.
4098        class Naive(tzinfo):
4099            def utcoffset(self, dt): return None
4100        t2 = self.theclass(5, 6, 7, tzinfo=Naive())
4101        self.assertNotEqual(t1, t2)
4102        self.assertEqual(t2, t2)
4103
4104        # OTOH, it's OK to compare two of these mixing the two ways of being
4105        # naive.
4106        t1 = self.theclass(5, 6, 7)
4107        self.assertEqual(t1, t2)
4108
4109        # Try a bogus uctoffset.
4110        class Bogus(tzinfo):
4111            def utcoffset(self, dt):
4112                return timedelta(minutes=1440) # out of bounds
4113        t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
4114        t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
4115        self.assertRaises(ValueError, lambda: t1 == t2)
4116
4117    def test_pickling(self):
4118        # Try one without a tzinfo.
4119        args = 6, 7, 23, 20, 59, 1, 64**2
4120        orig = self.theclass(*args)
4121        for pickler, unpickler, proto in pickle_choices:
4122            green = pickler.dumps(orig, proto)
4123            derived = unpickler.loads(green)
4124            self.assertEqual(orig, derived)
4125        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
4126
4127        # Try one with a tzinfo.
4128        tinfo = PicklableFixedOffset(-300, 'cookie')
4129        orig = self.theclass(*args, **{'tzinfo': tinfo})
4130        derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0))
4131        for pickler, unpickler, proto in pickle_choices:
4132            green = pickler.dumps(orig, proto)
4133            derived = unpickler.loads(green)
4134            self.assertEqual(orig, derived)
4135            self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4136            self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4137            self.assertEqual(derived.tzname(), 'cookie')
4138        self.assertEqual(orig.__reduce__(), orig.__reduce_ex__(2))
4139
4140    def test_compat_unpickle(self):
4141        tests = [
4142            b'cdatetime\ndatetime\n'
4143            b"(S'\\x07\\xdf\\x0b\\x1b\\x14;\\x01\\x01\\xe2@'\n"
4144            b'ctest.datetimetester\nPicklableFixedOffset\n(tR'
4145            b"(dS'_FixedOffset__offset'\ncdatetime\ntimedelta\n"
4146            b'(I-1\nI68400\nI0\ntRs'
4147            b"S'_FixedOffset__dstoffset'\nNs"
4148            b"S'_FixedOffset__name'\nS'cookie'\nsbtR.",
4149
4150            b'cdatetime\ndatetime\n'
4151            b'(U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4152            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4153            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4154            b'(J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00tR'
4155            b'U\x17_FixedOffset__dstoffsetN'
4156            b'U\x12_FixedOffset__nameU\x06cookieubtR.',
4157
4158            b'\x80\x02cdatetime\ndatetime\n'
4159            b'U\n\x07\xdf\x0b\x1b\x14;\x01\x01\xe2@'
4160            b'ctest.datetimetester\nPicklableFixedOffset\n)R'
4161            b'}(U\x14_FixedOffset__offsetcdatetime\ntimedelta\n'
4162            b'J\xff\xff\xff\xffJ0\x0b\x01\x00K\x00\x87R'
4163            b'U\x17_FixedOffset__dstoffsetN'
4164            b'U\x12_FixedOffset__nameU\x06cookieub\x86R.',
4165        ]
4166        args = 2015, 11, 27, 20, 59, 1, 123456
4167        tinfo = PicklableFixedOffset(-300, 'cookie')
4168        expected = self.theclass(*args, **{'tzinfo': tinfo})
4169        for data in tests:
4170            for loads in pickle_loads:
4171                derived = loads(data, encoding='latin1')
4172                self.assertEqual(derived, expected)
4173                self.assertIsInstance(derived.tzinfo, PicklableFixedOffset)
4174                self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
4175                self.assertEqual(derived.tzname(), 'cookie')
4176
4177    def test_extreme_hashes(self):
4178        # If an attempt is made to hash these via subtracting the offset
4179        # then hashing a datetime object, OverflowError results.  The
4180        # Python implementation used to blow up here.
4181        t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, ""))
4182        hash(t)
4183        t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4184                          tzinfo=FixedOffset(-1439, ""))
4185        hash(t)
4186
4187        # OTOH, an OOB offset should blow up.
4188        t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, ""))
4189        self.assertRaises(ValueError, hash, t)
4190
4191    def test_zones(self):
4192        est = FixedOffset(-300, "EST")
4193        utc = FixedOffset(0, "UTC")
4194        met = FixedOffset(60, "MET")
4195        t1 = datetime(2002, 3, 19,  7, 47, tzinfo=est)
4196        t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc)
4197        t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met)
4198        self.assertEqual(t1.tzinfo, est)
4199        self.assertEqual(t2.tzinfo, utc)
4200        self.assertEqual(t3.tzinfo, met)
4201        self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
4202        self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
4203        self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
4204        self.assertEqual(t1.tzname(), "EST")
4205        self.assertEqual(t2.tzname(), "UTC")
4206        self.assertEqual(t3.tzname(), "MET")
4207        self.assertEqual(hash(t1), hash(t2))
4208        self.assertEqual(hash(t1), hash(t3))
4209        self.assertEqual(hash(t2), hash(t3))
4210        self.assertEqual(t1, t2)
4211        self.assertEqual(t1, t3)
4212        self.assertEqual(t2, t3)
4213        self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00")
4214        self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00")
4215        self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00")
4216        d = 'datetime.datetime(2002, 3, 19, '
4217        self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)")
4218        self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)")
4219        self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)")
4220
4221    def test_combine(self):
4222        met = FixedOffset(60, "MET")
4223        d = date(2002, 3, 4)
4224        tz = time(18, 45, 3, 1234, tzinfo=met)
4225        dt = datetime.combine(d, tz)
4226        self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234,
4227                                        tzinfo=met))
4228
4229    def test_extract(self):
4230        met = FixedOffset(60, "MET")
4231        dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met)
4232        self.assertEqual(dt.date(), date(2002, 3, 4))
4233        self.assertEqual(dt.time(), time(18, 45, 3, 1234))
4234        self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met))
4235
4236    def test_tz_aware_arithmetic(self):
4237        now = self.theclass.now()
4238        tz55 = FixedOffset(-330, "west 5:30")
4239        timeaware = now.time().replace(tzinfo=tz55)
4240        nowaware = self.theclass.combine(now.date(), timeaware)
4241        self.assertIs(nowaware.tzinfo, tz55)
4242        self.assertEqual(nowaware.timetz(), timeaware)
4243
4244        # Can't mix aware and non-aware.
4245        self.assertRaises(TypeError, lambda: now - nowaware)
4246        self.assertRaises(TypeError, lambda: nowaware - now)
4247
4248        # And adding datetime's doesn't make sense, aware or not.
4249        self.assertRaises(TypeError, lambda: now + nowaware)
4250        self.assertRaises(TypeError, lambda: nowaware + now)
4251        self.assertRaises(TypeError, lambda: nowaware + nowaware)
4252
4253        # Subtracting should yield 0.
4254        self.assertEqual(now - now, timedelta(0))
4255        self.assertEqual(nowaware - nowaware, timedelta(0))
4256
4257        # Adding a delta should preserve tzinfo.
4258        delta = timedelta(weeks=1, minutes=12, microseconds=5678)
4259        nowawareplus = nowaware + delta
4260        self.assertIs(nowaware.tzinfo, tz55)
4261        nowawareplus2 = delta + nowaware
4262        self.assertIs(nowawareplus2.tzinfo, tz55)
4263        self.assertEqual(nowawareplus, nowawareplus2)
4264
4265        # that - delta should be what we started with, and that - what we
4266        # started with should be delta.
4267        diff = nowawareplus - delta
4268        self.assertIs(diff.tzinfo, tz55)
4269        self.assertEqual(nowaware, diff)
4270        self.assertRaises(TypeError, lambda: delta - nowawareplus)
4271        self.assertEqual(nowawareplus - nowaware, delta)
4272
4273        # Make up a random timezone.
4274        tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone")
4275        # Attach it to nowawareplus.
4276        nowawareplus = nowawareplus.replace(tzinfo=tzr)
4277        self.assertIs(nowawareplus.tzinfo, tzr)
4278        # Make sure the difference takes the timezone adjustments into account.
4279        got = nowaware - nowawareplus
4280        # Expected:  (nowaware base - nowaware offset) -
4281        #            (nowawareplus base - nowawareplus offset) =
4282        #            (nowaware base - nowawareplus base) +
4283        #            (nowawareplus offset - nowaware offset) =
4284        #            -delta + nowawareplus offset - nowaware offset
4285        expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
4286        self.assertEqual(got, expected)
4287
4288        # Try max possible difference.
4289        min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min"))
4290        max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999,
4291                            tzinfo=FixedOffset(-1439, "max"))
4292        maxdiff = max - min
4293        self.assertEqual(maxdiff, self.theclass.max - self.theclass.min +
4294                                  timedelta(minutes=2*1439))
4295        # Different tzinfo, but the same offset
4296        tza = timezone(HOUR, 'A')
4297        tzb = timezone(HOUR, 'B')
4298        delta = min.replace(tzinfo=tza) - max.replace(tzinfo=tzb)
4299        self.assertEqual(delta, self.theclass.min - self.theclass.max)
4300
4301    def test_tzinfo_now(self):
4302        meth = self.theclass.now
4303        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4304        base = meth()
4305        # Try with and without naming the keyword.
4306        off42 = FixedOffset(42, "42")
4307        another = meth(off42)
4308        again = meth(tz=off42)
4309        self.assertIs(another.tzinfo, again.tzinfo)
4310        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4311        # Bad argument with and w/o naming the keyword.
4312        self.assertRaises(TypeError, meth, 16)
4313        self.assertRaises(TypeError, meth, tzinfo=16)
4314        # Bad keyword name.
4315        self.assertRaises(TypeError, meth, tinfo=off42)
4316        # Too many args.
4317        self.assertRaises(TypeError, meth, off42, off42)
4318
4319        # We don't know which time zone we're in, and don't have a tzinfo
4320        # class to represent it, so seeing whether a tz argument actually
4321        # does a conversion is tricky.
4322        utc = FixedOffset(0, "utc", 0)
4323        for weirdtz in [FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0),
4324                        timezone(timedelta(hours=15, minutes=58), "weirdtz"),]:
4325            for dummy in range(3):
4326                now = datetime.now(weirdtz)
4327                self.assertIs(now.tzinfo, weirdtz)
4328                utcnow = datetime.utcnow().replace(tzinfo=utc)
4329                now2 = utcnow.astimezone(weirdtz)
4330                if abs(now - now2) < timedelta(seconds=30):
4331                    break
4332                # Else the code is broken, or more than 30 seconds passed between
4333                # calls; assuming the latter, just try again.
4334            else:
4335                # Three strikes and we're out.
4336                self.fail("utcnow(), now(tz), or astimezone() may be broken")
4337
4338    def test_tzinfo_fromtimestamp(self):
4339        import time
4340        meth = self.theclass.fromtimestamp
4341        ts = time.time()
4342        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4343        base = meth(ts)
4344        # Try with and without naming the keyword.
4345        off42 = FixedOffset(42, "42")
4346        another = meth(ts, off42)
4347        again = meth(ts, tz=off42)
4348        self.assertIs(another.tzinfo, again.tzinfo)
4349        self.assertEqual(another.utcoffset(), timedelta(minutes=42))
4350        # Bad argument with and w/o naming the keyword.
4351        self.assertRaises(TypeError, meth, ts, 16)
4352        self.assertRaises(TypeError, meth, ts, tzinfo=16)
4353        # Bad keyword name.
4354        self.assertRaises(TypeError, meth, ts, tinfo=off42)
4355        # Too many args.
4356        self.assertRaises(TypeError, meth, ts, off42, off42)
4357        # Too few args.
4358        self.assertRaises(TypeError, meth)
4359
4360        # Try to make sure tz= actually does some conversion.
4361        timestamp = 1000000000
4362        utcdatetime = datetime.utcfromtimestamp(timestamp)
4363        # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take.
4364        # But on some flavor of Mac, it's nowhere near that.  So we can't have
4365        # any idea here what time that actually is, we can only test that
4366        # relative changes match.
4367        utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero
4368        tz = FixedOffset(utcoffset, "tz", 0)
4369        expected = utcdatetime + utcoffset
4370        got = datetime.fromtimestamp(timestamp, tz)
4371        self.assertEqual(expected, got.replace(tzinfo=None))
4372
4373    def test_tzinfo_utcnow(self):
4374        meth = self.theclass.utcnow
4375        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4376        base = meth()
4377        # Try with and without naming the keyword; for whatever reason,
4378        # utcnow() doesn't accept a tzinfo argument.
4379        off42 = FixedOffset(42, "42")
4380        self.assertRaises(TypeError, meth, off42)
4381        self.assertRaises(TypeError, meth, tzinfo=off42)
4382
4383    def test_tzinfo_utcfromtimestamp(self):
4384        import time
4385        meth = self.theclass.utcfromtimestamp
4386        ts = time.time()
4387        # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up).
4388        base = meth(ts)
4389        # Try with and without naming the keyword; for whatever reason,
4390        # utcfromtimestamp() doesn't accept a tzinfo argument.
4391        off42 = FixedOffset(42, "42")
4392        self.assertRaises(TypeError, meth, ts, off42)
4393        self.assertRaises(TypeError, meth, ts, tzinfo=off42)
4394
4395    def test_tzinfo_timetuple(self):
4396        # TestDateTime tested most of this.  datetime adds a twist to the
4397        # DST flag.
4398        class DST(tzinfo):
4399            def __init__(self, dstvalue):
4400                if isinstance(dstvalue, int):
4401                    dstvalue = timedelta(minutes=dstvalue)
4402                self.dstvalue = dstvalue
4403            def dst(self, dt):
4404                return self.dstvalue
4405
4406        cls = self.theclass
4407        for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1):
4408            d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue))
4409            t = d.timetuple()
4410            self.assertEqual(1, t.tm_year)
4411            self.assertEqual(1, t.tm_mon)
4412            self.assertEqual(1, t.tm_mday)
4413            self.assertEqual(10, t.tm_hour)
4414            self.assertEqual(20, t.tm_min)
4415            self.assertEqual(30, t.tm_sec)
4416            self.assertEqual(0, t.tm_wday)
4417            self.assertEqual(1, t.tm_yday)
4418            self.assertEqual(flag, t.tm_isdst)
4419
4420        # dst() returns wrong type.
4421        self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple)
4422
4423        # dst() at the edge.
4424        self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1)
4425        self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1)
4426
4427        # dst() out of range.
4428        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple)
4429        self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple)
4430
4431    def test_utctimetuple(self):
4432        class DST(tzinfo):
4433            def __init__(self, dstvalue=0):
4434                if isinstance(dstvalue, int):
4435                    dstvalue = timedelta(minutes=dstvalue)
4436                self.dstvalue = dstvalue
4437            def dst(self, dt):
4438                return self.dstvalue
4439
4440        cls = self.theclass
4441        # This can't work:  DST didn't implement utcoffset.
4442        self.assertRaises(NotImplementedError,
4443                          cls(1, 1, 1, tzinfo=DST(0)).utcoffset)
4444
4445        class UOFS(DST):
4446            def __init__(self, uofs, dofs=None):
4447                DST.__init__(self, dofs)
4448                self.uofs = timedelta(minutes=uofs)
4449            def utcoffset(self, dt):
4450                return self.uofs
4451
4452        for dstvalue in -33, 33, 0, None:
4453            d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
4454            t = d.utctimetuple()
4455            self.assertEqual(d.year, t.tm_year)
4456            self.assertEqual(d.month, t.tm_mon)
4457            self.assertEqual(d.day, t.tm_mday)
4458            self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm
4459            self.assertEqual(13, t.tm_min)
4460            self.assertEqual(d.second, t.tm_sec)
4461            self.assertEqual(d.weekday(), t.tm_wday)
4462            self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
4463                             t.tm_yday)
4464            # Ensure tm_isdst is 0 regardless of what dst() says: DST
4465            # is never in effect for a UTC time.
4466            self.assertEqual(0, t.tm_isdst)
4467
4468        # For naive datetime, utctimetuple == timetuple except for isdst
4469        d = cls(1, 2, 3, 10, 20, 30, 40)
4470        t = d.utctimetuple()
4471        self.assertEqual(t[:-1], d.timetuple()[:-1])
4472        self.assertEqual(0, t.tm_isdst)
4473        # Same if utcoffset is None
4474        class NOFS(DST):
4475            def utcoffset(self, dt):
4476                return None
4477        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=NOFS())
4478        t = d.utctimetuple()
4479        self.assertEqual(t[:-1], d.timetuple()[:-1])
4480        self.assertEqual(0, t.tm_isdst)
4481        # Check that bad tzinfo is detected
4482        class BOFS(DST):
4483            def utcoffset(self, dt):
4484                return "EST"
4485        d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=BOFS())
4486        self.assertRaises(TypeError, d.utctimetuple)
4487
4488        # Check that utctimetuple() is the same as
4489        # astimezone(utc).timetuple()
4490        d = cls(2010, 11, 13, 14, 15, 16, 171819)
4491        for tz in [timezone.min, timezone.utc, timezone.max]:
4492            dtz = d.replace(tzinfo=tz)
4493            self.assertEqual(dtz.utctimetuple()[:-1],
4494                             dtz.astimezone(timezone.utc).timetuple()[:-1])
4495        # At the edges, UTC adjustment can produce years out-of-range
4496        # for a datetime object.  Ensure that an OverflowError is
4497        # raised.
4498        tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
4499        # That goes back 1 minute less than a full day.
4500        self.assertRaises(OverflowError, tiny.utctimetuple)
4501
4502        huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
4503        # That goes forward 1 minute less than a full day.
4504        self.assertRaises(OverflowError, huge.utctimetuple)
4505        # More overflow cases
4506        tiny = cls.min.replace(tzinfo=timezone(MINUTE))
4507        self.assertRaises(OverflowError, tiny.utctimetuple)
4508        huge = cls.max.replace(tzinfo=timezone(-MINUTE))
4509        self.assertRaises(OverflowError, huge.utctimetuple)
4510
4511    def test_tzinfo_isoformat(self):
4512        zero = FixedOffset(0, "+00:00")
4513        plus = FixedOffset(220, "+03:40")
4514        minus = FixedOffset(-231, "-03:51")
4515        unknown = FixedOffset(None, "")
4516
4517        cls = self.theclass
4518        datestr = '0001-02-03'
4519        for ofs in None, zero, plus, minus, unknown:
4520            for us in 0, 987001:
4521                d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs)
4522                timestr = '04:05:59' + (us and '.987001' or '')
4523                ofsstr = ofs is not None and d.tzname() or ''
4524                tailstr = timestr + ofsstr
4525                iso = d.isoformat()
4526                self.assertEqual(iso, datestr + 'T' + tailstr)
4527                self.assertEqual(iso, d.isoformat('T'))
4528                self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr)
4529                self.assertEqual(d.isoformat('\u1234'), datestr + '\u1234' + tailstr)
4530                self.assertEqual(str(d), datestr + ' ' + tailstr)
4531
4532    def test_replace(self):
4533        cls = self.theclass
4534        z100 = FixedOffset(100, "+100")
4535        zm200 = FixedOffset(timedelta(minutes=-200), "-200")
4536        args = [1, 2, 3, 4, 5, 6, 7, z100]
4537        base = cls(*args)
4538        self.assertEqual(base, base.replace())
4539
4540        i = 0
4541        for name, newval in (("year", 2),
4542                             ("month", 3),
4543                             ("day", 4),
4544                             ("hour", 5),
4545                             ("minute", 6),
4546                             ("second", 7),
4547                             ("microsecond", 8),
4548                             ("tzinfo", zm200)):
4549            newargs = args[:]
4550            newargs[i] = newval
4551            expected = cls(*newargs)
4552            got = base.replace(**{name: newval})
4553            self.assertEqual(expected, got)
4554            i += 1
4555
4556        # Ensure we can get rid of a tzinfo.
4557        self.assertEqual(base.tzname(), "+100")
4558        base2 = base.replace(tzinfo=None)
4559        self.assertIsNone(base2.tzinfo)
4560        self.assertIsNone(base2.tzname())
4561
4562        # Ensure we can add one.
4563        base3 = base2.replace(tzinfo=z100)
4564        self.assertEqual(base, base3)
4565        self.assertIs(base.tzinfo, base3.tzinfo)
4566
4567        # Out of bounds.
4568        base = cls(2000, 2, 29)
4569        self.assertRaises(ValueError, base.replace, year=2001)
4570
4571    def test_more_astimezone(self):
4572        # The inherited test_astimezone covered some trivial and error cases.
4573        fnone = FixedOffset(None, "None")
4574        f44m = FixedOffset(44, "44")
4575        fm5h = FixedOffset(-timedelta(hours=5), "m300")
4576
4577        dt = self.theclass.now(tz=f44m)
4578        self.assertIs(dt.tzinfo, f44m)
4579        # Replacing with degenerate tzinfo raises an exception.
4580        self.assertRaises(ValueError, dt.astimezone, fnone)
4581        # Replacing with same tzinfo makes no change.
4582        x = dt.astimezone(dt.tzinfo)
4583        self.assertIs(x.tzinfo, f44m)
4584        self.assertEqual(x.date(), dt.date())
4585        self.assertEqual(x.time(), dt.time())
4586
4587        # Replacing with different tzinfo does adjust.
4588        got = dt.astimezone(fm5h)
4589        self.assertIs(got.tzinfo, fm5h)
4590        self.assertEqual(got.utcoffset(), timedelta(hours=-5))
4591        expected = dt - dt.utcoffset()  # in effect, convert to UTC
4592        expected += fm5h.utcoffset(dt)  # and from there to local time
4593        expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo
4594        self.assertEqual(got.date(), expected.date())
4595        self.assertEqual(got.time(), expected.time())
4596        self.assertEqual(got.timetz(), expected.timetz())
4597        self.assertIs(got.tzinfo, expected.tzinfo)
4598        self.assertEqual(got, expected)
4599
4600    @support.run_with_tz('UTC')
4601    def test_astimezone_default_utc(self):
4602        dt = self.theclass.now(timezone.utc)
4603        self.assertEqual(dt.astimezone(None), dt)
4604        self.assertEqual(dt.astimezone(), dt)
4605
4606    # Note that offset in TZ variable has the opposite sign to that
4607    # produced by %z directive.
4608    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4609    def test_astimezone_default_eastern(self):
4610        dt = self.theclass(2012, 11, 4, 6, 30, tzinfo=timezone.utc)
4611        local = dt.astimezone()
4612        self.assertEqual(dt, local)
4613        self.assertEqual(local.strftime("%z %Z"), "-0500 EST")
4614        dt = self.theclass(2012, 11, 4, 5, 30, tzinfo=timezone.utc)
4615        local = dt.astimezone()
4616        self.assertEqual(dt, local)
4617        self.assertEqual(local.strftime("%z %Z"), "-0400 EDT")
4618
4619    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
4620    def test_astimezone_default_near_fold(self):
4621        # Issue #26616.
4622        u = datetime(2015, 11, 1, 5, tzinfo=timezone.utc)
4623        t = u.astimezone()
4624        s = t.astimezone()
4625        self.assertEqual(t.tzinfo, s.tzinfo)
4626
4627    def test_aware_subtract(self):
4628        cls = self.theclass
4629
4630        # Ensure that utcoffset() is ignored when the operands have the
4631        # same tzinfo member.
4632        class OperandDependentOffset(tzinfo):
4633            def utcoffset(self, t):
4634                if t.minute < 10:
4635                    # d0 and d1 equal after adjustment
4636                    return timedelta(minutes=t.minute)
4637                else:
4638                    # d2 off in the weeds
4639                    return timedelta(minutes=59)
4640
4641        base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
4642        d0 = base.replace(minute=3)
4643        d1 = base.replace(minute=9)
4644        d2 = base.replace(minute=11)
4645        for x in d0, d1, d2:
4646            for y in d0, d1, d2:
4647                got = x - y
4648                expected = timedelta(minutes=x.minute - y.minute)
4649                self.assertEqual(got, expected)
4650
4651        # OTOH, if the tzinfo members are distinct, utcoffsets aren't
4652        # ignored.
4653        base = cls(8, 9, 10, 11, 12, 13, 14)
4654        d0 = base.replace(minute=3, tzinfo=OperandDependentOffset())
4655        d1 = base.replace(minute=9, tzinfo=OperandDependentOffset())
4656        d2 = base.replace(minute=11, tzinfo=OperandDependentOffset())
4657        for x in d0, d1, d2:
4658            for y in d0, d1, d2:
4659                got = x - y
4660                if (x is d0 or x is d1) and (y is d0 or y is d1):
4661                    expected = timedelta(0)
4662                elif x is y is d2:
4663                    expected = timedelta(0)
4664                elif x is d2:
4665                    expected = timedelta(minutes=(11-59)-0)
4666                else:
4667                    assert y is d2
4668                    expected = timedelta(minutes=0-(11-59))
4669                self.assertEqual(got, expected)
4670
4671    def test_mixed_compare(self):
4672        t1 = datetime(1, 2, 3, 4, 5, 6, 7)
4673        t2 = datetime(1, 2, 3, 4, 5, 6, 7)
4674        self.assertEqual(t1, t2)
4675        t2 = t2.replace(tzinfo=None)
4676        self.assertEqual(t1, t2)
4677        t2 = t2.replace(tzinfo=FixedOffset(None, ""))
4678        self.assertEqual(t1, t2)
4679        t2 = t2.replace(tzinfo=FixedOffset(0, ""))
4680        self.assertNotEqual(t1, t2)
4681
4682        # In datetime w/ identical tzinfo objects, utcoffset is ignored.
4683        class Varies(tzinfo):
4684            def __init__(self):
4685                self.offset = timedelta(minutes=22)
4686            def utcoffset(self, t):
4687                self.offset += timedelta(minutes=1)
4688                return self.offset
4689
4690        v = Varies()
4691        t1 = t2.replace(tzinfo=v)
4692        t2 = t2.replace(tzinfo=v)
4693        self.assertEqual(t1.utcoffset(), timedelta(minutes=23))
4694        self.assertEqual(t2.utcoffset(), timedelta(minutes=24))
4695        self.assertEqual(t1, t2)
4696
4697        # But if they're not identical, it isn't ignored.
4698        t2 = t2.replace(tzinfo=Varies())
4699        self.assertTrue(t1 < t2)  # t1's offset counter still going up
4700
4701    def test_subclass_datetimetz(self):
4702
4703        class C(self.theclass):
4704            theAnswer = 42
4705
4706            def __new__(cls, *args, **kws):
4707                temp = kws.copy()
4708                extra = temp.pop('extra')
4709                result = self.theclass.__new__(cls, *args, **temp)
4710                result.extra = extra
4711                return result
4712
4713            def newmeth(self, start):
4714                return start + self.hour + self.year
4715
4716        args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1)
4717
4718        dt1 = self.theclass(*args)
4719        dt2 = C(*args, **{'extra': 7})
4720
4721        self.assertEqual(dt2.__class__, C)
4722        self.assertEqual(dt2.theAnswer, 42)
4723        self.assertEqual(dt2.extra, 7)
4724        self.assertEqual(dt1.utcoffset(), dt2.utcoffset())
4725        self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7)
4726
4727# Pain to set up DST-aware tzinfo classes.
4728
4729def first_sunday_on_or_after(dt):
4730    days_to_go = 6 - dt.weekday()
4731    if days_to_go:
4732        dt += timedelta(days_to_go)
4733    return dt
4734
4735ZERO = timedelta(0)
4736MINUTE = timedelta(minutes=1)
4737HOUR = timedelta(hours=1)
4738DAY = timedelta(days=1)
4739# In the US, DST starts at 2am (standard time) on the first Sunday in April.
4740DSTSTART = datetime(1, 4, 1, 2)
4741# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct,
4742# which is the first Sunday on or after Oct 25.  Because we view 1:MM as
4743# being standard time on that day, there is no spelling in local time of
4744# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time).
4745DSTEND = datetime(1, 10, 25, 1)
4746
4747class USTimeZone(tzinfo):
4748
4749    def __init__(self, hours, reprname, stdname, dstname):
4750        self.stdoffset = timedelta(hours=hours)
4751        self.reprname = reprname
4752        self.stdname = stdname
4753        self.dstname = dstname
4754
4755    def __repr__(self):
4756        return self.reprname
4757
4758    def tzname(self, dt):
4759        if self.dst(dt):
4760            return self.dstname
4761        else:
4762            return self.stdname
4763
4764    def utcoffset(self, dt):
4765        return self.stdoffset + self.dst(dt)
4766
4767    def dst(self, dt):
4768        if dt is None or dt.tzinfo is None:
4769            # An exception instead may be sensible here, in one or more of
4770            # the cases.
4771            return ZERO
4772        assert dt.tzinfo is self
4773
4774        # Find first Sunday in April.
4775        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
4776        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
4777
4778        # Find last Sunday in October.
4779        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
4780        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
4781
4782        # Can't compare naive to aware objects, so strip the timezone from
4783        # dt first.
4784        if start <= dt.replace(tzinfo=None) < end:
4785            return HOUR
4786        else:
4787            return ZERO
4788
4789Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
4790Central  = USTimeZone(-6, "Central",  "CST", "CDT")
4791Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
4792Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
4793utc_real = FixedOffset(0, "UTC", 0)
4794# For better test coverage, we want another flavor of UTC that's west of
4795# the Eastern and Pacific timezones.
4796utc_fake = FixedOffset(-12*60, "UTCfake", 0)
4797
4798class TestTimezoneConversions(unittest.TestCase):
4799    # The DST switch times for 2002, in std time.
4800    dston = datetime(2002, 4, 7, 2)
4801    dstoff = datetime(2002, 10, 27, 1)
4802
4803    theclass = datetime
4804
4805    # Check a time that's inside DST.
4806    def checkinside(self, dt, tz, utc, dston, dstoff):
4807        self.assertEqual(dt.dst(), HOUR)
4808
4809        # Conversion to our own timezone is always an identity.
4810        self.assertEqual(dt.astimezone(tz), dt)
4811
4812        asutc = dt.astimezone(utc)
4813        there_and_back = asutc.astimezone(tz)
4814
4815        # Conversion to UTC and back isn't always an identity here,
4816        # because there are redundant spellings (in local time) of
4817        # UTC time when DST begins:  the clock jumps from 1:59:59
4818        # to 3:00:00, and a local time of 2:MM:SS doesn't really
4819        # make sense then.  The classes above treat 2:MM:SS as
4820        # daylight time then (it's "after 2am"), really an alias
4821        # for 1:MM:SS standard time.  The latter form is what
4822        # conversion back from UTC produces.
4823        if dt.date() == dston.date() and dt.hour == 2:
4824            # We're in the redundant hour, and coming back from
4825            # UTC gives the 1:MM:SS standard-time spelling.
4826            self.assertEqual(there_and_back + HOUR, dt)
4827            # Although during was considered to be in daylight
4828            # time, there_and_back is not.
4829            self.assertEqual(there_and_back.dst(), ZERO)
4830            # They're the same times in UTC.
4831            self.assertEqual(there_and_back.astimezone(utc),
4832                             dt.astimezone(utc))
4833        else:
4834            # We're not in the redundant hour.
4835            self.assertEqual(dt, there_and_back)
4836
4837        # Because we have a redundant spelling when DST begins, there is
4838        # (unfortunately) an hour when DST ends that can't be spelled at all in
4839        # local time.  When DST ends, the clock jumps from 1:59 back to 1:00
4840        # again.  The hour 1:MM DST has no spelling then:  1:MM is taken to be
4841        # standard time.  1:MM DST == 0:MM EST, but 0:MM is taken to be
4842        # daylight time.  The hour 1:MM daylight == 0:MM standard can't be
4843        # expressed in local time.  Nevertheless, we want conversion back
4844        # from UTC to mimic the local clock's "repeat an hour" behavior.
4845        nexthour_utc = asutc + HOUR
4846        nexthour_tz = nexthour_utc.astimezone(tz)
4847        if dt.date() == dstoff.date() and dt.hour == 0:
4848            # We're in the hour before the last DST hour.  The last DST hour
4849            # is ineffable.  We want the conversion back to repeat 1:MM.
4850            self.assertEqual(nexthour_tz, dt.replace(hour=1))
4851            nexthour_utc += HOUR
4852            nexthour_tz = nexthour_utc.astimezone(tz)
4853            self.assertEqual(nexthour_tz, dt.replace(hour=1))
4854        else:
4855            self.assertEqual(nexthour_tz - dt, HOUR)
4856
4857    # Check a time that's outside DST.
4858    def checkoutside(self, dt, tz, utc):
4859        self.assertEqual(dt.dst(), ZERO)
4860
4861        # Conversion to our own timezone is always an identity.
4862        self.assertEqual(dt.astimezone(tz), dt)
4863
4864        # Converting to UTC and back is an identity too.
4865        asutc = dt.astimezone(utc)
4866        there_and_back = asutc.astimezone(tz)
4867        self.assertEqual(dt, there_and_back)
4868
4869    def convert_between_tz_and_utc(self, tz, utc):
4870        dston = self.dston.replace(tzinfo=tz)
4871        # Because 1:MM on the day DST ends is taken as being standard time,
4872        # there is no spelling in tz for the last hour of daylight time.
4873        # For purposes of the test, the last hour of DST is 0:MM, which is
4874        # taken as being daylight time (and 1:MM is taken as being standard
4875        # time).
4876        dstoff = self.dstoff.replace(tzinfo=tz)
4877        for delta in (timedelta(weeks=13),
4878                      DAY,
4879                      HOUR,
4880                      timedelta(minutes=1),
4881                      timedelta(microseconds=1)):
4882
4883            self.checkinside(dston, tz, utc, dston, dstoff)
4884            for during in dston + delta, dstoff - delta:
4885                self.checkinside(during, tz, utc, dston, dstoff)
4886
4887            self.checkoutside(dstoff, tz, utc)
4888            for outside in dston - delta, dstoff + delta:
4889                self.checkoutside(outside, tz, utc)
4890
4891    def test_easy(self):
4892        # Despite the name of this test, the endcases are excruciating.
4893        self.convert_between_tz_and_utc(Eastern, utc_real)
4894        self.convert_between_tz_and_utc(Pacific, utc_real)
4895        self.convert_between_tz_and_utc(Eastern, utc_fake)
4896        self.convert_between_tz_and_utc(Pacific, utc_fake)
4897        # The next is really dancing near the edge.  It works because
4898        # Pacific and Eastern are far enough apart that their "problem
4899        # hours" don't overlap.
4900        self.convert_between_tz_and_utc(Eastern, Pacific)
4901        self.convert_between_tz_and_utc(Pacific, Eastern)
4902        # OTOH, these fail!  Don't enable them.  The difficulty is that
4903        # the edge case tests assume that every hour is representable in
4904        # the "utc" class.  This is always true for a fixed-offset tzinfo
4905        # class (lke utc_real and utc_fake), but not for Eastern or Central.
4906        # For these adjacent DST-aware time zones, the range of time offsets
4907        # tested ends up creating hours in the one that aren't representable
4908        # in the other.  For the same reason, we would see failures in the
4909        # Eastern vs Pacific tests too if we added 3*HOUR to the list of
4910        # offset deltas in convert_between_tz_and_utc().
4911        #
4912        # self.convert_between_tz_and_utc(Eastern, Central)  # can't work
4913        # self.convert_between_tz_and_utc(Central, Eastern)  # can't work
4914
4915    def test_tricky(self):
4916        # 22:00 on day before daylight starts.
4917        fourback = self.dston - timedelta(hours=4)
4918        ninewest = FixedOffset(-9*60, "-0900", 0)
4919        fourback = fourback.replace(tzinfo=ninewest)
4920        # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST.  Since it's "after
4921        # 2", we should get the 3 spelling.
4922        # If we plug 22:00 the day before into Eastern, it "looks like std
4923        # time", so its offset is returned as -5, and -5 - -9 = 4.  Adding 4
4924        # to 22:00 lands on 2:00, which makes no sense in local time (the
4925        # local clock jumps from 1 to 3).  The point here is to make sure we
4926        # get the 3 spelling.
4927        expected = self.dston.replace(hour=3)
4928        got = fourback.astimezone(Eastern).replace(tzinfo=None)
4929        self.assertEqual(expected, got)
4930
4931        # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST.  In that
4932        # case we want the 1:00 spelling.
4933        sixutc = self.dston.replace(hour=6, tzinfo=utc_real)
4934        # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4,
4935        # and adding -4-0 == -4 gives the 2:00 spelling.  We want the 1:00 EST
4936        # spelling.
4937        expected = self.dston.replace(hour=1)
4938        got = sixutc.astimezone(Eastern).replace(tzinfo=None)
4939        self.assertEqual(expected, got)
4940
4941        # Now on the day DST ends, we want "repeat an hour" behavior.
4942        #  UTC  4:MM  5:MM  6:MM  7:MM  checking these
4943        #  EST 23:MM  0:MM  1:MM  2:MM
4944        #  EDT  0:MM  1:MM  2:MM  3:MM
4945        # wall  0:MM  1:MM  1:MM  2:MM  against these
4946        for utc in utc_real, utc_fake:
4947            for tz in Eastern, Pacific:
4948                first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM
4949                # Convert that to UTC.
4950                first_std_hour -= tz.utcoffset(None)
4951                # Adjust for possibly fake UTC.
4952                asutc = first_std_hour + utc.utcoffset(None)
4953                # First UTC hour to convert; this is 4:00 when utc=utc_real &
4954                # tz=Eastern.
4955                asutcbase = asutc.replace(tzinfo=utc)
4956                for tzhour in (0, 1, 1, 2):
4957                    expectedbase = self.dstoff.replace(hour=tzhour)
4958                    for minute in 0, 30, 59:
4959                        expected = expectedbase.replace(minute=minute)
4960                        asutc = asutcbase.replace(minute=minute)
4961                        astz = asutc.astimezone(tz)
4962                        self.assertEqual(astz.replace(tzinfo=None), expected)
4963                    asutcbase += HOUR
4964
4965
4966    def test_bogus_dst(self):
4967        class ok(tzinfo):
4968            def utcoffset(self, dt): return HOUR
4969            def dst(self, dt): return HOUR
4970
4971        now = self.theclass.now().replace(tzinfo=utc_real)
4972        # Doesn't blow up.
4973        now.astimezone(ok())
4974
4975        # Does blow up.
4976        class notok(ok):
4977            def dst(self, dt): return None
4978        self.assertRaises(ValueError, now.astimezone, notok())
4979
4980        # Sometimes blow up. In the following, tzinfo.dst()
4981        # implementation may return None or not None depending on
4982        # whether DST is assumed to be in effect.  In this situation,
4983        # a ValueError should be raised by astimezone().
4984        class tricky_notok(ok):
4985            def dst(self, dt):
4986                if dt.year == 2000:
4987                    return None
4988                else:
4989                    return 10*HOUR
4990        dt = self.theclass(2001, 1, 1).replace(tzinfo=utc_real)
4991        self.assertRaises(ValueError, dt.astimezone, tricky_notok())
4992
4993    def test_fromutc(self):
4994        self.assertRaises(TypeError, Eastern.fromutc)   # not enough args
4995        now = datetime.utcnow().replace(tzinfo=utc_real)
4996        self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo
4997        now = now.replace(tzinfo=Eastern)   # insert correct tzinfo
4998        enow = Eastern.fromutc(now)         # doesn't blow up
4999        self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member
5000        self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args
5001        self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type
5002
5003        # Always converts UTC to standard time.
5004        class FauxUSTimeZone(USTimeZone):
5005            def fromutc(self, dt):
5006                return dt + self.stdoffset
5007        FEastern  = FauxUSTimeZone(-5, "FEastern",  "FEST", "FEDT")
5008
5009        #  UTC  4:MM  5:MM  6:MM  7:MM  8:MM  9:MM
5010        #  EST 23:MM  0:MM  1:MM  2:MM  3:MM  4:MM
5011        #  EDT  0:MM  1:MM  2:MM  3:MM  4:MM  5:MM
5012
5013        # Check around DST start.
5014        start = self.dston.replace(hour=4, tzinfo=Eastern)
5015        fstart = start.replace(tzinfo=FEastern)
5016        for wall in 23, 0, 1, 3, 4, 5:
5017            expected = start.replace(hour=wall)
5018            if wall == 23:
5019                expected -= timedelta(days=1)
5020            got = Eastern.fromutc(start)
5021            self.assertEqual(expected, got)
5022
5023            expected = fstart + FEastern.stdoffset
5024            got = FEastern.fromutc(fstart)
5025            self.assertEqual(expected, got)
5026
5027            # Ensure astimezone() calls fromutc() too.
5028            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5029            self.assertEqual(expected, got)
5030
5031            start += HOUR
5032            fstart += HOUR
5033
5034        # Check around DST end.
5035        start = self.dstoff.replace(hour=4, tzinfo=Eastern)
5036        fstart = start.replace(tzinfo=FEastern)
5037        for wall in 0, 1, 1, 2, 3, 4:
5038            expected = start.replace(hour=wall)
5039            got = Eastern.fromutc(start)
5040            self.assertEqual(expected, got)
5041
5042            expected = fstart + FEastern.stdoffset
5043            got = FEastern.fromutc(fstart)
5044            self.assertEqual(expected, got)
5045
5046            # Ensure astimezone() calls fromutc() too.
5047            got = fstart.replace(tzinfo=utc_real).astimezone(FEastern)
5048            self.assertEqual(expected, got)
5049
5050            start += HOUR
5051            fstart += HOUR
5052
5053
5054#############################################################################
5055# oddballs
5056
5057class Oddballs(unittest.TestCase):
5058
5059    def test_bug_1028306(self):
5060        # Trying to compare a date to a datetime should act like a mixed-
5061        # type comparison, despite that datetime is a subclass of date.
5062        as_date = date.today()
5063        as_datetime = datetime.combine(as_date, time())
5064        self.assertTrue(as_date != as_datetime)
5065        self.assertTrue(as_datetime != as_date)
5066        self.assertFalse(as_date == as_datetime)
5067        self.assertFalse(as_datetime == as_date)
5068        self.assertRaises(TypeError, lambda: as_date < as_datetime)
5069        self.assertRaises(TypeError, lambda: as_datetime < as_date)
5070        self.assertRaises(TypeError, lambda: as_date <= as_datetime)
5071        self.assertRaises(TypeError, lambda: as_datetime <= as_date)
5072        self.assertRaises(TypeError, lambda: as_date > as_datetime)
5073        self.assertRaises(TypeError, lambda: as_datetime > as_date)
5074        self.assertRaises(TypeError, lambda: as_date >= as_datetime)
5075        self.assertRaises(TypeError, lambda: as_datetime >= as_date)
5076
5077        # Nevertheless, comparison should work with the base-class (date)
5078        # projection if use of a date method is forced.
5079        self.assertEqual(as_date.__eq__(as_datetime), True)
5080        different_day = (as_date.day + 1) % 20 + 1
5081        as_different = as_datetime.replace(day= different_day)
5082        self.assertEqual(as_date.__eq__(as_different), False)
5083
5084        # And date should compare with other subclasses of date.  If a
5085        # subclass wants to stop this, it's up to the subclass to do so.
5086        date_sc = SubclassDate(as_date.year, as_date.month, as_date.day)
5087        self.assertEqual(as_date, date_sc)
5088        self.assertEqual(date_sc, as_date)
5089
5090        # Ditto for datetimes.
5091        datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month,
5092                                       as_date.day, 0, 0, 0)
5093        self.assertEqual(as_datetime, datetime_sc)
5094        self.assertEqual(datetime_sc, as_datetime)
5095
5096    def test_extra_attributes(self):
5097        for x in [date.today(),
5098                  time(),
5099                  datetime.utcnow(),
5100                  timedelta(),
5101                  tzinfo(),
5102                  timezone(timedelta())]:
5103            with self.assertRaises(AttributeError):
5104                x.abc = 1
5105
5106    def test_check_arg_types(self):
5107        class Number:
5108            def __init__(self, value):
5109                self.value = value
5110            def __int__(self):
5111                return self.value
5112
5113        for xx in [decimal.Decimal(10),
5114                   decimal.Decimal('10.9'),
5115                   Number(10)]:
5116            with self.assertWarns(DeprecationWarning):
5117                self.assertEqual(datetime(10, 10, 10, 10, 10, 10, 10),
5118                                 datetime(xx, xx, xx, xx, xx, xx, xx))
5119
5120        with self.assertRaisesRegex(TypeError, '^an integer is required '
5121                                              r'\(got type str\)$'):
5122            datetime(10, 10, '10')
5123
5124        f10 = Number(10.9)
5125        with self.assertRaisesRegex(TypeError, '^__int__ returned non-int '
5126                                               r'\(type float\)$'):
5127            datetime(10, 10, f10)
5128
5129        class Float(float):
5130            pass
5131        s10 = Float(10.9)
5132        with self.assertRaisesRegex(TypeError, '^integer argument expected, '
5133                                               'got float$'):
5134            datetime(10, 10, s10)
5135
5136        with self.assertRaises(TypeError):
5137            datetime(10., 10, 10)
5138        with self.assertRaises(TypeError):
5139            datetime(10, 10., 10)
5140        with self.assertRaises(TypeError):
5141            datetime(10, 10, 10.)
5142        with self.assertRaises(TypeError):
5143            datetime(10, 10, 10, 10.)
5144        with self.assertRaises(TypeError):
5145            datetime(10, 10, 10, 10, 10.)
5146        with self.assertRaises(TypeError):
5147            datetime(10, 10, 10, 10, 10, 10.)
5148        with self.assertRaises(TypeError):
5149            datetime(10, 10, 10, 10, 10, 10, 10.)
5150
5151#############################################################################
5152# Local Time Disambiguation
5153
5154# An experimental reimplementation of fromutc that respects the "fold" flag.
5155
5156class tzinfo2(tzinfo):
5157
5158    def fromutc(self, dt):
5159        "datetime in UTC -> datetime in local time."
5160
5161        if not isinstance(dt, datetime):
5162            raise TypeError("fromutc() requires a datetime argument")
5163        if dt.tzinfo is not self:
5164            raise ValueError("dt.tzinfo is not self")
5165        # Returned value satisfies
5166        #          dt + ldt.utcoffset() = ldt
5167        off0 = dt.replace(fold=0).utcoffset()
5168        off1 = dt.replace(fold=1).utcoffset()
5169        if off0 is None or off1 is None or dt.dst() is None:
5170            raise ValueError
5171        if off0 == off1:
5172            ldt = dt + off0
5173            off1 = ldt.utcoffset()
5174            if off0 == off1:
5175                return ldt
5176        # Now, we discovered both possible offsets, so
5177        # we can just try four possible solutions:
5178        for off in [off0, off1]:
5179            ldt = dt + off
5180            if ldt.utcoffset() == off:
5181                return ldt
5182            ldt = ldt.replace(fold=1)
5183            if ldt.utcoffset() == off:
5184                return ldt
5185
5186        raise ValueError("No suitable local time found")
5187
5188# Reimplementing simplified US timezones to respect the "fold" flag:
5189
5190class USTimeZone2(tzinfo2):
5191
5192    def __init__(self, hours, reprname, stdname, dstname):
5193        self.stdoffset = timedelta(hours=hours)
5194        self.reprname = reprname
5195        self.stdname = stdname
5196        self.dstname = dstname
5197
5198    def __repr__(self):
5199        return self.reprname
5200
5201    def tzname(self, dt):
5202        if self.dst(dt):
5203            return self.dstname
5204        else:
5205            return self.stdname
5206
5207    def utcoffset(self, dt):
5208        return self.stdoffset + self.dst(dt)
5209
5210    def dst(self, dt):
5211        if dt is None or dt.tzinfo is None:
5212            # An exception instead may be sensible here, in one or more of
5213            # the cases.
5214            return ZERO
5215        assert dt.tzinfo is self
5216
5217        # Find first Sunday in April.
5218        start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
5219        assert start.weekday() == 6 and start.month == 4 and start.day <= 7
5220
5221        # Find last Sunday in October.
5222        end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
5223        assert end.weekday() == 6 and end.month == 10 and end.day >= 25
5224
5225        # Can't compare naive to aware objects, so strip the timezone from
5226        # dt first.
5227        dt = dt.replace(tzinfo=None)
5228        if start + HOUR <= dt < end:
5229            # DST is in effect.
5230            return HOUR
5231        elif end <= dt < end + HOUR:
5232            # Fold (an ambiguous hour): use dt.fold to disambiguate.
5233            return ZERO if dt.fold else HOUR
5234        elif start <= dt < start + HOUR:
5235            # Gap (a non-existent hour): reverse the fold rule.
5236            return HOUR if dt.fold else ZERO
5237        else:
5238            # DST is off.
5239            return ZERO
5240
5241Eastern2  = USTimeZone2(-5, "Eastern2",  "EST", "EDT")
5242Central2  = USTimeZone2(-6, "Central2",  "CST", "CDT")
5243Mountain2 = USTimeZone2(-7, "Mountain2", "MST", "MDT")
5244Pacific2  = USTimeZone2(-8, "Pacific2",  "PST", "PDT")
5245
5246# Europe_Vilnius_1941 tzinfo implementation reproduces the following
5247# 1941 transition from Olson's tzdist:
5248#
5249# Zone NAME           GMTOFF RULES  FORMAT [UNTIL]
5250# ZoneEurope/Vilnius  1:00   -      CET    1940 Aug  3
5251#                     3:00   -      MSK    1941 Jun 24
5252#                     1:00   C-Eur  CE%sT  1944 Aug
5253#
5254# $ zdump -v Europe/Vilnius | grep 1941
5255# Europe/Vilnius  Mon Jun 23 20:59:59 1941 UTC = Mon Jun 23 23:59:59 1941 MSK isdst=0 gmtoff=10800
5256# Europe/Vilnius  Mon Jun 23 21:00:00 1941 UTC = Mon Jun 23 23:00:00 1941 CEST isdst=1 gmtoff=7200
5257
5258class Europe_Vilnius_1941(tzinfo):
5259    def _utc_fold(self):
5260        return [datetime(1941, 6, 23, 21, tzinfo=self),  # Mon Jun 23 21:00:00 1941 UTC
5261                datetime(1941, 6, 23, 22, tzinfo=self)]  # Mon Jun 23 22:00:00 1941 UTC
5262
5263    def _loc_fold(self):
5264        return [datetime(1941, 6, 23, 23, tzinfo=self),  # Mon Jun 23 23:00:00 1941 MSK / CEST
5265                datetime(1941, 6, 24, 0, tzinfo=self)]   # Mon Jun 24 00:00:00 1941 CEST
5266
5267    def utcoffset(self, dt):
5268        fold_start, fold_stop = self._loc_fold()
5269        if dt < fold_start:
5270            return 3 * HOUR
5271        if dt < fold_stop:
5272            return (2 if dt.fold else 3) * HOUR
5273        # if dt >= fold_stop
5274        return 2 * HOUR
5275
5276    def dst(self, dt):
5277        fold_start, fold_stop = self._loc_fold()
5278        if dt < fold_start:
5279            return 0 * HOUR
5280        if dt < fold_stop:
5281            return (1 if dt.fold else 0) * HOUR
5282        # if dt >= fold_stop
5283        return 1 * HOUR
5284
5285    def tzname(self, dt):
5286        fold_start, fold_stop = self._loc_fold()
5287        if dt < fold_start:
5288            return 'MSK'
5289        if dt < fold_stop:
5290            return ('MSK', 'CEST')[dt.fold]
5291        # if dt >= fold_stop
5292        return 'CEST'
5293
5294    def fromutc(self, dt):
5295        assert dt.fold == 0
5296        assert dt.tzinfo is self
5297        if dt.year != 1941:
5298            raise NotImplementedError
5299        fold_start, fold_stop = self._utc_fold()
5300        if dt < fold_start:
5301            return dt + 3 * HOUR
5302        if dt < fold_stop:
5303            return (dt + 2 * HOUR).replace(fold=1)
5304        # if dt >= fold_stop
5305        return dt + 2 * HOUR
5306
5307
5308class TestLocalTimeDisambiguation(unittest.TestCase):
5309
5310    def test_vilnius_1941_fromutc(self):
5311        Vilnius = Europe_Vilnius_1941()
5312
5313        gdt = datetime(1941, 6, 23, 20, 59, 59, tzinfo=timezone.utc)
5314        ldt = gdt.astimezone(Vilnius)
5315        self.assertEqual(ldt.strftime("%c %Z%z"),
5316                         'Mon Jun 23 23:59:59 1941 MSK+0300')
5317        self.assertEqual(ldt.fold, 0)
5318        self.assertFalse(ldt.dst())
5319
5320        gdt = datetime(1941, 6, 23, 21, tzinfo=timezone.utc)
5321        ldt = gdt.astimezone(Vilnius)
5322        self.assertEqual(ldt.strftime("%c %Z%z"),
5323                         'Mon Jun 23 23:00:00 1941 CEST+0200')
5324        self.assertEqual(ldt.fold, 1)
5325        self.assertTrue(ldt.dst())
5326
5327        gdt = datetime(1941, 6, 23, 22, tzinfo=timezone.utc)
5328        ldt = gdt.astimezone(Vilnius)
5329        self.assertEqual(ldt.strftime("%c %Z%z"),
5330                         'Tue Jun 24 00:00:00 1941 CEST+0200')
5331        self.assertEqual(ldt.fold, 0)
5332        self.assertTrue(ldt.dst())
5333
5334    def test_vilnius_1941_toutc(self):
5335        Vilnius = Europe_Vilnius_1941()
5336
5337        ldt = datetime(1941, 6, 23, 22, 59, 59, tzinfo=Vilnius)
5338        gdt = ldt.astimezone(timezone.utc)
5339        self.assertEqual(gdt.strftime("%c %Z"),
5340                         'Mon Jun 23 19:59:59 1941 UTC')
5341
5342        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius)
5343        gdt = ldt.astimezone(timezone.utc)
5344        self.assertEqual(gdt.strftime("%c %Z"),
5345                         'Mon Jun 23 20:59:59 1941 UTC')
5346
5347        ldt = datetime(1941, 6, 23, 23, 59, 59, tzinfo=Vilnius, fold=1)
5348        gdt = ldt.astimezone(timezone.utc)
5349        self.assertEqual(gdt.strftime("%c %Z"),
5350                         'Mon Jun 23 21:59:59 1941 UTC')
5351
5352        ldt = datetime(1941, 6, 24, 0, tzinfo=Vilnius)
5353        gdt = ldt.astimezone(timezone.utc)
5354        self.assertEqual(gdt.strftime("%c %Z"),
5355                         'Mon Jun 23 22:00:00 1941 UTC')
5356
5357    def test_constructors(self):
5358        t = time(0, fold=1)
5359        dt = datetime(1, 1, 1, fold=1)
5360        self.assertEqual(t.fold, 1)
5361        self.assertEqual(dt.fold, 1)
5362        with self.assertRaises(TypeError):
5363            time(0, 0, 0, 0, None, 0)
5364
5365    def test_member(self):
5366        dt = datetime(1, 1, 1, fold=1)
5367        t = dt.time()
5368        self.assertEqual(t.fold, 1)
5369        t = dt.timetz()
5370        self.assertEqual(t.fold, 1)
5371
5372    def test_replace(self):
5373        t = time(0)
5374        dt = datetime(1, 1, 1)
5375        self.assertEqual(t.replace(fold=1).fold, 1)
5376        self.assertEqual(dt.replace(fold=1).fold, 1)
5377        self.assertEqual(t.replace(fold=0).fold, 0)
5378        self.assertEqual(dt.replace(fold=0).fold, 0)
5379        # Check that replacement of other fields does not change "fold".
5380        t = t.replace(fold=1, tzinfo=Eastern)
5381        dt = dt.replace(fold=1, tzinfo=Eastern)
5382        self.assertEqual(t.replace(tzinfo=None).fold, 1)
5383        self.assertEqual(dt.replace(tzinfo=None).fold, 1)
5384        # Out of bounds.
5385        with self.assertRaises(ValueError):
5386            t.replace(fold=2)
5387        with self.assertRaises(ValueError):
5388            dt.replace(fold=2)
5389        # Check that fold is a keyword-only argument
5390        with self.assertRaises(TypeError):
5391            t.replace(1, 1, 1, None, 1)
5392        with self.assertRaises(TypeError):
5393            dt.replace(1, 1, 1, 1, 1, 1, 1, None, 1)
5394
5395    def test_comparison(self):
5396        t = time(0)
5397        dt = datetime(1, 1, 1)
5398        self.assertEqual(t, t.replace(fold=1))
5399        self.assertEqual(dt, dt.replace(fold=1))
5400
5401    def test_hash(self):
5402        t = time(0)
5403        dt = datetime(1, 1, 1)
5404        self.assertEqual(hash(t), hash(t.replace(fold=1)))
5405        self.assertEqual(hash(dt), hash(dt.replace(fold=1)))
5406
5407    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5408    def test_fromtimestamp(self):
5409        s = 1414906200
5410        dt0 = datetime.fromtimestamp(s)
5411        dt1 = datetime.fromtimestamp(s + 3600)
5412        self.assertEqual(dt0.fold, 0)
5413        self.assertEqual(dt1.fold, 1)
5414
5415    @support.run_with_tz('Australia/Lord_Howe')
5416    def test_fromtimestamp_lord_howe(self):
5417        tm = _time.localtime(1.4e9)
5418        if _time.strftime('%Z%z', tm) != 'LHST+1030':
5419            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5420        # $ TZ=Australia/Lord_Howe date -r 1428158700
5421        # Sun Apr  5 01:45:00 LHDT 2015
5422        # $ TZ=Australia/Lord_Howe date -r 1428160500
5423        # Sun Apr  5 01:45:00 LHST 2015
5424        s = 1428158700
5425        t0 = datetime.fromtimestamp(s)
5426        t1 = datetime.fromtimestamp(s + 1800)
5427        self.assertEqual(t0, t1)
5428        self.assertEqual(t0.fold, 0)
5429        self.assertEqual(t1.fold, 1)
5430
5431    def test_fromtimestamp_low_fold_detection(self):
5432        # Ensure that fold detection doesn't cause an
5433        # OSError for really low values, see bpo-29097
5434        self.assertEqual(datetime.fromtimestamp(0).fold, 0)
5435
5436    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5437    def test_timestamp(self):
5438        dt0 = datetime(2014, 11, 2, 1, 30)
5439        dt1 = dt0.replace(fold=1)
5440        self.assertEqual(dt0.timestamp() + 3600,
5441                         dt1.timestamp())
5442
5443    @support.run_with_tz('Australia/Lord_Howe')
5444    def test_timestamp_lord_howe(self):
5445        tm = _time.localtime(1.4e9)
5446        if _time.strftime('%Z%z', tm) != 'LHST+1030':
5447            self.skipTest('Australia/Lord_Howe timezone is not supported on this platform')
5448        t = datetime(2015, 4, 5, 1, 45)
5449        s0 = t.replace(fold=0).timestamp()
5450        s1 = t.replace(fold=1).timestamp()
5451        self.assertEqual(s0 + 1800, s1)
5452
5453    @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
5454    def test_astimezone(self):
5455        dt0 = datetime(2014, 11, 2, 1, 30)
5456        dt1 = dt0.replace(fold=1)
5457        # Convert both naive instances to aware.
5458        adt0 = dt0.astimezone()
5459        adt1 = dt1.astimezone()
5460        # Check that the first instance in DST zone and the second in STD
5461        self.assertEqual(adt0.tzname(), 'EDT')
5462        self.assertEqual(adt1.tzname(), 'EST')
5463        self.assertEqual(adt0 + HOUR, adt1)
5464        # Aware instances with fixed offset tzinfo's always have fold=0
5465        self.assertEqual(adt0.fold, 0)
5466        self.assertEqual(adt1.fold, 0)
5467
5468    def test_pickle_fold(self):
5469        t = time(fold=1)
5470        dt = datetime(1, 1, 1, fold=1)
5471        for pickler, unpickler, proto in pickle_choices:
5472            for x in [t, dt]:
5473                s = pickler.dumps(x, proto)
5474                y = unpickler.loads(s)
5475                self.assertEqual(x, y)
5476                self.assertEqual((0 if proto < 4 else x.fold), y.fold)
5477
5478    def test_repr(self):
5479        t = time(fold=1)
5480        dt = datetime(1, 1, 1, fold=1)
5481        self.assertEqual(repr(t), 'datetime.time(0, 0, fold=1)')
5482        self.assertEqual(repr(dt),
5483                         'datetime.datetime(1, 1, 1, 0, 0, fold=1)')
5484
5485    def test_dst(self):
5486        # Let's first establish that things work in regular times.
5487        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5488        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5489        self.assertEqual(dt_summer.dst(), HOUR)
5490        self.assertEqual(dt_winter.dst(), ZERO)
5491        # The disambiguation flag is ignored
5492        self.assertEqual(dt_summer.replace(fold=1).dst(), HOUR)
5493        self.assertEqual(dt_winter.replace(fold=1).dst(), ZERO)
5494
5495        # Pick local time in the fold.
5496        for minute in [0, 30, 59]:
5497            dt = datetime(2002, 10, 27, 1, minute, tzinfo=Eastern2)
5498            # With fold=0 (the default) it is in DST.
5499            self.assertEqual(dt.dst(), HOUR)
5500            # With fold=1 it is in STD.
5501            self.assertEqual(dt.replace(fold=1).dst(), ZERO)
5502
5503        # Pick local time in the gap.
5504        for minute in [0, 30, 59]:
5505            dt = datetime(2002, 4, 7, 2, minute, tzinfo=Eastern2)
5506            # With fold=0 (the default) it is in STD.
5507            self.assertEqual(dt.dst(), ZERO)
5508            # With fold=1 it is in DST.
5509            self.assertEqual(dt.replace(fold=1).dst(), HOUR)
5510
5511
5512    def test_utcoffset(self):
5513        # Let's first establish that things work in regular times.
5514        dt_summer = datetime(2002, 10, 27, 1, tzinfo=Eastern2) - timedelta.resolution
5515        dt_winter = datetime(2002, 10, 27, 2, tzinfo=Eastern2)
5516        self.assertEqual(dt_summer.utcoffset(), -4 * HOUR)
5517        self.assertEqual(dt_winter.utcoffset(), -5 * HOUR)
5518        # The disambiguation flag is ignored
5519        self.assertEqual(dt_summer.replace(fold=1).utcoffset(), -4 * HOUR)
5520        self.assertEqual(dt_winter.replace(fold=1).utcoffset(), -5 * HOUR)
5521
5522    def test_fromutc(self):
5523        # Let's first establish that things work in regular times.
5524        u_summer = datetime(2002, 10, 27, 6, tzinfo=Eastern2) - timedelta.resolution
5525        u_winter = datetime(2002, 10, 27, 7, tzinfo=Eastern2)
5526        t_summer = Eastern2.fromutc(u_summer)
5527        t_winter = Eastern2.fromutc(u_winter)
5528        self.assertEqual(t_summer, u_summer - 4 * HOUR)
5529        self.assertEqual(t_winter, u_winter - 5 * HOUR)
5530        self.assertEqual(t_summer.fold, 0)
5531        self.assertEqual(t_winter.fold, 0)
5532
5533        # What happens in the fall-back fold?
5534        u = datetime(2002, 10, 27, 5, 30, tzinfo=Eastern2)
5535        t0 = Eastern2.fromutc(u)
5536        u += HOUR
5537        t1 = Eastern2.fromutc(u)
5538        self.assertEqual(t0, t1)
5539        self.assertEqual(t0.fold, 0)
5540        self.assertEqual(t1.fold, 1)
5541        # The tricky part is when u is in the local fold:
5542        u = datetime(2002, 10, 27, 1, 30, tzinfo=Eastern2)
5543        t = Eastern2.fromutc(u)
5544        self.assertEqual((t.day, t.hour), (26, 21))
5545        # .. or gets into the local fold after a standard time adjustment
5546        u = datetime(2002, 10, 27, 6, 30, tzinfo=Eastern2)
5547        t = Eastern2.fromutc(u)
5548        self.assertEqual((t.day, t.hour), (27, 1))
5549
5550        # What happens in the spring-forward gap?
5551        u = datetime(2002, 4, 7, 2, 0, tzinfo=Eastern2)
5552        t = Eastern2.fromutc(u)
5553        self.assertEqual((t.day, t.hour), (6, 21))
5554
5555    def test_mixed_compare_regular(self):
5556        t = datetime(2000, 1, 1, tzinfo=Eastern2)
5557        self.assertEqual(t, t.astimezone(timezone.utc))
5558        t = datetime(2000, 6, 1, tzinfo=Eastern2)
5559        self.assertEqual(t, t.astimezone(timezone.utc))
5560
5561    def test_mixed_compare_fold(self):
5562        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5563        t_fold_utc = t_fold.astimezone(timezone.utc)
5564        self.assertNotEqual(t_fold, t_fold_utc)
5565        self.assertNotEqual(t_fold_utc, t_fold)
5566
5567    def test_mixed_compare_gap(self):
5568        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5569        t_gap_utc = t_gap.astimezone(timezone.utc)
5570        self.assertNotEqual(t_gap, t_gap_utc)
5571        self.assertNotEqual(t_gap_utc, t_gap)
5572
5573    def test_hash_aware(self):
5574        t = datetime(2000, 1, 1, tzinfo=Eastern2)
5575        self.assertEqual(hash(t), hash(t.replace(fold=1)))
5576        t_fold = datetime(2002, 10, 27, 1, 45, tzinfo=Eastern2)
5577        t_gap = datetime(2002, 4, 7, 2, 45, tzinfo=Eastern2)
5578        self.assertEqual(hash(t_fold), hash(t_fold.replace(fold=1)))
5579        self.assertEqual(hash(t_gap), hash(t_gap.replace(fold=1)))
5580
5581SEC = timedelta(0, 1)
5582
5583def pairs(iterable):
5584    a, b = itertools.tee(iterable)
5585    next(b, None)
5586    return zip(a, b)
5587
5588class ZoneInfo(tzinfo):
5589    zoneroot = '/usr/share/zoneinfo'
5590    def __init__(self, ut, ti):
5591        """
5592
5593        :param ut: array
5594            Array of transition point timestamps
5595        :param ti: list
5596            A list of (offset, isdst, abbr) tuples
5597        :return: None
5598        """
5599        self.ut = ut
5600        self.ti = ti
5601        self.lt = self.invert(ut, ti)
5602
5603    @staticmethod
5604    def invert(ut, ti):
5605        lt = (array('q', ut), array('q', ut))
5606        if ut:
5607            offset = ti[0][0] // SEC
5608            lt[0][0] += offset
5609            lt[1][0] += offset
5610            for i in range(1, len(ut)):
5611                lt[0][i] += ti[i-1][0] // SEC
5612                lt[1][i] += ti[i][0] // SEC
5613        return lt
5614
5615    @classmethod
5616    def fromfile(cls, fileobj):
5617        if fileobj.read(4).decode() != "TZif":
5618            raise ValueError("not a zoneinfo file")
5619        fileobj.seek(32)
5620        counts = array('i')
5621        counts.fromfile(fileobj, 3)
5622        if sys.byteorder != 'big':
5623            counts.byteswap()
5624
5625        ut = array('i')
5626        ut.fromfile(fileobj, counts[0])
5627        if sys.byteorder != 'big':
5628            ut.byteswap()
5629
5630        type_indices = array('B')
5631        type_indices.fromfile(fileobj, counts[0])
5632
5633        ttis = []
5634        for i in range(counts[1]):
5635            ttis.append(struct.unpack(">lbb", fileobj.read(6)))
5636
5637        abbrs = fileobj.read(counts[2])
5638
5639        # Convert ttis
5640        for i, (gmtoff, isdst, abbrind) in enumerate(ttis):
5641            abbr = abbrs[abbrind:abbrs.find(0, abbrind)].decode()
5642            ttis[i] = (timedelta(0, gmtoff), isdst, abbr)
5643
5644        ti = [None] * len(ut)
5645        for i, idx in enumerate(type_indices):
5646            ti[i] = ttis[idx]
5647
5648        self = cls(ut, ti)
5649
5650        return self
5651
5652    @classmethod
5653    def fromname(cls, name):
5654        path = os.path.join(cls.zoneroot, name)
5655        with open(path, 'rb') as f:
5656            return cls.fromfile(f)
5657
5658    EPOCHORDINAL = date(1970, 1, 1).toordinal()
5659
5660    def fromutc(self, dt):
5661        """datetime in UTC -> datetime in local time."""
5662
5663        if not isinstance(dt, datetime):
5664            raise TypeError("fromutc() requires a datetime argument")
5665        if dt.tzinfo is not self:
5666            raise ValueError("dt.tzinfo is not self")
5667
5668        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5669                     + dt.hour * 3600
5670                     + dt.minute * 60
5671                     + dt.second)
5672
5673        if timestamp < self.ut[1]:
5674            tti = self.ti[0]
5675            fold = 0
5676        else:
5677            idx = bisect.bisect_right(self.ut, timestamp)
5678            assert self.ut[idx-1] <= timestamp
5679            assert idx == len(self.ut) or timestamp < self.ut[idx]
5680            tti_prev, tti = self.ti[idx-2:idx]
5681            # Detect fold
5682            shift = tti_prev[0] - tti[0]
5683            fold = (shift > timedelta(0, timestamp - self.ut[idx-1]))
5684        dt += tti[0]
5685        if fold:
5686            return dt.replace(fold=1)
5687        else:
5688            return dt
5689
5690    def _find_ti(self, dt, i):
5691        timestamp = ((dt.toordinal() - self.EPOCHORDINAL) * 86400
5692             + dt.hour * 3600
5693             + dt.minute * 60
5694             + dt.second)
5695        lt = self.lt[dt.fold]
5696        idx = bisect.bisect_right(lt, timestamp)
5697
5698        return self.ti[max(0, idx - 1)][i]
5699
5700    def utcoffset(self, dt):
5701        return self._find_ti(dt, 0)
5702
5703    def dst(self, dt):
5704        isdst = self._find_ti(dt, 1)
5705        # XXX: We cannot accurately determine the "save" value,
5706        # so let's return 1h whenever DST is in effect.  Since
5707        # we don't use dst() in fromutc(), it is unlikely that
5708        # it will be needed for anything more than bool(dst()).
5709        return ZERO if isdst else HOUR
5710
5711    def tzname(self, dt):
5712        return self._find_ti(dt, 2)
5713
5714    @classmethod
5715    def zonenames(cls, zonedir=None):
5716        if zonedir is None:
5717            zonedir = cls.zoneroot
5718        zone_tab = os.path.join(zonedir, 'zone.tab')
5719        try:
5720            f = open(zone_tab)
5721        except OSError:
5722            return
5723        with f:
5724            for line in f:
5725                line = line.strip()
5726                if line and not line.startswith('#'):
5727                    yield line.split()[2]
5728
5729    @classmethod
5730    def stats(cls, start_year=1):
5731        count = gap_count = fold_count = zeros_count = 0
5732        min_gap = min_fold = timedelta.max
5733        max_gap = max_fold = ZERO
5734        min_gap_datetime = max_gap_datetime = datetime.min
5735        min_gap_zone = max_gap_zone = None
5736        min_fold_datetime = max_fold_datetime = datetime.min
5737        min_fold_zone = max_fold_zone = None
5738        stats_since = datetime(start_year, 1, 1) # Starting from 1970 eliminates a lot of noise
5739        for zonename in cls.zonenames():
5740            count += 1
5741            tz = cls.fromname(zonename)
5742            for dt, shift in tz.transitions():
5743                if dt < stats_since:
5744                    continue
5745                if shift > ZERO:
5746                    gap_count += 1
5747                    if (shift, dt) > (max_gap, max_gap_datetime):
5748                        max_gap = shift
5749                        max_gap_zone = zonename
5750                        max_gap_datetime = dt
5751                    if (shift, datetime.max - dt) < (min_gap, datetime.max - min_gap_datetime):
5752                        min_gap = shift
5753                        min_gap_zone = zonename
5754                        min_gap_datetime = dt
5755                elif shift < ZERO:
5756                    fold_count += 1
5757                    shift = -shift
5758                    if (shift, dt) > (max_fold, max_fold_datetime):
5759                        max_fold = shift
5760                        max_fold_zone = zonename
5761                        max_fold_datetime = dt
5762                    if (shift, datetime.max - dt) < (min_fold, datetime.max - min_fold_datetime):
5763                        min_fold = shift
5764                        min_fold_zone = zonename
5765                        min_fold_datetime = dt
5766                else:
5767                    zeros_count += 1
5768        trans_counts = (gap_count, fold_count, zeros_count)
5769        print("Number of zones:       %5d" % count)
5770        print("Number of transitions: %5d = %d (gaps) + %d (folds) + %d (zeros)" %
5771              ((sum(trans_counts),) + trans_counts))
5772        print("Min gap:         %16s at %s in %s" % (min_gap, min_gap_datetime, min_gap_zone))
5773        print("Max gap:         %16s at %s in %s" % (max_gap, max_gap_datetime, max_gap_zone))
5774        print("Min fold:        %16s at %s in %s" % (min_fold, min_fold_datetime, min_fold_zone))
5775        print("Max fold:        %16s at %s in %s" % (max_fold, max_fold_datetime, max_fold_zone))
5776
5777
5778    def transitions(self):
5779        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5780            shift = ti[0] - prev_ti[0]
5781            yield datetime.utcfromtimestamp(t), shift
5782
5783    def nondst_folds(self):
5784        """Find all folds with the same value of isdst on both sides of the transition."""
5785        for (_, prev_ti), (t, ti) in pairs(zip(self.ut, self.ti)):
5786            shift = ti[0] - prev_ti[0]
5787            if shift < ZERO and ti[1] == prev_ti[1]:
5788                yield datetime.utcfromtimestamp(t), -shift, prev_ti[2], ti[2]
5789
5790    @classmethod
5791    def print_all_nondst_folds(cls, same_abbr=False, start_year=1):
5792        count = 0
5793        for zonename in cls.zonenames():
5794            tz = cls.fromname(zonename)
5795            for dt, shift, prev_abbr, abbr in tz.nondst_folds():
5796                if dt.year < start_year or same_abbr and prev_abbr != abbr:
5797                    continue
5798                count += 1
5799                print("%3d) %-30s %s %10s %5s -> %s" %
5800                      (count, zonename, dt, shift, prev_abbr, abbr))
5801
5802    def folds(self):
5803        for t, shift in self.transitions():
5804            if shift < ZERO:
5805                yield t, -shift
5806
5807    def gaps(self):
5808        for t, shift in self.transitions():
5809            if shift > ZERO:
5810                yield t, shift
5811
5812    def zeros(self):
5813        for t, shift in self.transitions():
5814            if not shift:
5815                yield t
5816
5817
5818class ZoneInfoTest(unittest.TestCase):
5819    zonename = 'America/New_York'
5820
5821    def setUp(self):
5822        if sys.platform == "vxworks":
5823            self.skipTest("Skipping zoneinfo tests on VxWorks")
5824        if sys.platform == "win32":
5825            self.skipTest("Skipping zoneinfo tests on Windows")
5826        try:
5827            self.tz = ZoneInfo.fromname(self.zonename)
5828        except FileNotFoundError as err:
5829            self.skipTest("Skipping %s: %s" % (self.zonename, err))
5830
5831    def assertEquivDatetimes(self, a, b):
5832        self.assertEqual((a.replace(tzinfo=None), a.fold, id(a.tzinfo)),
5833                         (b.replace(tzinfo=None), b.fold, id(b.tzinfo)))
5834
5835    def test_folds(self):
5836        tz = self.tz
5837        for dt, shift in tz.folds():
5838            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5839                udt = dt + x
5840                ldt = tz.fromutc(udt.replace(tzinfo=tz))
5841                self.assertEqual(ldt.fold, 1)
5842                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5843                self.assertEquivDatetimes(adt, ldt)
5844                utcoffset = ldt.utcoffset()
5845                self.assertEqual(ldt.replace(tzinfo=None), udt + utcoffset)
5846                # Round trip
5847                self.assertEquivDatetimes(ldt.astimezone(timezone.utc),
5848                                          udt.replace(tzinfo=timezone.utc))
5849
5850
5851            for x in [-timedelta.resolution, shift]:
5852                udt = dt + x
5853                udt = udt.replace(tzinfo=tz)
5854                ldt = tz.fromutc(udt)
5855                self.assertEqual(ldt.fold, 0)
5856
5857    def test_gaps(self):
5858        tz = self.tz
5859        for dt, shift in tz.gaps():
5860            for x in [0 * shift, 0.5 * shift, shift - timedelta.resolution]:
5861                udt = dt + x
5862                udt = udt.replace(tzinfo=tz)
5863                ldt = tz.fromutc(udt)
5864                self.assertEqual(ldt.fold, 0)
5865                adt = udt.replace(tzinfo=timezone.utc).astimezone(tz)
5866                self.assertEquivDatetimes(adt, ldt)
5867                utcoffset = ldt.utcoffset()
5868                self.assertEqual(ldt.replace(tzinfo=None), udt.replace(tzinfo=None) + utcoffset)
5869                # Create a local time inside the gap
5870                ldt = tz.fromutc(dt.replace(tzinfo=tz)) - shift + x
5871                self.assertLess(ldt.replace(fold=1).utcoffset(),
5872                                ldt.replace(fold=0).utcoffset(),
5873                                "At %s." % ldt)
5874
5875            for x in [-timedelta.resolution, shift]:
5876                udt = dt + x
5877                ldt = tz.fromutc(udt.replace(tzinfo=tz))
5878                self.assertEqual(ldt.fold, 0)
5879
5880    def test_system_transitions(self):
5881        if ('Riyadh8' in self.zonename or
5882            # From tzdata NEWS file:
5883            # The files solar87, solar88, and solar89 are no longer distributed.
5884            # They were a negative experiment - that is, a demonstration that
5885            # tz data can represent solar time only with some difficulty and error.
5886            # Their presence in the distribution caused confusion, as Riyadh
5887            # civil time was generally not solar time in those years.
5888                self.zonename.startswith('right/')):
5889            self.skipTest("Skipping %s" % self.zonename)
5890        tz = self.tz
5891        TZ = os.environ.get('TZ')
5892        os.environ['TZ'] = self.zonename
5893        try:
5894            _time.tzset()
5895            for udt, shift in tz.transitions():
5896                if udt.year >= 2037:
5897                    # System support for times around the end of 32-bit time_t
5898                    # and later is flaky on many systems.
5899                    break
5900                s0 = (udt - datetime(1970, 1, 1)) // SEC
5901                ss = shift // SEC   # shift seconds
5902                for x in [-40 * 3600, -20*3600, -1, 0,
5903                          ss - 1, ss + 20 * 3600, ss + 40 * 3600]:
5904                    s = s0 + x
5905                    sdt = datetime.fromtimestamp(s)
5906                    tzdt = datetime.fromtimestamp(s, tz).replace(tzinfo=None)
5907                    self.assertEquivDatetimes(sdt, tzdt)
5908                    s1 = sdt.timestamp()
5909                    self.assertEqual(s, s1)
5910                if ss > 0:  # gap
5911                    # Create local time inside the gap
5912                    dt = datetime.fromtimestamp(s0) - shift / 2
5913                    ts0 = dt.timestamp()
5914                    ts1 = dt.replace(fold=1).timestamp()
5915                    self.assertEqual(ts0, s0 + ss / 2)
5916                    self.assertEqual(ts1, s0 - ss / 2)
5917        finally:
5918            if TZ is None:
5919                del os.environ['TZ']
5920            else:
5921                os.environ['TZ'] = TZ
5922            _time.tzset()
5923
5924
5925class ZoneInfoCompleteTest(unittest.TestSuite):
5926    def __init__(self):
5927        tests = []
5928        if is_resource_enabled('tzdata'):
5929            for name in ZoneInfo.zonenames():
5930                Test = type('ZoneInfoTest[%s]' % name, (ZoneInfoTest,), {})
5931                Test.zonename = name
5932                for method in dir(Test):
5933                    if method.startswith('test_'):
5934                        tests.append(Test(method))
5935        super().__init__(tests)
5936
5937# Iran had a sub-minute UTC offset before 1946.
5938class IranTest(ZoneInfoTest):
5939    zonename = 'Asia/Tehran'
5940
5941
5942class CapiTest(unittest.TestCase):
5943    def setUp(self):
5944        # Since the C API is not present in the _Pure tests, skip all tests
5945        if self.__class__.__name__.endswith('Pure'):
5946            self.skipTest('Not relevant in pure Python')
5947
5948        # This *must* be called, and it must be called first, so until either
5949        # restriction is loosened, we'll call it as part of test setup
5950        _testcapi.test_datetime_capi()
5951
5952    def test_utc_capi(self):
5953        for use_macro in (True, False):
5954            capi_utc = _testcapi.get_timezone_utc_capi(use_macro)
5955
5956            with self.subTest(use_macro=use_macro):
5957                self.assertIs(capi_utc, timezone.utc)
5958
5959    def test_timezones_capi(self):
5960        est_capi, est_macro, est_macro_nn = _testcapi.make_timezones_capi()
5961
5962        exp_named = timezone(timedelta(hours=-5), "EST")
5963        exp_unnamed = timezone(timedelta(hours=-5))
5964
5965        cases = [
5966            ('est_capi', est_capi, exp_named),
5967            ('est_macro', est_macro, exp_named),
5968            ('est_macro_nn', est_macro_nn, exp_unnamed)
5969        ]
5970
5971        for name, tz_act, tz_exp in cases:
5972            with self.subTest(name=name):
5973                self.assertEqual(tz_act, tz_exp)
5974
5975                dt1 = datetime(2000, 2, 4, tzinfo=tz_act)
5976                dt2 = datetime(2000, 2, 4, tzinfo=tz_exp)
5977
5978                self.assertEqual(dt1, dt2)
5979                self.assertEqual(dt1.tzname(), dt2.tzname())
5980
5981                dt_utc = datetime(2000, 2, 4, 5, tzinfo=timezone.utc)
5982
5983                self.assertEqual(dt1.astimezone(timezone.utc), dt_utc)
5984
5985    def test_PyDateTime_DELTA_GET(self):
5986        class TimeDeltaSubclass(timedelta):
5987            pass
5988
5989        for klass in [timedelta, TimeDeltaSubclass]:
5990            for args in [(26, 55, 99999), (26, 55, 99999)]:
5991                d = klass(*args)
5992                with self.subTest(cls=klass, date=args):
5993                    days, seconds, microseconds = _testcapi.PyDateTime_DELTA_GET(d)
5994
5995                    self.assertEqual(days, d.days)
5996                    self.assertEqual(seconds, d.seconds)
5997                    self.assertEqual(microseconds, d.microseconds)
5998
5999    def test_PyDateTime_GET(self):
6000        class DateSubclass(date):
6001            pass
6002
6003        for klass in [date, DateSubclass]:
6004            for args in [(2000, 1, 2), (2012, 2, 29)]:
6005                d = klass(*args)
6006                with self.subTest(cls=klass, date=args):
6007                    year, month, day = _testcapi.PyDateTime_GET(d)
6008
6009                    self.assertEqual(year, d.year)
6010                    self.assertEqual(month, d.month)
6011                    self.assertEqual(day, d.day)
6012
6013    def test_PyDateTime_DATE_GET(self):
6014        class DateTimeSubclass(datetime):
6015            pass
6016
6017        for klass in [datetime, DateTimeSubclass]:
6018            for args in [(1993, 8, 26, 22, 12, 55, 99999),
6019                         (1993, 8, 26, 22, 12, 55, 99999)]:
6020                d = klass(*args)
6021                with self.subTest(cls=klass, date=args):
6022                    hour, minute, second, microsecond = _testcapi.PyDateTime_DATE_GET(d)
6023
6024                    self.assertEqual(hour, d.hour)
6025                    self.assertEqual(minute, d.minute)
6026                    self.assertEqual(second, d.second)
6027                    self.assertEqual(microsecond, d.microsecond)
6028
6029    def test_PyDateTime_TIME_GET(self):
6030        class TimeSubclass(time):
6031            pass
6032
6033        for klass in [time, TimeSubclass]:
6034            for args in [(12, 30, 20, 10), (12, 30, 20, 10)]:
6035                d = klass(*args)
6036                with self.subTest(cls=klass, date=args):
6037                    hour, minute, second, microsecond = _testcapi.PyDateTime_TIME_GET(d)
6038
6039                    self.assertEqual(hour, d.hour)
6040                    self.assertEqual(minute, d.minute)
6041                    self.assertEqual(second, d.second)
6042                    self.assertEqual(microsecond, d.microsecond)
6043
6044    def test_timezones_offset_zero(self):
6045        utc0, utc1, non_utc = _testcapi.get_timezones_offset_zero()
6046
6047        with self.subTest(testname="utc0"):
6048            self.assertIs(utc0, timezone.utc)
6049
6050        with self.subTest(testname="utc1"):
6051            self.assertIs(utc1, timezone.utc)
6052
6053        with self.subTest(testname="non_utc"):
6054            self.assertIsNot(non_utc, timezone.utc)
6055
6056            non_utc_exp = timezone(timedelta(hours=0), "")
6057
6058            self.assertEqual(non_utc, non_utc_exp)
6059
6060            dt1 = datetime(2000, 2, 4, tzinfo=non_utc)
6061            dt2 = datetime(2000, 2, 4, tzinfo=non_utc_exp)
6062
6063            self.assertEqual(dt1, dt2)
6064            self.assertEqual(dt1.tzname(), dt2.tzname())
6065
6066    def test_check_date(self):
6067        class DateSubclass(date):
6068            pass
6069
6070        d = date(2011, 1, 1)
6071        ds = DateSubclass(2011, 1, 1)
6072        dt = datetime(2011, 1, 1)
6073
6074        is_date = _testcapi.datetime_check_date
6075
6076        # Check the ones that should be valid
6077        self.assertTrue(is_date(d))
6078        self.assertTrue(is_date(dt))
6079        self.assertTrue(is_date(ds))
6080        self.assertTrue(is_date(d, True))
6081
6082        # Check that the subclasses do not match exactly
6083        self.assertFalse(is_date(dt, True))
6084        self.assertFalse(is_date(ds, True))
6085
6086        # Check that various other things are not dates at all
6087        args = [tuple(), list(), 1, '2011-01-01',
6088                timedelta(1), timezone.utc, time(12, 00)]
6089        for arg in args:
6090            for exact in (True, False):
6091                with self.subTest(arg=arg, exact=exact):
6092                    self.assertFalse(is_date(arg, exact))
6093
6094    def test_check_time(self):
6095        class TimeSubclass(time):
6096            pass
6097
6098        t = time(12, 30)
6099        ts = TimeSubclass(12, 30)
6100
6101        is_time = _testcapi.datetime_check_time
6102
6103        # Check the ones that should be valid
6104        self.assertTrue(is_time(t))
6105        self.assertTrue(is_time(ts))
6106        self.assertTrue(is_time(t, True))
6107
6108        # Check that the subclass does not match exactly
6109        self.assertFalse(is_time(ts, True))
6110
6111        # Check that various other things are not times
6112        args = [tuple(), list(), 1, '2011-01-01',
6113                timedelta(1), timezone.utc, date(2011, 1, 1)]
6114
6115        for arg in args:
6116            for exact in (True, False):
6117                with self.subTest(arg=arg, exact=exact):
6118                    self.assertFalse(is_time(arg, exact))
6119
6120    def test_check_datetime(self):
6121        class DateTimeSubclass(datetime):
6122            pass
6123
6124        dt = datetime(2011, 1, 1, 12, 30)
6125        dts = DateTimeSubclass(2011, 1, 1, 12, 30)
6126
6127        is_datetime = _testcapi.datetime_check_datetime
6128
6129        # Check the ones that should be valid
6130        self.assertTrue(is_datetime(dt))
6131        self.assertTrue(is_datetime(dts))
6132        self.assertTrue(is_datetime(dt, True))
6133
6134        # Check that the subclass does not match exactly
6135        self.assertFalse(is_datetime(dts, True))
6136
6137        # Check that various other things are not datetimes
6138        args = [tuple(), list(), 1, '2011-01-01',
6139                timedelta(1), timezone.utc, date(2011, 1, 1)]
6140
6141        for arg in args:
6142            for exact in (True, False):
6143                with self.subTest(arg=arg, exact=exact):
6144                    self.assertFalse(is_datetime(arg, exact))
6145
6146    def test_check_delta(self):
6147        class TimeDeltaSubclass(timedelta):
6148            pass
6149
6150        td = timedelta(1)
6151        tds = TimeDeltaSubclass(1)
6152
6153        is_timedelta = _testcapi.datetime_check_delta
6154
6155        # Check the ones that should be valid
6156        self.assertTrue(is_timedelta(td))
6157        self.assertTrue(is_timedelta(tds))
6158        self.assertTrue(is_timedelta(td, True))
6159
6160        # Check that the subclass does not match exactly
6161        self.assertFalse(is_timedelta(tds, True))
6162
6163        # Check that various other things are not timedeltas
6164        args = [tuple(), list(), 1, '2011-01-01',
6165                timezone.utc, date(2011, 1, 1), datetime(2011, 1, 1)]
6166
6167        for arg in args:
6168            for exact in (True, False):
6169                with self.subTest(arg=arg, exact=exact):
6170                    self.assertFalse(is_timedelta(arg, exact))
6171
6172    def test_check_tzinfo(self):
6173        class TZInfoSubclass(tzinfo):
6174            pass
6175
6176        tzi = tzinfo()
6177        tzis = TZInfoSubclass()
6178        tz = timezone(timedelta(hours=-5))
6179
6180        is_tzinfo = _testcapi.datetime_check_tzinfo
6181
6182        # Check the ones that should be valid
6183        self.assertTrue(is_tzinfo(tzi))
6184        self.assertTrue(is_tzinfo(tz))
6185        self.assertTrue(is_tzinfo(tzis))
6186        self.assertTrue(is_tzinfo(tzi, True))
6187
6188        # Check that the subclasses do not match exactly
6189        self.assertFalse(is_tzinfo(tz, True))
6190        self.assertFalse(is_tzinfo(tzis, True))
6191
6192        # Check that various other things are not tzinfos
6193        args = [tuple(), list(), 1, '2011-01-01',
6194                date(2011, 1, 1), datetime(2011, 1, 1)]
6195
6196        for arg in args:
6197            for exact in (True, False):
6198                with self.subTest(arg=arg, exact=exact):
6199                    self.assertFalse(is_tzinfo(arg, exact))
6200
6201    def test_date_from_date(self):
6202        exp_date = date(1993, 8, 26)
6203
6204        for macro in False, True:
6205            with self.subTest(macro=macro):
6206                c_api_date = _testcapi.get_date_fromdate(
6207                    macro,
6208                    exp_date.year,
6209                    exp_date.month,
6210                    exp_date.day)
6211
6212                self.assertEqual(c_api_date, exp_date)
6213
6214    def test_datetime_from_dateandtime(self):
6215        exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6216
6217        for macro in False, True:
6218            with self.subTest(macro=macro):
6219                c_api_date = _testcapi.get_datetime_fromdateandtime(
6220                    macro,
6221                    exp_date.year,
6222                    exp_date.month,
6223                    exp_date.day,
6224                    exp_date.hour,
6225                    exp_date.minute,
6226                    exp_date.second,
6227                    exp_date.microsecond)
6228
6229                self.assertEqual(c_api_date, exp_date)
6230
6231    def test_datetime_from_dateandtimeandfold(self):
6232        exp_date = datetime(1993, 8, 26, 22, 12, 55, 99999)
6233
6234        for fold in [0, 1]:
6235            for macro in False, True:
6236                with self.subTest(macro=macro, fold=fold):
6237                    c_api_date = _testcapi.get_datetime_fromdateandtimeandfold(
6238                        macro,
6239                        exp_date.year,
6240                        exp_date.month,
6241                        exp_date.day,
6242                        exp_date.hour,
6243                        exp_date.minute,
6244                        exp_date.second,
6245                        exp_date.microsecond,
6246                        exp_date.fold)
6247
6248                    self.assertEqual(c_api_date, exp_date)
6249                    self.assertEqual(c_api_date.fold, exp_date.fold)
6250
6251    def test_time_from_time(self):
6252        exp_time = time(22, 12, 55, 99999)
6253
6254        for macro in False, True:
6255            with self.subTest(macro=macro):
6256                c_api_time = _testcapi.get_time_fromtime(
6257                    macro,
6258                    exp_time.hour,
6259                    exp_time.minute,
6260                    exp_time.second,
6261                    exp_time.microsecond)
6262
6263                self.assertEqual(c_api_time, exp_time)
6264
6265    def test_time_from_timeandfold(self):
6266        exp_time = time(22, 12, 55, 99999)
6267
6268        for fold in [0, 1]:
6269            for macro in False, True:
6270                with self.subTest(macro=macro, fold=fold):
6271                    c_api_time = _testcapi.get_time_fromtimeandfold(
6272                        macro,
6273                        exp_time.hour,
6274                        exp_time.minute,
6275                        exp_time.second,
6276                        exp_time.microsecond,
6277                        exp_time.fold)
6278
6279                    self.assertEqual(c_api_time, exp_time)
6280                    self.assertEqual(c_api_time.fold, exp_time.fold)
6281
6282    def test_delta_from_dsu(self):
6283        exp_delta = timedelta(26, 55, 99999)
6284
6285        for macro in False, True:
6286            with self.subTest(macro=macro):
6287                c_api_delta = _testcapi.get_delta_fromdsu(
6288                    macro,
6289                    exp_delta.days,
6290                    exp_delta.seconds,
6291                    exp_delta.microseconds)
6292
6293                self.assertEqual(c_api_delta, exp_delta)
6294
6295    def test_date_from_timestamp(self):
6296        ts = datetime(1995, 4, 12).timestamp()
6297
6298        for macro in False, True:
6299            with self.subTest(macro=macro):
6300                d = _testcapi.get_date_fromtimestamp(int(ts), macro)
6301
6302                self.assertEqual(d, date(1995, 4, 12))
6303
6304    def test_datetime_from_timestamp(self):
6305        cases = [
6306            ((1995, 4, 12), None, False),
6307            ((1995, 4, 12), None, True),
6308            ((1995, 4, 12), timezone(timedelta(hours=1)), True),
6309            ((1995, 4, 12, 14, 30), None, False),
6310            ((1995, 4, 12, 14, 30), None, True),
6311            ((1995, 4, 12, 14, 30), timezone(timedelta(hours=1)), True),
6312        ]
6313
6314        from_timestamp = _testcapi.get_datetime_fromtimestamp
6315        for case in cases:
6316            for macro in False, True:
6317                with self.subTest(case=case, macro=macro):
6318                    dtup, tzinfo, usetz = case
6319                    dt_orig = datetime(*dtup, tzinfo=tzinfo)
6320                    ts = int(dt_orig.timestamp())
6321
6322                    dt_rt = from_timestamp(ts, tzinfo, usetz, macro)
6323
6324                    self.assertEqual(dt_orig, dt_rt)
6325
6326
6327def load_tests(loader, standard_tests, pattern):
6328    standard_tests.addTest(ZoneInfoCompleteTest())
6329    return standard_tests
6330
6331
6332if __name__ == "__main__":
6333    unittest.main()
6334