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