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