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