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