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