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