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