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