1"""Test date/time type. 2 3See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases 4""" 5from __future__ import division 6import sys 7import pickle 8import cPickle 9import unittest 10 11from test import test_support 12 13from datetime import MINYEAR, MAXYEAR 14from datetime import timedelta 15from datetime import tzinfo 16from datetime import time 17from datetime import date, datetime 18 19pickle_choices = [(pickler, unpickler, proto) 20 for pickler in pickle, cPickle 21 for unpickler in pickle, cPickle 22 for proto in range(3)] 23assert len(pickle_choices) == 2*2*3 24 25# An arbitrary collection of objects of non-datetime types, for testing 26# mixed-type comparisons. 27OTHERSTUFF = (10, 10L, 34.5, "abc", {}, [], ()) 28 29 30############################################################################# 31# module tests 32 33class TestModule(unittest.TestCase): 34 35 def test_constants(self): 36 import datetime 37 self.assertEqual(datetime.MINYEAR, 1) 38 self.assertEqual(datetime.MAXYEAR, 9999) 39 40############################################################################# 41# tzinfo tests 42 43class FixedOffset(tzinfo): 44 def __init__(self, offset, name, dstoffset=42): 45 if isinstance(offset, int): 46 offset = timedelta(minutes=offset) 47 if isinstance(dstoffset, int): 48 dstoffset = timedelta(minutes=dstoffset) 49 self.__offset = offset 50 self.__name = name 51 self.__dstoffset = dstoffset 52 def __repr__(self): 53 return self.__name.lower() 54 def utcoffset(self, dt): 55 return self.__offset 56 def tzname(self, dt): 57 return self.__name 58 def dst(self, dt): 59 return self.__dstoffset 60 61class PicklableFixedOffset(FixedOffset): 62 def __init__(self, offset=None, name=None, dstoffset=None): 63 FixedOffset.__init__(self, offset, name, dstoffset) 64 65class TestTZInfo(unittest.TestCase): 66 67 def test_non_abstractness(self): 68 # In order to allow subclasses to get pickled, the C implementation 69 # wasn't able to get away with having __init__ raise 70 # NotImplementedError. 71 useless = tzinfo() 72 dt = datetime.max 73 self.assertRaises(NotImplementedError, useless.tzname, dt) 74 self.assertRaises(NotImplementedError, useless.utcoffset, dt) 75 self.assertRaises(NotImplementedError, useless.dst, dt) 76 77 def test_subclass_must_override(self): 78 class NotEnough(tzinfo): 79 def __init__(self, offset, name): 80 self.__offset = offset 81 self.__name = name 82 self.assertTrue(issubclass(NotEnough, tzinfo)) 83 ne = NotEnough(3, "NotByALongShot") 84 self.assertIsInstance(ne, tzinfo) 85 86 dt = datetime.now() 87 self.assertRaises(NotImplementedError, ne.tzname, dt) 88 self.assertRaises(NotImplementedError, ne.utcoffset, dt) 89 self.assertRaises(NotImplementedError, ne.dst, dt) 90 91 def test_normal(self): 92 fo = FixedOffset(3, "Three") 93 self.assertIsInstance(fo, tzinfo) 94 for dt in datetime.now(), None: 95 self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3)) 96 self.assertEqual(fo.tzname(dt), "Three") 97 self.assertEqual(fo.dst(dt), timedelta(minutes=42)) 98 99 def test_pickling_base(self): 100 # There's no point to pickling tzinfo objects on their own (they 101 # carry no data), but they need to be picklable anyway else 102 # concrete subclasses can't be pickled. 103 orig = tzinfo.__new__(tzinfo) 104 self.assertIs(type(orig), tzinfo) 105 for pickler, unpickler, proto in pickle_choices: 106 green = pickler.dumps(orig, proto) 107 derived = unpickler.loads(green) 108 self.assertIs(type(derived), tzinfo) 109 110 def test_pickling_subclass(self): 111 # Make sure we can pickle/unpickle an instance of a subclass. 112 offset = timedelta(minutes=-300) 113 orig = PicklableFixedOffset(offset, 'cookie') 114 self.assertIsInstance(orig, tzinfo) 115 self.assertTrue(type(orig) is PicklableFixedOffset) 116 self.assertEqual(orig.utcoffset(None), offset) 117 self.assertEqual(orig.tzname(None), 'cookie') 118 for pickler, unpickler, proto in pickle_choices: 119 green = pickler.dumps(orig, proto) 120 derived = unpickler.loads(green) 121 self.assertIsInstance(derived, tzinfo) 122 self.assertTrue(type(derived) is PicklableFixedOffset) 123 self.assertEqual(derived.utcoffset(None), offset) 124 self.assertEqual(derived.tzname(None), 'cookie') 125 126############################################################################# 127# Base class for testing a particular aspect of timedelta, time, date and 128# datetime comparisons. 129 130class HarmlessMixedComparison: 131 # Test that __eq__ and __ne__ don't complain for mixed-type comparisons. 132 133 # Subclasses must define 'theclass', and theclass(1, 1, 1) must be a 134 # legit constructor. 135 136 def test_harmless_mixed_comparison(self): 137 me = self.theclass(1, 1, 1) 138 139 self.assertFalse(me == ()) 140 self.assertTrue(me != ()) 141 self.assertFalse(() == me) 142 self.assertTrue(() != me) 143 144 self.assertIn(me, [1, 20L, [], me]) 145 self.assertIn([], [me, 1, 20L, []]) 146 147 def test_harmful_mixed_comparison(self): 148 me = self.theclass(1, 1, 1) 149 150 self.assertRaises(TypeError, lambda: me < ()) 151 self.assertRaises(TypeError, lambda: me <= ()) 152 self.assertRaises(TypeError, lambda: me > ()) 153 self.assertRaises(TypeError, lambda: me >= ()) 154 155 self.assertRaises(TypeError, lambda: () < me) 156 self.assertRaises(TypeError, lambda: () <= me) 157 self.assertRaises(TypeError, lambda: () > me) 158 self.assertRaises(TypeError, lambda: () >= me) 159 160 self.assertRaises(TypeError, cmp, (), me) 161 self.assertRaises(TypeError, cmp, me, ()) 162 163############################################################################# 164# timedelta tests 165 166class TestTimeDelta(HarmlessMixedComparison, unittest.TestCase): 167 168 theclass = timedelta 169 170 def test_constructor(self): 171 eq = self.assertEqual 172 td = timedelta 173 174 # Check keyword args to constructor 175 eq(td(), td(weeks=0, days=0, hours=0, minutes=0, seconds=0, 176 milliseconds=0, microseconds=0)) 177 eq(td(1), td(days=1)) 178 eq(td(0, 1), td(seconds=1)) 179 eq(td(0, 0, 1), td(microseconds=1)) 180 eq(td(weeks=1), td(days=7)) 181 eq(td(days=1), td(hours=24)) 182 eq(td(hours=1), td(minutes=60)) 183 eq(td(minutes=1), td(seconds=60)) 184 eq(td(seconds=1), td(milliseconds=1000)) 185 eq(td(milliseconds=1), td(microseconds=1000)) 186 187 # Check float args to constructor 188 eq(td(weeks=1.0/7), td(days=1)) 189 eq(td(days=1.0/24), td(hours=1)) 190 eq(td(hours=1.0/60), td(minutes=1)) 191 eq(td(minutes=1.0/60), td(seconds=1)) 192 eq(td(seconds=0.001), td(milliseconds=1)) 193 eq(td(milliseconds=0.001), td(microseconds=1)) 194 195 def test_computations(self): 196 eq = self.assertEqual 197 td = timedelta 198 199 a = td(7) # One week 200 b = td(0, 60) # One minute 201 c = td(0, 0, 1000) # One millisecond 202 eq(a+b+c, td(7, 60, 1000)) 203 eq(a-b, td(6, 24*3600 - 60)) 204 eq(-a, td(-7)) 205 eq(+a, td(7)) 206 eq(-b, td(-1, 24*3600 - 60)) 207 eq(-c, td(-1, 24*3600 - 1, 999000)) 208 eq(abs(a), a) 209 eq(abs(-a), a) 210 eq(td(6, 24*3600), a) 211 eq(td(0, 0, 60*1000000), b) 212 eq(a*10, td(70)) 213 eq(a*10, 10*a) 214 eq(a*10L, 10*a) 215 eq(b*10, td(0, 600)) 216 eq(10*b, td(0, 600)) 217 eq(b*10L, td(0, 600)) 218 eq(c*10, td(0, 0, 10000)) 219 eq(10*c, td(0, 0, 10000)) 220 eq(c*10L, td(0, 0, 10000)) 221 eq(a*-1, -a) 222 eq(b*-2, -b-b) 223 eq(c*-2, -c+-c) 224 eq(b*(60*24), (b*60)*24) 225 eq(b*(60*24), (60*b)*24) 226 eq(c*1000, td(0, 1)) 227 eq(1000*c, td(0, 1)) 228 eq(a//7, td(1)) 229 eq(b//10, td(0, 6)) 230 eq(c//1000, td(0, 0, 1)) 231 eq(a//10, td(0, 7*24*360)) 232 eq(a//3600000, td(0, 0, 7*24*1000)) 233 234 # Issue #11576 235 eq(td(999999999, 86399, 999999) - td(999999999, 86399, 999998), 236 td(0, 0, 1)) 237 eq(td(999999999, 1, 1) - td(999999999, 1, 0), 238 td(0, 0, 1)) 239 240 241 def test_disallowed_computations(self): 242 a = timedelta(42) 243 244 # Add/sub ints, longs, floats should be illegal 245 for i in 1, 1L, 1.0: 246 self.assertRaises(TypeError, lambda: a+i) 247 self.assertRaises(TypeError, lambda: a-i) 248 self.assertRaises(TypeError, lambda: i+a) 249 self.assertRaises(TypeError, lambda: i-a) 250 251 # Mul/div by float isn't supported. 252 x = 2.3 253 self.assertRaises(TypeError, lambda: a*x) 254 self.assertRaises(TypeError, lambda: x*a) 255 self.assertRaises(TypeError, lambda: a/x) 256 self.assertRaises(TypeError, lambda: x/a) 257 self.assertRaises(TypeError, lambda: a // x) 258 self.assertRaises(TypeError, lambda: x // a) 259 260 # Division of int by timedelta doesn't make sense. 261 # Division by zero doesn't make sense. 262 for zero in 0, 0L: 263 self.assertRaises(TypeError, lambda: zero // a) 264 self.assertRaises(ZeroDivisionError, lambda: a // zero) 265 266 def test_basic_attributes(self): 267 days, seconds, us = 1, 7, 31 268 td = timedelta(days, seconds, us) 269 self.assertEqual(td.days, days) 270 self.assertEqual(td.seconds, seconds) 271 self.assertEqual(td.microseconds, us) 272 273 def test_total_seconds(self): 274 td = timedelta(days=365) 275 self.assertEqual(td.total_seconds(), 31536000.0) 276 for total_seconds in [123456.789012, -123456.789012, 0.123456, 0, 1e6]: 277 td = timedelta(seconds=total_seconds) 278 self.assertEqual(td.total_seconds(), total_seconds) 279 # Issue8644: Test that td.total_seconds() has the same 280 # accuracy as td / timedelta(seconds=1). 281 for ms in [-1, -2, -123]: 282 td = timedelta(microseconds=ms) 283 self.assertEqual(td.total_seconds(), 284 ((24*3600*td.days + td.seconds)*10**6 285 + td.microseconds)/10**6) 286 287 def test_carries(self): 288 t1 = timedelta(days=100, 289 weeks=-7, 290 hours=-24*(100-49), 291 minutes=-3, 292 seconds=12, 293 microseconds=(3*60 - 12) * 1e6 + 1) 294 t2 = timedelta(microseconds=1) 295 self.assertEqual(t1, t2) 296 297 def test_hash_equality(self): 298 t1 = timedelta(days=100, 299 weeks=-7, 300 hours=-24*(100-49), 301 minutes=-3, 302 seconds=12, 303 microseconds=(3*60 - 12) * 1000000) 304 t2 = timedelta() 305 self.assertEqual(hash(t1), hash(t2)) 306 307 t1 += timedelta(weeks=7) 308 t2 += timedelta(days=7*7) 309 self.assertEqual(t1, t2) 310 self.assertEqual(hash(t1), hash(t2)) 311 312 d = {t1: 1} 313 d[t2] = 2 314 self.assertEqual(len(d), 1) 315 self.assertEqual(d[t1], 2) 316 317 def test_pickling(self): 318 args = 12, 34, 56 319 orig = timedelta(*args) 320 for pickler, unpickler, proto in pickle_choices: 321 green = pickler.dumps(orig, proto) 322 derived = unpickler.loads(green) 323 self.assertEqual(orig, derived) 324 325 def test_compare(self): 326 t1 = timedelta(2, 3, 4) 327 t2 = timedelta(2, 3, 4) 328 self.assertTrue(t1 == t2) 329 self.assertTrue(t1 <= t2) 330 self.assertTrue(t1 >= t2) 331 self.assertFalse(t1 != t2) 332 self.assertFalse(t1 < t2) 333 self.assertFalse(t1 > t2) 334 self.assertEqual(cmp(t1, t2), 0) 335 self.assertEqual(cmp(t2, t1), 0) 336 337 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): 338 t2 = timedelta(*args) # this is larger than t1 339 self.assertTrue(t1 < t2) 340 self.assertTrue(t2 > t1) 341 self.assertTrue(t1 <= t2) 342 self.assertTrue(t2 >= t1) 343 self.assertTrue(t1 != t2) 344 self.assertTrue(t2 != t1) 345 self.assertFalse(t1 == t2) 346 self.assertFalse(t2 == t1) 347 self.assertFalse(t1 > t2) 348 self.assertFalse(t2 < t1) 349 self.assertFalse(t1 >= t2) 350 self.assertFalse(t2 <= t1) 351 self.assertEqual(cmp(t1, t2), -1) 352 self.assertEqual(cmp(t2, t1), 1) 353 354 for badarg in OTHERSTUFF: 355 self.assertEqual(t1 == badarg, False) 356 self.assertEqual(t1 != badarg, True) 357 self.assertEqual(badarg == t1, False) 358 self.assertEqual(badarg != t1, True) 359 360 self.assertRaises(TypeError, lambda: t1 <= badarg) 361 self.assertRaises(TypeError, lambda: t1 < badarg) 362 self.assertRaises(TypeError, lambda: t1 > badarg) 363 self.assertRaises(TypeError, lambda: t1 >= badarg) 364 self.assertRaises(TypeError, lambda: badarg <= t1) 365 self.assertRaises(TypeError, lambda: badarg < t1) 366 self.assertRaises(TypeError, lambda: badarg > t1) 367 self.assertRaises(TypeError, lambda: badarg >= t1) 368 369 def test_str(self): 370 td = timedelta 371 eq = self.assertEqual 372 373 eq(str(td(1)), "1 day, 0:00:00") 374 eq(str(td(-1)), "-1 day, 0:00:00") 375 eq(str(td(2)), "2 days, 0:00:00") 376 eq(str(td(-2)), "-2 days, 0:00:00") 377 378 eq(str(td(hours=12, minutes=58, seconds=59)), "12:58:59") 379 eq(str(td(hours=2, minutes=3, seconds=4)), "2:03:04") 380 eq(str(td(weeks=-30, hours=23, minutes=12, seconds=34)), 381 "-210 days, 23:12:34") 382 383 eq(str(td(milliseconds=1)), "0:00:00.001000") 384 eq(str(td(microseconds=3)), "0:00:00.000003") 385 386 eq(str(td(days=999999999, hours=23, minutes=59, seconds=59, 387 microseconds=999999)), 388 "999999999 days, 23:59:59.999999") 389 390 def test_roundtrip(self): 391 for td in (timedelta(days=999999999, hours=23, minutes=59, 392 seconds=59, microseconds=999999), 393 timedelta(days=-999999999), 394 timedelta(days=1, seconds=2, microseconds=3)): 395 396 # Verify td -> string -> td identity. 397 s = repr(td) 398 self.assertTrue(s.startswith('datetime.')) 399 s = s[9:] 400 td2 = eval(s) 401 self.assertEqual(td, td2) 402 403 # Verify identity via reconstructing from pieces. 404 td2 = timedelta(td.days, td.seconds, td.microseconds) 405 self.assertEqual(td, td2) 406 407 def test_resolution_info(self): 408 self.assertIsInstance(timedelta.min, timedelta) 409 self.assertIsInstance(timedelta.max, timedelta) 410 self.assertIsInstance(timedelta.resolution, timedelta) 411 self.assertTrue(timedelta.max > timedelta.min) 412 self.assertEqual(timedelta.min, timedelta(-999999999)) 413 self.assertEqual(timedelta.max, timedelta(999999999, 24*3600-1, 1e6-1)) 414 self.assertEqual(timedelta.resolution, timedelta(0, 0, 1)) 415 416 def test_overflow(self): 417 tiny = timedelta.resolution 418 419 td = timedelta.min + tiny 420 td -= tiny # no problem 421 self.assertRaises(OverflowError, td.__sub__, tiny) 422 self.assertRaises(OverflowError, td.__add__, -tiny) 423 424 td = timedelta.max - tiny 425 td += tiny # no problem 426 self.assertRaises(OverflowError, td.__add__, tiny) 427 self.assertRaises(OverflowError, td.__sub__, -tiny) 428 429 self.assertRaises(OverflowError, lambda: -timedelta.max) 430 431 def test_microsecond_rounding(self): 432 td = timedelta 433 eq = self.assertEqual 434 435 # Single-field rounding. 436 eq(td(milliseconds=0.4/1000), td(0)) # rounds to 0 437 eq(td(milliseconds=-0.4/1000), td(0)) # rounds to 0 438 eq(td(milliseconds=0.6/1000), td(microseconds=1)) 439 eq(td(milliseconds=-0.6/1000), td(microseconds=-1)) 440 441 # Rounding due to contributions from more than one field. 442 us_per_hour = 3600e6 443 us_per_day = us_per_hour * 24 444 eq(td(days=.4/us_per_day), td(0)) 445 eq(td(hours=.2/us_per_hour), td(0)) 446 eq(td(days=.4/us_per_day, hours=.2/us_per_hour), td(microseconds=1)) 447 448 eq(td(days=-.4/us_per_day), td(0)) 449 eq(td(hours=-.2/us_per_hour), td(0)) 450 eq(td(days=-.4/us_per_day, hours=-.2/us_per_hour), td(microseconds=-1)) 451 452 def test_massive_normalization(self): 453 td = timedelta(microseconds=-1) 454 self.assertEqual((td.days, td.seconds, td.microseconds), 455 (-1, 24*3600-1, 999999)) 456 457 def test_bool(self): 458 self.assertTrue(timedelta(1)) 459 self.assertTrue(timedelta(0, 1)) 460 self.assertTrue(timedelta(0, 0, 1)) 461 self.assertTrue(timedelta(microseconds=1)) 462 self.assertFalse(timedelta(0)) 463 464 def test_subclass_timedelta(self): 465 466 class T(timedelta): 467 @staticmethod 468 def from_td(td): 469 return T(td.days, td.seconds, td.microseconds) 470 471 def as_hours(self): 472 sum = (self.days * 24 + 473 self.seconds / 3600.0 + 474 self.microseconds / 3600e6) 475 return round(sum) 476 477 t1 = T(days=1) 478 self.assertIs(type(t1), T) 479 self.assertEqual(t1.as_hours(), 24) 480 481 t2 = T(days=-1, seconds=-3600) 482 self.assertIs(type(t2), T) 483 self.assertEqual(t2.as_hours(), -25) 484 485 t3 = t1 + t2 486 self.assertIs(type(t3), timedelta) 487 t4 = T.from_td(t3) 488 self.assertIs(type(t4), T) 489 self.assertEqual(t3.days, t4.days) 490 self.assertEqual(t3.seconds, t4.seconds) 491 self.assertEqual(t3.microseconds, t4.microseconds) 492 self.assertEqual(str(t3), str(t4)) 493 self.assertEqual(t4.as_hours(), -1) 494 495############################################################################# 496# date tests 497 498class TestDateOnly(unittest.TestCase): 499 # Tests here won't pass if also run on datetime objects, so don't 500 # subclass this to test datetimes too. 501 502 def test_delta_non_days_ignored(self): 503 dt = date(2000, 1, 2) 504 delta = timedelta(days=1, hours=2, minutes=3, seconds=4, 505 microseconds=5) 506 days = timedelta(delta.days) 507 self.assertEqual(days, timedelta(1)) 508 509 dt2 = dt + delta 510 self.assertEqual(dt2, dt + days) 511 512 dt2 = delta + dt 513 self.assertEqual(dt2, dt + days) 514 515 dt2 = dt - delta 516 self.assertEqual(dt2, dt - days) 517 518 delta = -delta 519 days = timedelta(delta.days) 520 self.assertEqual(days, timedelta(-2)) 521 522 dt2 = dt + delta 523 self.assertEqual(dt2, dt + days) 524 525 dt2 = delta + dt 526 self.assertEqual(dt2, dt + days) 527 528 dt2 = dt - delta 529 self.assertEqual(dt2, dt - days) 530 531class SubclassDate(date): 532 sub_var = 1 533 534class TestDate(HarmlessMixedComparison, unittest.TestCase): 535 # Tests here should pass for both dates and datetimes, except for a 536 # few tests that TestDateTime overrides. 537 538 theclass = date 539 540 def test_basic_attributes(self): 541 dt = self.theclass(2002, 3, 1) 542 self.assertEqual(dt.year, 2002) 543 self.assertEqual(dt.month, 3) 544 self.assertEqual(dt.day, 1) 545 546 def test_roundtrip(self): 547 for dt in (self.theclass(1, 2, 3), 548 self.theclass.today()): 549 # Verify dt -> string -> date identity. 550 s = repr(dt) 551 self.assertTrue(s.startswith('datetime.')) 552 s = s[9:] 553 dt2 = eval(s) 554 self.assertEqual(dt, dt2) 555 556 # Verify identity via reconstructing from pieces. 557 dt2 = self.theclass(dt.year, dt.month, dt.day) 558 self.assertEqual(dt, dt2) 559 560 def test_ordinal_conversions(self): 561 # Check some fixed values. 562 for y, m, d, n in [(1, 1, 1, 1), # calendar origin 563 (1, 12, 31, 365), 564 (2, 1, 1, 366), 565 # first example from "Calendrical Calculations" 566 (1945, 11, 12, 710347)]: 567 d = self.theclass(y, m, d) 568 self.assertEqual(n, d.toordinal()) 569 fromord = self.theclass.fromordinal(n) 570 self.assertEqual(d, fromord) 571 if hasattr(fromord, "hour"): 572 # if we're checking something fancier than a date, verify 573 # the extra fields have been zeroed out 574 self.assertEqual(fromord.hour, 0) 575 self.assertEqual(fromord.minute, 0) 576 self.assertEqual(fromord.second, 0) 577 self.assertEqual(fromord.microsecond, 0) 578 579 # Check first and last days of year spottily across the whole 580 # range of years supported. 581 for year in xrange(MINYEAR, MAXYEAR+1, 7): 582 # Verify (year, 1, 1) -> ordinal -> y, m, d is identity. 583 d = self.theclass(year, 1, 1) 584 n = d.toordinal() 585 d2 = self.theclass.fromordinal(n) 586 self.assertEqual(d, d2) 587 # Verify that moving back a day gets to the end of year-1. 588 if year > 1: 589 d = self.theclass.fromordinal(n-1) 590 d2 = self.theclass(year-1, 12, 31) 591 self.assertEqual(d, d2) 592 self.assertEqual(d2.toordinal(), n-1) 593 594 # Test every day in a leap-year and a non-leap year. 595 dim = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 596 for year, isleap in (2000, True), (2002, False): 597 n = self.theclass(year, 1, 1).toordinal() 598 for month, maxday in zip(range(1, 13), dim): 599 if month == 2 and isleap: 600 maxday += 1 601 for day in range(1, maxday+1): 602 d = self.theclass(year, month, day) 603 self.assertEqual(d.toordinal(), n) 604 self.assertEqual(d, self.theclass.fromordinal(n)) 605 n += 1 606 607 def test_extreme_ordinals(self): 608 a = self.theclass.min 609 a = self.theclass(a.year, a.month, a.day) # get rid of time parts 610 aord = a.toordinal() 611 b = a.fromordinal(aord) 612 self.assertEqual(a, b) 613 614 self.assertRaises(ValueError, lambda: a.fromordinal(aord - 1)) 615 616 b = a + timedelta(days=1) 617 self.assertEqual(b.toordinal(), aord + 1) 618 self.assertEqual(b, self.theclass.fromordinal(aord + 1)) 619 620 a = self.theclass.max 621 a = self.theclass(a.year, a.month, a.day) # get rid of time parts 622 aord = a.toordinal() 623 b = a.fromordinal(aord) 624 self.assertEqual(a, b) 625 626 self.assertRaises(ValueError, lambda: a.fromordinal(aord + 1)) 627 628 b = a - timedelta(days=1) 629 self.assertEqual(b.toordinal(), aord - 1) 630 self.assertEqual(b, self.theclass.fromordinal(aord - 1)) 631 632 def test_bad_constructor_arguments(self): 633 # bad years 634 self.theclass(MINYEAR, 1, 1) # no exception 635 self.theclass(MAXYEAR, 1, 1) # no exception 636 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) 637 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) 638 # bad months 639 self.theclass(2000, 1, 1) # no exception 640 self.theclass(2000, 12, 1) # no exception 641 self.assertRaises(ValueError, self.theclass, 2000, 0, 1) 642 self.assertRaises(ValueError, self.theclass, 2000, 13, 1) 643 # bad days 644 self.theclass(2000, 2, 29) # no exception 645 self.theclass(2004, 2, 29) # no exception 646 self.theclass(2400, 2, 29) # no exception 647 self.assertRaises(ValueError, self.theclass, 2000, 2, 30) 648 self.assertRaises(ValueError, self.theclass, 2001, 2, 29) 649 self.assertRaises(ValueError, self.theclass, 2100, 2, 29) 650 self.assertRaises(ValueError, self.theclass, 1900, 2, 29) 651 self.assertRaises(ValueError, self.theclass, 2000, 1, 0) 652 self.assertRaises(ValueError, self.theclass, 2000, 1, 32) 653 654 def test_hash_equality(self): 655 d = self.theclass(2000, 12, 31) 656 # same thing 657 e = self.theclass(2000, 12, 31) 658 self.assertEqual(d, e) 659 self.assertEqual(hash(d), hash(e)) 660 661 dic = {d: 1} 662 dic[e] = 2 663 self.assertEqual(len(dic), 1) 664 self.assertEqual(dic[d], 2) 665 self.assertEqual(dic[e], 2) 666 667 d = self.theclass(2001, 1, 1) 668 # same thing 669 e = self.theclass(2001, 1, 1) 670 self.assertEqual(d, e) 671 self.assertEqual(hash(d), hash(e)) 672 673 dic = {d: 1} 674 dic[e] = 2 675 self.assertEqual(len(dic), 1) 676 self.assertEqual(dic[d], 2) 677 self.assertEqual(dic[e], 2) 678 679 def test_computations(self): 680 a = self.theclass(2002, 1, 31) 681 b = self.theclass(1956, 1, 31) 682 683 diff = a-b 684 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) 685 self.assertEqual(diff.seconds, 0) 686 self.assertEqual(diff.microseconds, 0) 687 688 day = timedelta(1) 689 week = timedelta(7) 690 a = self.theclass(2002, 3, 2) 691 self.assertEqual(a + day, self.theclass(2002, 3, 3)) 692 self.assertEqual(day + a, self.theclass(2002, 3, 3)) 693 self.assertEqual(a - day, self.theclass(2002, 3, 1)) 694 self.assertEqual(-day + a, self.theclass(2002, 3, 1)) 695 self.assertEqual(a + week, self.theclass(2002, 3, 9)) 696 self.assertEqual(a - week, self.theclass(2002, 2, 23)) 697 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1)) 698 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3)) 699 self.assertEqual((a + week) - a, week) 700 self.assertEqual((a + day) - a, day) 701 self.assertEqual((a - week) - a, -week) 702 self.assertEqual((a - day) - a, -day) 703 self.assertEqual(a - (a + week), -week) 704 self.assertEqual(a - (a + day), -day) 705 self.assertEqual(a - (a - week), week) 706 self.assertEqual(a - (a - day), day) 707 708 # Add/sub ints, longs, floats should be illegal 709 for i in 1, 1L, 1.0: 710 self.assertRaises(TypeError, lambda: a+i) 711 self.assertRaises(TypeError, lambda: a-i) 712 self.assertRaises(TypeError, lambda: i+a) 713 self.assertRaises(TypeError, lambda: i-a) 714 715 # delta - date is senseless. 716 self.assertRaises(TypeError, lambda: day - a) 717 # mixing date and (delta or date) via * or // is senseless 718 self.assertRaises(TypeError, lambda: day * a) 719 self.assertRaises(TypeError, lambda: a * day) 720 self.assertRaises(TypeError, lambda: day // a) 721 self.assertRaises(TypeError, lambda: a // day) 722 self.assertRaises(TypeError, lambda: a * a) 723 self.assertRaises(TypeError, lambda: a // a) 724 # date + date is senseless 725 self.assertRaises(TypeError, lambda: a + a) 726 727 def test_overflow(self): 728 tiny = self.theclass.resolution 729 730 for delta in [tiny, timedelta(1), timedelta(2)]: 731 dt = self.theclass.min + delta 732 dt -= delta # no problem 733 self.assertRaises(OverflowError, dt.__sub__, delta) 734 self.assertRaises(OverflowError, dt.__add__, -delta) 735 736 dt = self.theclass.max - delta 737 dt += delta # no problem 738 self.assertRaises(OverflowError, dt.__add__, delta) 739 self.assertRaises(OverflowError, dt.__sub__, -delta) 740 741 def test_fromtimestamp(self): 742 import time 743 744 # Try an arbitrary fixed value. 745 year, month, day = 1999, 9, 19 746 ts = time.mktime((year, month, day, 0, 0, 0, 0, 0, -1)) 747 d = self.theclass.fromtimestamp(ts) 748 self.assertEqual(d.year, year) 749 self.assertEqual(d.month, month) 750 self.assertEqual(d.day, day) 751 752 def test_insane_fromtimestamp(self): 753 # It's possible that some platform maps time_t to double, 754 # and that this test will fail there. This test should 755 # exempt such platforms (provided they return reasonable 756 # results!). 757 for insane in -1e200, 1e200: 758 self.assertRaises(ValueError, self.theclass.fromtimestamp, 759 insane) 760 761 def test_today(self): 762 import time 763 764 # We claim that today() is like fromtimestamp(time.time()), so 765 # prove it. 766 for dummy in range(3): 767 today = self.theclass.today() 768 ts = time.time() 769 todayagain = self.theclass.fromtimestamp(ts) 770 if today == todayagain: 771 break 772 # There are several legit reasons that could fail: 773 # 1. It recently became midnight, between the today() and the 774 # time() calls. 775 # 2. The platform time() has such fine resolution that we'll 776 # never get the same value twice. 777 # 3. The platform time() has poor resolution, and we just 778 # happened to call today() right before a resolution quantum 779 # boundary. 780 # 4. The system clock got fiddled between calls. 781 # In any case, wait a little while and try again. 782 time.sleep(0.1) 783 784 # It worked or it didn't. If it didn't, assume it's reason #2, and 785 # let the test pass if they're within half a second of each other. 786 if today != todayagain: 787 self.assertAlmostEqual(todayagain, today, 788 delta=timedelta(seconds=0.5)) 789 790 def test_weekday(self): 791 for i in range(7): 792 # March 4, 2002 is a Monday 793 self.assertEqual(self.theclass(2002, 3, 4+i).weekday(), i) 794 self.assertEqual(self.theclass(2002, 3, 4+i).isoweekday(), i+1) 795 # January 2, 1956 is a Monday 796 self.assertEqual(self.theclass(1956, 1, 2+i).weekday(), i) 797 self.assertEqual(self.theclass(1956, 1, 2+i).isoweekday(), i+1) 798 799 def test_isocalendar(self): 800 # Check examples from 801 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm 802 for i in range(7): 803 d = self.theclass(2003, 12, 22+i) 804 self.assertEqual(d.isocalendar(), (2003, 52, i+1)) 805 d = self.theclass(2003, 12, 29) + timedelta(i) 806 self.assertEqual(d.isocalendar(), (2004, 1, i+1)) 807 d = self.theclass(2004, 1, 5+i) 808 self.assertEqual(d.isocalendar(), (2004, 2, i+1)) 809 d = self.theclass(2009, 12, 21+i) 810 self.assertEqual(d.isocalendar(), (2009, 52, i+1)) 811 d = self.theclass(2009, 12, 28) + timedelta(i) 812 self.assertEqual(d.isocalendar(), (2009, 53, i+1)) 813 d = self.theclass(2010, 1, 4+i) 814 self.assertEqual(d.isocalendar(), (2010, 1, i+1)) 815 816 def test_iso_long_years(self): 817 # Calculate long ISO years and compare to table from 818 # http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm 819 ISO_LONG_YEARS_TABLE = """ 820 4 32 60 88 821 9 37 65 93 822 15 43 71 99 823 20 48 76 824 26 54 82 825 826 105 133 161 189 827 111 139 167 195 828 116 144 172 829 122 150 178 830 128 156 184 831 832 201 229 257 285 833 207 235 263 291 834 212 240 268 296 835 218 246 274 836 224 252 280 837 838 303 331 359 387 839 308 336 364 392 840 314 342 370 398 841 320 348 376 842 325 353 381 843 """ 844 iso_long_years = map(int, ISO_LONG_YEARS_TABLE.split()) 845 iso_long_years.sort() 846 L = [] 847 for i in range(400): 848 d = self.theclass(2000+i, 12, 31) 849 d1 = self.theclass(1600+i, 12, 31) 850 self.assertEqual(d.isocalendar()[1:], d1.isocalendar()[1:]) 851 if d.isocalendar()[1] == 53: 852 L.append(i) 853 self.assertEqual(L, iso_long_years) 854 855 def test_isoformat(self): 856 t = self.theclass(2, 3, 2) 857 self.assertEqual(t.isoformat(), "0002-03-02") 858 859 def test_ctime(self): 860 t = self.theclass(2002, 3, 2) 861 self.assertEqual(t.ctime(), "Sat Mar 2 00:00:00 2002") 862 863 def test_strftime(self): 864 t = self.theclass(2005, 3, 2) 865 self.assertEqual(t.strftime("m:%m d:%d y:%y"), "m:03 d:02 y:05") 866 self.assertEqual(t.strftime(""), "") # SF bug #761337 867 self.assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784 868 869 self.assertRaises(TypeError, t.strftime) # needs an arg 870 self.assertRaises(TypeError, t.strftime, "one", "two") # too many args 871 self.assertRaises(TypeError, t.strftime, 42) # arg wrong type 872 873 # test that unicode input is allowed (issue 2782) 874 self.assertEqual(t.strftime(u"%m"), "03") 875 876 # A naive object replaces %z and %Z w/ empty strings. 877 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 878 879 #make sure that invalid format specifiers are handled correctly 880 #self.assertRaises(ValueError, t.strftime, "%e") 881 #self.assertRaises(ValueError, t.strftime, "%") 882 #self.assertRaises(ValueError, t.strftime, "%#") 883 884 #oh well, some systems just ignore those invalid ones. 885 #at least, exercise them to make sure that no crashes 886 #are generated 887 for f in ["%e", "%", "%#"]: 888 try: 889 t.strftime(f) 890 except ValueError: 891 pass 892 893 #check that this standard extension works 894 t.strftime("%f") 895 896 897 def test_format(self): 898 dt = self.theclass(2007, 9, 10) 899 self.assertEqual(dt.__format__(''), str(dt)) 900 901 # check that a derived class's __str__() gets called 902 class A(self.theclass): 903 def __str__(self): 904 return 'A' 905 a = A(2007, 9, 10) 906 self.assertEqual(a.__format__(''), 'A') 907 908 # check that a derived class's strftime gets called 909 class B(self.theclass): 910 def strftime(self, format_spec): 911 return 'B' 912 b = B(2007, 9, 10) 913 self.assertEqual(b.__format__(''), str(dt)) 914 915 for fmt in ["m:%m d:%d y:%y", 916 "m:%m d:%d y:%y H:%H M:%M S:%S", 917 "%z %Z", 918 ]: 919 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) 920 self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) 921 self.assertEqual(b.__format__(fmt), 'B') 922 923 def test_resolution_info(self): 924 self.assertIsInstance(self.theclass.min, self.theclass) 925 self.assertIsInstance(self.theclass.max, self.theclass) 926 self.assertIsInstance(self.theclass.resolution, timedelta) 927 self.assertTrue(self.theclass.max > self.theclass.min) 928 929 def test_extreme_timedelta(self): 930 big = self.theclass.max - self.theclass.min 931 # 3652058 days, 23 hours, 59 minutes, 59 seconds, 999999 microseconds 932 n = (big.days*24*3600 + big.seconds)*1000000 + big.microseconds 933 # n == 315537897599999999 ~= 2**58.13 934 justasbig = timedelta(0, 0, n) 935 self.assertEqual(big, justasbig) 936 self.assertEqual(self.theclass.min + big, self.theclass.max) 937 self.assertEqual(self.theclass.max - big, self.theclass.min) 938 939 def test_timetuple(self): 940 for i in range(7): 941 # January 2, 1956 is a Monday (0) 942 d = self.theclass(1956, 1, 2+i) 943 t = d.timetuple() 944 self.assertEqual(t, (1956, 1, 2+i, 0, 0, 0, i, 2+i, -1)) 945 # February 1, 1956 is a Wednesday (2) 946 d = self.theclass(1956, 2, 1+i) 947 t = d.timetuple() 948 self.assertEqual(t, (1956, 2, 1+i, 0, 0, 0, (2+i)%7, 32+i, -1)) 949 # March 1, 1956 is a Thursday (3), and is the 31+29+1 = 61st day 950 # of the year. 951 d = self.theclass(1956, 3, 1+i) 952 t = d.timetuple() 953 self.assertEqual(t, (1956, 3, 1+i, 0, 0, 0, (3+i)%7, 61+i, -1)) 954 self.assertEqual(t.tm_year, 1956) 955 self.assertEqual(t.tm_mon, 3) 956 self.assertEqual(t.tm_mday, 1+i) 957 self.assertEqual(t.tm_hour, 0) 958 self.assertEqual(t.tm_min, 0) 959 self.assertEqual(t.tm_sec, 0) 960 self.assertEqual(t.tm_wday, (3+i)%7) 961 self.assertEqual(t.tm_yday, 61+i) 962 self.assertEqual(t.tm_isdst, -1) 963 964 def test_pickling(self): 965 args = 6, 7, 23 966 orig = self.theclass(*args) 967 for pickler, unpickler, proto in pickle_choices: 968 green = pickler.dumps(orig, proto) 969 derived = unpickler.loads(green) 970 self.assertEqual(orig, derived) 971 972 def test_compare(self): 973 t1 = self.theclass(2, 3, 4) 974 t2 = self.theclass(2, 3, 4) 975 self.assertTrue(t1 == t2) 976 self.assertTrue(t1 <= t2) 977 self.assertTrue(t1 >= t2) 978 self.assertFalse(t1 != t2) 979 self.assertFalse(t1 < t2) 980 self.assertFalse(t1 > t2) 981 self.assertEqual(cmp(t1, t2), 0) 982 self.assertEqual(cmp(t2, t1), 0) 983 984 for args in (3, 3, 3), (2, 4, 4), (2, 3, 5): 985 t2 = self.theclass(*args) # this is larger than t1 986 self.assertTrue(t1 < t2) 987 self.assertTrue(t2 > t1) 988 self.assertTrue(t1 <= t2) 989 self.assertTrue(t2 >= t1) 990 self.assertTrue(t1 != t2) 991 self.assertTrue(t2 != t1) 992 self.assertFalse(t1 == t2) 993 self.assertFalse(t2 == t1) 994 self.assertFalse(t1 > t2) 995 self.assertFalse(t2 < t1) 996 self.assertFalse(t1 >= t2) 997 self.assertFalse(t2 <= t1) 998 self.assertEqual(cmp(t1, t2), -1) 999 self.assertEqual(cmp(t2, t1), 1) 1000 1001 for badarg in OTHERSTUFF: 1002 self.assertEqual(t1 == badarg, False) 1003 self.assertEqual(t1 != badarg, True) 1004 self.assertEqual(badarg == t1, False) 1005 self.assertEqual(badarg != t1, True) 1006 1007 self.assertRaises(TypeError, lambda: t1 < badarg) 1008 self.assertRaises(TypeError, lambda: t1 > badarg) 1009 self.assertRaises(TypeError, lambda: t1 >= badarg) 1010 self.assertRaises(TypeError, lambda: badarg <= t1) 1011 self.assertRaises(TypeError, lambda: badarg < t1) 1012 self.assertRaises(TypeError, lambda: badarg > t1) 1013 self.assertRaises(TypeError, lambda: badarg >= t1) 1014 1015 def test_mixed_compare(self): 1016 our = self.theclass(2000, 4, 5) 1017 self.assertRaises(TypeError, cmp, our, 1) 1018 self.assertRaises(TypeError, cmp, 1, our) 1019 1020 class AnotherDateTimeClass(object): 1021 def __cmp__(self, other): 1022 # Return "equal" so calling this can't be confused with 1023 # compare-by-address (which never says "equal" for distinct 1024 # objects). 1025 return 0 1026 __hash__ = None # Silence Py3k warning 1027 1028 # This still errors, because date and datetime comparison raise 1029 # TypeError instead of NotImplemented when they don't know what to 1030 # do, in order to stop comparison from falling back to the default 1031 # compare-by-address. 1032 their = AnotherDateTimeClass() 1033 self.assertRaises(TypeError, cmp, our, their) 1034 # Oops: The next stab raises TypeError in the C implementation, 1035 # but not in the Python implementation of datetime. The difference 1036 # is due to that the Python implementation defines __cmp__ but 1037 # the C implementation defines tp_richcompare. This is more pain 1038 # to fix than it's worth, so commenting out the test. 1039 # self.assertEqual(cmp(their, our), 0) 1040 1041 # But date and datetime comparison return NotImplemented instead if the 1042 # other object has a timetuple attr. This gives the other object a 1043 # chance to do the comparison. 1044 class Comparable(AnotherDateTimeClass): 1045 def timetuple(self): 1046 return () 1047 1048 their = Comparable() 1049 self.assertEqual(cmp(our, their), 0) 1050 self.assertEqual(cmp(their, our), 0) 1051 self.assertTrue(our == their) 1052 self.assertTrue(their == our) 1053 1054 def test_bool(self): 1055 # All dates are considered true. 1056 self.assertTrue(self.theclass.min) 1057 self.assertTrue(self.theclass.max) 1058 1059 def test_strftime_out_of_range(self): 1060 # For nasty technical reasons, we can't handle years before 1900. 1061 cls = self.theclass 1062 self.assertEqual(cls(1900, 1, 1).strftime("%Y"), "1900") 1063 for y in 1, 49, 51, 99, 100, 1000, 1899: 1064 self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") 1065 1066 def test_replace(self): 1067 cls = self.theclass 1068 args = [1, 2, 3] 1069 base = cls(*args) 1070 self.assertEqual(base, base.replace()) 1071 1072 i = 0 1073 for name, newval in (("year", 2), 1074 ("month", 3), 1075 ("day", 4)): 1076 newargs = args[:] 1077 newargs[i] = newval 1078 expected = cls(*newargs) 1079 got = base.replace(**{name: newval}) 1080 self.assertEqual(expected, got) 1081 i += 1 1082 1083 # Out of bounds. 1084 base = cls(2000, 2, 29) 1085 self.assertRaises(ValueError, base.replace, year=2001) 1086 1087 def test_subclass_date(self): 1088 1089 class C(self.theclass): 1090 theAnswer = 42 1091 1092 def __new__(cls, *args, **kws): 1093 temp = kws.copy() 1094 extra = temp.pop('extra') 1095 result = self.theclass.__new__(cls, *args, **temp) 1096 result.extra = extra 1097 return result 1098 1099 def newmeth(self, start): 1100 return start + self.year + self.month 1101 1102 args = 2003, 4, 14 1103 1104 dt1 = self.theclass(*args) 1105 dt2 = C(*args, **{'extra': 7}) 1106 1107 self.assertEqual(dt2.__class__, C) 1108 self.assertEqual(dt2.theAnswer, 42) 1109 self.assertEqual(dt2.extra, 7) 1110 self.assertEqual(dt1.toordinal(), dt2.toordinal()) 1111 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) 1112 1113 def test_pickling_subclass_date(self): 1114 1115 args = 6, 7, 23 1116 orig = SubclassDate(*args) 1117 for pickler, unpickler, proto in pickle_choices: 1118 green = pickler.dumps(orig, proto) 1119 derived = unpickler.loads(green) 1120 self.assertEqual(orig, derived) 1121 1122 def test_backdoor_resistance(self): 1123 # For fast unpickling, the constructor accepts a pickle string. 1124 # This is a low-overhead backdoor. A user can (by intent or 1125 # mistake) pass a string directly, which (if it's the right length) 1126 # will get treated like a pickle, and bypass the normal sanity 1127 # checks in the constructor. This can create insane objects. 1128 # The constructor doesn't want to burn the time to validate all 1129 # fields, but does check the month field. This stops, e.g., 1130 # datetime.datetime('1995-03-25') from yielding an insane object. 1131 base = '1995-03-25' 1132 if not issubclass(self.theclass, datetime): 1133 base = base[:4] 1134 for month_byte in '9', chr(0), chr(13), '\xff': 1135 self.assertRaises(TypeError, self.theclass, 1136 base[:2] + month_byte + base[3:]) 1137 for ord_byte in range(1, 13): 1138 # This shouldn't blow up because of the month byte alone. If 1139 # the implementation changes to do more-careful checking, it may 1140 # blow up because other fields are insane. 1141 self.theclass(base[:2] + chr(ord_byte) + base[3:]) 1142 1143############################################################################# 1144# datetime tests 1145 1146class SubclassDatetime(datetime): 1147 sub_var = 1 1148 1149class TestDateTime(TestDate): 1150 1151 theclass = datetime 1152 1153 def test_basic_attributes(self): 1154 dt = self.theclass(2002, 3, 1, 12, 0) 1155 self.assertEqual(dt.year, 2002) 1156 self.assertEqual(dt.month, 3) 1157 self.assertEqual(dt.day, 1) 1158 self.assertEqual(dt.hour, 12) 1159 self.assertEqual(dt.minute, 0) 1160 self.assertEqual(dt.second, 0) 1161 self.assertEqual(dt.microsecond, 0) 1162 1163 def test_basic_attributes_nonzero(self): 1164 # Make sure all attributes are non-zero so bugs in 1165 # bit-shifting access show up. 1166 dt = self.theclass(2002, 3, 1, 12, 59, 59, 8000) 1167 self.assertEqual(dt.year, 2002) 1168 self.assertEqual(dt.month, 3) 1169 self.assertEqual(dt.day, 1) 1170 self.assertEqual(dt.hour, 12) 1171 self.assertEqual(dt.minute, 59) 1172 self.assertEqual(dt.second, 59) 1173 self.assertEqual(dt.microsecond, 8000) 1174 1175 def test_roundtrip(self): 1176 for dt in (self.theclass(1, 2, 3, 4, 5, 6, 7), 1177 self.theclass.now()): 1178 # Verify dt -> string -> datetime identity. 1179 s = repr(dt) 1180 self.assertTrue(s.startswith('datetime.')) 1181 s = s[9:] 1182 dt2 = eval(s) 1183 self.assertEqual(dt, dt2) 1184 1185 # Verify identity via reconstructing from pieces. 1186 dt2 = self.theclass(dt.year, dt.month, dt.day, 1187 dt.hour, dt.minute, dt.second, 1188 dt.microsecond) 1189 self.assertEqual(dt, dt2) 1190 1191 def test_isoformat(self): 1192 t = self.theclass(2, 3, 2, 4, 5, 1, 123) 1193 self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") 1194 self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123") 1195 self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123") 1196 self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123") 1197 # str is ISO format with the separator forced to a blank. 1198 self.assertEqual(str(t), "0002-03-02 04:05:01.000123") 1199 1200 t = self.theclass(2, 3, 2) 1201 self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") 1202 self.assertEqual(t.isoformat('T'), "0002-03-02T00:00:00") 1203 self.assertEqual(t.isoformat(' '), "0002-03-02 00:00:00") 1204 # str is ISO format with the separator forced to a blank. 1205 self.assertEqual(str(t), "0002-03-02 00:00:00") 1206 1207 def test_format(self): 1208 dt = self.theclass(2007, 9, 10, 4, 5, 1, 123) 1209 self.assertEqual(dt.__format__(''), str(dt)) 1210 1211 # check that a derived class's __str__() gets called 1212 class A(self.theclass): 1213 def __str__(self): 1214 return 'A' 1215 a = A(2007, 9, 10, 4, 5, 1, 123) 1216 self.assertEqual(a.__format__(''), 'A') 1217 1218 # check that a derived class's strftime gets called 1219 class B(self.theclass): 1220 def strftime(self, format_spec): 1221 return 'B' 1222 b = B(2007, 9, 10, 4, 5, 1, 123) 1223 self.assertEqual(b.__format__(''), str(dt)) 1224 1225 for fmt in ["m:%m d:%d y:%y", 1226 "m:%m d:%d y:%y H:%H M:%M S:%S", 1227 "%z %Z", 1228 ]: 1229 self.assertEqual(dt.__format__(fmt), dt.strftime(fmt)) 1230 self.assertEqual(a.__format__(fmt), dt.strftime(fmt)) 1231 self.assertEqual(b.__format__(fmt), 'B') 1232 1233 def test_more_ctime(self): 1234 # Test fields that TestDate doesn't touch. 1235 import time 1236 1237 t = self.theclass(2002, 3, 2, 18, 3, 5, 123) 1238 self.assertEqual(t.ctime(), "Sat Mar 2 18:03:05 2002") 1239 # Oops! The next line fails on Win2K under MSVC 6, so it's commented 1240 # out. The difference is that t.ctime() produces " 2" for the day, 1241 # but platform ctime() produces "02" for the day. According to 1242 # C99, t.ctime() is correct here. 1243 # self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) 1244 1245 # So test a case where that difference doesn't matter. 1246 t = self.theclass(2002, 3, 22, 18, 3, 5, 123) 1247 self.assertEqual(t.ctime(), time.ctime(time.mktime(t.timetuple()))) 1248 1249 def test_tz_independent_comparing(self): 1250 dt1 = self.theclass(2002, 3, 1, 9, 0, 0) 1251 dt2 = self.theclass(2002, 3, 1, 10, 0, 0) 1252 dt3 = self.theclass(2002, 3, 1, 9, 0, 0) 1253 self.assertEqual(dt1, dt3) 1254 self.assertTrue(dt2 > dt3) 1255 1256 # Make sure comparison doesn't forget microseconds, and isn't done 1257 # via comparing a float timestamp (an IEEE double doesn't have enough 1258 # precision to span microsecond resolution across years 1 thru 9999, 1259 # so comparing via timestamp necessarily calls some distinct values 1260 # equal). 1261 dt1 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999998) 1262 us = timedelta(microseconds=1) 1263 dt2 = dt1 + us 1264 self.assertEqual(dt2 - dt1, us) 1265 self.assertTrue(dt1 < dt2) 1266 1267 def test_strftime_with_bad_tzname_replace(self): 1268 # verify ok if tzinfo.tzname().replace() returns a non-string 1269 class MyTzInfo(FixedOffset): 1270 def tzname(self, dt): 1271 class MyStr(str): 1272 def replace(self, *args): 1273 return None 1274 return MyStr('name') 1275 t = self.theclass(2005, 3, 2, 0, 0, 0, 0, MyTzInfo(3, 'name')) 1276 self.assertRaises(TypeError, t.strftime, '%Z') 1277 1278 def test_bad_constructor_arguments(self): 1279 # bad years 1280 self.theclass(MINYEAR, 1, 1) # no exception 1281 self.theclass(MAXYEAR, 1, 1) # no exception 1282 self.assertRaises(ValueError, self.theclass, MINYEAR-1, 1, 1) 1283 self.assertRaises(ValueError, self.theclass, MAXYEAR+1, 1, 1) 1284 # bad months 1285 self.theclass(2000, 1, 1) # no exception 1286 self.theclass(2000, 12, 1) # no exception 1287 self.assertRaises(ValueError, self.theclass, 2000, 0, 1) 1288 self.assertRaises(ValueError, self.theclass, 2000, 13, 1) 1289 # bad days 1290 self.theclass(2000, 2, 29) # no exception 1291 self.theclass(2004, 2, 29) # no exception 1292 self.theclass(2400, 2, 29) # no exception 1293 self.assertRaises(ValueError, self.theclass, 2000, 2, 30) 1294 self.assertRaises(ValueError, self.theclass, 2001, 2, 29) 1295 self.assertRaises(ValueError, self.theclass, 2100, 2, 29) 1296 self.assertRaises(ValueError, self.theclass, 1900, 2, 29) 1297 self.assertRaises(ValueError, self.theclass, 2000, 1, 0) 1298 self.assertRaises(ValueError, self.theclass, 2000, 1, 32) 1299 # bad hours 1300 self.theclass(2000, 1, 31, 0) # no exception 1301 self.theclass(2000, 1, 31, 23) # no exception 1302 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, -1) 1303 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 24) 1304 # bad minutes 1305 self.theclass(2000, 1, 31, 23, 0) # no exception 1306 self.theclass(2000, 1, 31, 23, 59) # no exception 1307 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, -1) 1308 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 60) 1309 # bad seconds 1310 self.theclass(2000, 1, 31, 23, 59, 0) # no exception 1311 self.theclass(2000, 1, 31, 23, 59, 59) # no exception 1312 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, -1) 1313 self.assertRaises(ValueError, self.theclass, 2000, 1, 31, 23, 59, 60) 1314 # bad microseconds 1315 self.theclass(2000, 1, 31, 23, 59, 59, 0) # no exception 1316 self.theclass(2000, 1, 31, 23, 59, 59, 999999) # no exception 1317 self.assertRaises(ValueError, self.theclass, 1318 2000, 1, 31, 23, 59, 59, -1) 1319 self.assertRaises(ValueError, self.theclass, 1320 2000, 1, 31, 23, 59, 59, 1321 1000000) 1322 1323 def test_hash_equality(self): 1324 d = self.theclass(2000, 12, 31, 23, 30, 17) 1325 e = self.theclass(2000, 12, 31, 23, 30, 17) 1326 self.assertEqual(d, e) 1327 self.assertEqual(hash(d), hash(e)) 1328 1329 dic = {d: 1} 1330 dic[e] = 2 1331 self.assertEqual(len(dic), 1) 1332 self.assertEqual(dic[d], 2) 1333 self.assertEqual(dic[e], 2) 1334 1335 d = self.theclass(2001, 1, 1, 0, 5, 17) 1336 e = self.theclass(2001, 1, 1, 0, 5, 17) 1337 self.assertEqual(d, e) 1338 self.assertEqual(hash(d), hash(e)) 1339 1340 dic = {d: 1} 1341 dic[e] = 2 1342 self.assertEqual(len(dic), 1) 1343 self.assertEqual(dic[d], 2) 1344 self.assertEqual(dic[e], 2) 1345 1346 def test_computations(self): 1347 a = self.theclass(2002, 1, 31) 1348 b = self.theclass(1956, 1, 31) 1349 diff = a-b 1350 self.assertEqual(diff.days, 46*365 + len(range(1956, 2002, 4))) 1351 self.assertEqual(diff.seconds, 0) 1352 self.assertEqual(diff.microseconds, 0) 1353 a = self.theclass(2002, 3, 2, 17, 6) 1354 millisec = timedelta(0, 0, 1000) 1355 hour = timedelta(0, 3600) 1356 day = timedelta(1) 1357 week = timedelta(7) 1358 self.assertEqual(a + hour, self.theclass(2002, 3, 2, 18, 6)) 1359 self.assertEqual(hour + a, self.theclass(2002, 3, 2, 18, 6)) 1360 self.assertEqual(a + 10*hour, self.theclass(2002, 3, 3, 3, 6)) 1361 self.assertEqual(a - hour, self.theclass(2002, 3, 2, 16, 6)) 1362 self.assertEqual(-hour + a, self.theclass(2002, 3, 2, 16, 6)) 1363 self.assertEqual(a - hour, a + -hour) 1364 self.assertEqual(a - 20*hour, self.theclass(2002, 3, 1, 21, 6)) 1365 self.assertEqual(a + day, self.theclass(2002, 3, 3, 17, 6)) 1366 self.assertEqual(a - day, self.theclass(2002, 3, 1, 17, 6)) 1367 self.assertEqual(a + week, self.theclass(2002, 3, 9, 17, 6)) 1368 self.assertEqual(a - week, self.theclass(2002, 2, 23, 17, 6)) 1369 self.assertEqual(a + 52*week, self.theclass(2003, 3, 1, 17, 6)) 1370 self.assertEqual(a - 52*week, self.theclass(2001, 3, 3, 17, 6)) 1371 self.assertEqual((a + week) - a, week) 1372 self.assertEqual((a + day) - a, day) 1373 self.assertEqual((a + hour) - a, hour) 1374 self.assertEqual((a + millisec) - a, millisec) 1375 self.assertEqual((a - week) - a, -week) 1376 self.assertEqual((a - day) - a, -day) 1377 self.assertEqual((a - hour) - a, -hour) 1378 self.assertEqual((a - millisec) - a, -millisec) 1379 self.assertEqual(a - (a + week), -week) 1380 self.assertEqual(a - (a + day), -day) 1381 self.assertEqual(a - (a + hour), -hour) 1382 self.assertEqual(a - (a + millisec), -millisec) 1383 self.assertEqual(a - (a - week), week) 1384 self.assertEqual(a - (a - day), day) 1385 self.assertEqual(a - (a - hour), hour) 1386 self.assertEqual(a - (a - millisec), millisec) 1387 self.assertEqual(a + (week + day + hour + millisec), 1388 self.theclass(2002, 3, 10, 18, 6, 0, 1000)) 1389 self.assertEqual(a + (week + day + hour + millisec), 1390 (((a + week) + day) + hour) + millisec) 1391 self.assertEqual(a - (week + day + hour + millisec), 1392 self.theclass(2002, 2, 22, 16, 5, 59, 999000)) 1393 self.assertEqual(a - (week + day + hour + millisec), 1394 (((a - week) - day) - hour) - millisec) 1395 # Add/sub ints, longs, floats should be illegal 1396 for i in 1, 1L, 1.0: 1397 self.assertRaises(TypeError, lambda: a+i) 1398 self.assertRaises(TypeError, lambda: a-i) 1399 self.assertRaises(TypeError, lambda: i+a) 1400 self.assertRaises(TypeError, lambda: i-a) 1401 1402 # delta - datetime is senseless. 1403 self.assertRaises(TypeError, lambda: day - a) 1404 # mixing datetime and (delta or datetime) via * or // is senseless 1405 self.assertRaises(TypeError, lambda: day * a) 1406 self.assertRaises(TypeError, lambda: a * day) 1407 self.assertRaises(TypeError, lambda: day // a) 1408 self.assertRaises(TypeError, lambda: a // day) 1409 self.assertRaises(TypeError, lambda: a * a) 1410 self.assertRaises(TypeError, lambda: a // a) 1411 # datetime + datetime is senseless 1412 self.assertRaises(TypeError, lambda: a + a) 1413 1414 def test_pickling(self): 1415 args = 6, 7, 23, 20, 59, 1, 64**2 1416 orig = self.theclass(*args) 1417 for pickler, unpickler, proto in pickle_choices: 1418 green = pickler.dumps(orig, proto) 1419 derived = unpickler.loads(green) 1420 self.assertEqual(orig, derived) 1421 1422 def test_more_pickling(self): 1423 a = self.theclass(2003, 2, 7, 16, 48, 37, 444116) 1424 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 1425 s = pickle.dumps(a, proto) 1426 b = pickle.loads(s) 1427 self.assertEqual(b.year, 2003) 1428 self.assertEqual(b.month, 2) 1429 self.assertEqual(b.day, 7) 1430 1431 def test_pickling_subclass_datetime(self): 1432 args = 6, 7, 23, 20, 59, 1, 64**2 1433 orig = SubclassDatetime(*args) 1434 for pickler, unpickler, proto in pickle_choices: 1435 green = pickler.dumps(orig, proto) 1436 derived = unpickler.loads(green) 1437 self.assertEqual(orig, derived) 1438 1439 def test_more_compare(self): 1440 # The test_compare() inherited from TestDate covers the error cases. 1441 # We just want to test lexicographic ordering on the members datetime 1442 # has that date lacks. 1443 args = [2000, 11, 29, 20, 58, 16, 999998] 1444 t1 = self.theclass(*args) 1445 t2 = self.theclass(*args) 1446 self.assertTrue(t1 == t2) 1447 self.assertTrue(t1 <= t2) 1448 self.assertTrue(t1 >= t2) 1449 self.assertFalse(t1 != t2) 1450 self.assertFalse(t1 < t2) 1451 self.assertFalse(t1 > t2) 1452 self.assertEqual(cmp(t1, t2), 0) 1453 self.assertEqual(cmp(t2, t1), 0) 1454 1455 for i in range(len(args)): 1456 newargs = args[:] 1457 newargs[i] = args[i] + 1 1458 t2 = self.theclass(*newargs) # this is larger than t1 1459 self.assertTrue(t1 < t2) 1460 self.assertTrue(t2 > t1) 1461 self.assertTrue(t1 <= t2) 1462 self.assertTrue(t2 >= t1) 1463 self.assertTrue(t1 != t2) 1464 self.assertTrue(t2 != t1) 1465 self.assertFalse(t1 == t2) 1466 self.assertFalse(t2 == t1) 1467 self.assertFalse(t1 > t2) 1468 self.assertFalse(t2 < t1) 1469 self.assertFalse(t1 >= t2) 1470 self.assertFalse(t2 <= t1) 1471 self.assertEqual(cmp(t1, t2), -1) 1472 self.assertEqual(cmp(t2, t1), 1) 1473 1474 1475 # A helper for timestamp constructor tests. 1476 def verify_field_equality(self, expected, got): 1477 self.assertEqual(expected.tm_year, got.year) 1478 self.assertEqual(expected.tm_mon, got.month) 1479 self.assertEqual(expected.tm_mday, got.day) 1480 self.assertEqual(expected.tm_hour, got.hour) 1481 self.assertEqual(expected.tm_min, got.minute) 1482 self.assertEqual(expected.tm_sec, got.second) 1483 1484 def test_fromtimestamp(self): 1485 import time 1486 1487 ts = time.time() 1488 expected = time.localtime(ts) 1489 got = self.theclass.fromtimestamp(ts) 1490 self.verify_field_equality(expected, got) 1491 1492 def test_utcfromtimestamp(self): 1493 import time 1494 1495 ts = time.time() 1496 expected = time.gmtime(ts) 1497 got = self.theclass.utcfromtimestamp(ts) 1498 self.verify_field_equality(expected, got) 1499 1500 def test_microsecond_rounding(self): 1501 # Test whether fromtimestamp "rounds up" floats that are less 1502 # than one microsecond smaller than an integer. 1503 self.assertEqual(self.theclass.fromtimestamp(0.9999999), 1504 self.theclass.fromtimestamp(1)) 1505 1506 def test_insane_fromtimestamp(self): 1507 # It's possible that some platform maps time_t to double, 1508 # and that this test will fail there. This test should 1509 # exempt such platforms (provided they return reasonable 1510 # results!). 1511 for insane in -1e200, 1e200: 1512 self.assertRaises(ValueError, self.theclass.fromtimestamp, 1513 insane) 1514 1515 def test_insane_utcfromtimestamp(self): 1516 # It's possible that some platform maps time_t to double, 1517 # and that this test will fail there. This test should 1518 # exempt such platforms (provided they return reasonable 1519 # results!). 1520 for insane in -1e200, 1e200: 1521 self.assertRaises(ValueError, self.theclass.utcfromtimestamp, 1522 insane) 1523 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") 1524 def test_negative_float_fromtimestamp(self): 1525 # The result is tz-dependent; at least test that this doesn't 1526 # fail (like it did before bug 1646728 was fixed). 1527 self.theclass.fromtimestamp(-1.05) 1528 1529 @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") 1530 def test_negative_float_utcfromtimestamp(self): 1531 d = self.theclass.utcfromtimestamp(-1.05) 1532 self.assertEqual(d, self.theclass(1969, 12, 31, 23, 59, 58, 950000)) 1533 1534 def test_utcnow(self): 1535 import time 1536 1537 # Call it a success if utcnow() and utcfromtimestamp() are within 1538 # a second of each other. 1539 tolerance = timedelta(seconds=1) 1540 for dummy in range(3): 1541 from_now = self.theclass.utcnow() 1542 from_timestamp = self.theclass.utcfromtimestamp(time.time()) 1543 if abs(from_timestamp - from_now) <= tolerance: 1544 break 1545 # Else try again a few times. 1546 self.assertLessEqual(abs(from_timestamp - from_now), tolerance) 1547 1548 def test_strptime(self): 1549 import _strptime 1550 1551 string = '2004-12-01 13:02:47.197' 1552 format = '%Y-%m-%d %H:%M:%S.%f' 1553 result, frac = _strptime._strptime(string, format) 1554 expected = self.theclass(*(result[0:6]+(frac,))) 1555 got = self.theclass.strptime(string, format) 1556 self.assertEqual(expected, got) 1557 1558 def test_more_timetuple(self): 1559 # This tests fields beyond those tested by the TestDate.test_timetuple. 1560 t = self.theclass(2004, 12, 31, 6, 22, 33) 1561 self.assertEqual(t.timetuple(), (2004, 12, 31, 6, 22, 33, 4, 366, -1)) 1562 self.assertEqual(t.timetuple(), 1563 (t.year, t.month, t.day, 1564 t.hour, t.minute, t.second, 1565 t.weekday(), 1566 t.toordinal() - date(t.year, 1, 1).toordinal() + 1, 1567 -1)) 1568 tt = t.timetuple() 1569 self.assertEqual(tt.tm_year, t.year) 1570 self.assertEqual(tt.tm_mon, t.month) 1571 self.assertEqual(tt.tm_mday, t.day) 1572 self.assertEqual(tt.tm_hour, t.hour) 1573 self.assertEqual(tt.tm_min, t.minute) 1574 self.assertEqual(tt.tm_sec, t.second) 1575 self.assertEqual(tt.tm_wday, t.weekday()) 1576 self.assertEqual(tt.tm_yday, t.toordinal() - 1577 date(t.year, 1, 1).toordinal() + 1) 1578 self.assertEqual(tt.tm_isdst, -1) 1579 1580 def test_more_strftime(self): 1581 # This tests fields beyond those tested by the TestDate.test_strftime. 1582 t = self.theclass(2004, 12, 31, 6, 22, 33, 47) 1583 self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), 1584 "12 31 04 000047 33 22 06 366") 1585 1586 def test_extract(self): 1587 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) 1588 self.assertEqual(dt.date(), date(2002, 3, 4)) 1589 self.assertEqual(dt.time(), time(18, 45, 3, 1234)) 1590 1591 def test_combine(self): 1592 d = date(2002, 3, 4) 1593 t = time(18, 45, 3, 1234) 1594 expected = self.theclass(2002, 3, 4, 18, 45, 3, 1234) 1595 combine = self.theclass.combine 1596 dt = combine(d, t) 1597 self.assertEqual(dt, expected) 1598 1599 dt = combine(time=t, date=d) 1600 self.assertEqual(dt, expected) 1601 1602 self.assertEqual(d, dt.date()) 1603 self.assertEqual(t, dt.time()) 1604 self.assertEqual(dt, combine(dt.date(), dt.time())) 1605 1606 self.assertRaises(TypeError, combine) # need an arg 1607 self.assertRaises(TypeError, combine, d) # need two args 1608 self.assertRaises(TypeError, combine, t, d) # args reversed 1609 self.assertRaises(TypeError, combine, d, t, 1) # too many args 1610 self.assertRaises(TypeError, combine, "date", "time") # wrong types 1611 1612 def test_replace(self): 1613 cls = self.theclass 1614 args = [1, 2, 3, 4, 5, 6, 7] 1615 base = cls(*args) 1616 self.assertEqual(base, base.replace()) 1617 1618 i = 0 1619 for name, newval in (("year", 2), 1620 ("month", 3), 1621 ("day", 4), 1622 ("hour", 5), 1623 ("minute", 6), 1624 ("second", 7), 1625 ("microsecond", 8)): 1626 newargs = args[:] 1627 newargs[i] = newval 1628 expected = cls(*newargs) 1629 got = base.replace(**{name: newval}) 1630 self.assertEqual(expected, got) 1631 i += 1 1632 1633 # Out of bounds. 1634 base = cls(2000, 2, 29) 1635 self.assertRaises(ValueError, base.replace, year=2001) 1636 1637 def test_astimezone(self): 1638 # Pretty boring! The TZ test is more interesting here. astimezone() 1639 # simply can't be applied to a naive object. 1640 dt = self.theclass.now() 1641 f = FixedOffset(44, "") 1642 self.assertRaises(TypeError, dt.astimezone) # not enough args 1643 self.assertRaises(TypeError, dt.astimezone, f, f) # too many args 1644 self.assertRaises(TypeError, dt.astimezone, dt) # arg wrong type 1645 self.assertRaises(ValueError, dt.astimezone, f) # naive 1646 self.assertRaises(ValueError, dt.astimezone, tz=f) # naive 1647 1648 class Bogus(tzinfo): 1649 def utcoffset(self, dt): return None 1650 def dst(self, dt): return timedelta(0) 1651 bog = Bogus() 1652 self.assertRaises(ValueError, dt.astimezone, bog) # naive 1653 1654 class AlsoBogus(tzinfo): 1655 def utcoffset(self, dt): return timedelta(0) 1656 def dst(self, dt): return None 1657 alsobog = AlsoBogus() 1658 self.assertRaises(ValueError, dt.astimezone, alsobog) # also naive 1659 1660 def test_subclass_datetime(self): 1661 1662 class C(self.theclass): 1663 theAnswer = 42 1664 1665 def __new__(cls, *args, **kws): 1666 temp = kws.copy() 1667 extra = temp.pop('extra') 1668 result = self.theclass.__new__(cls, *args, **temp) 1669 result.extra = extra 1670 return result 1671 1672 def newmeth(self, start): 1673 return start + self.year + self.month + self.second 1674 1675 args = 2003, 4, 14, 12, 13, 41 1676 1677 dt1 = self.theclass(*args) 1678 dt2 = C(*args, **{'extra': 7}) 1679 1680 self.assertEqual(dt2.__class__, C) 1681 self.assertEqual(dt2.theAnswer, 42) 1682 self.assertEqual(dt2.extra, 7) 1683 self.assertEqual(dt1.toordinal(), dt2.toordinal()) 1684 self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + 1685 dt1.second - 7) 1686 1687class SubclassTime(time): 1688 sub_var = 1 1689 1690class TestTime(HarmlessMixedComparison, unittest.TestCase): 1691 1692 theclass = time 1693 1694 def test_basic_attributes(self): 1695 t = self.theclass(12, 0) 1696 self.assertEqual(t.hour, 12) 1697 self.assertEqual(t.minute, 0) 1698 self.assertEqual(t.second, 0) 1699 self.assertEqual(t.microsecond, 0) 1700 1701 def test_basic_attributes_nonzero(self): 1702 # Make sure all attributes are non-zero so bugs in 1703 # bit-shifting access show up. 1704 t = self.theclass(12, 59, 59, 8000) 1705 self.assertEqual(t.hour, 12) 1706 self.assertEqual(t.minute, 59) 1707 self.assertEqual(t.second, 59) 1708 self.assertEqual(t.microsecond, 8000) 1709 1710 def test_roundtrip(self): 1711 t = self.theclass(1, 2, 3, 4) 1712 1713 # Verify t -> string -> time identity. 1714 s = repr(t) 1715 self.assertTrue(s.startswith('datetime.')) 1716 s = s[9:] 1717 t2 = eval(s) 1718 self.assertEqual(t, t2) 1719 1720 # Verify identity via reconstructing from pieces. 1721 t2 = self.theclass(t.hour, t.minute, t.second, 1722 t.microsecond) 1723 self.assertEqual(t, t2) 1724 1725 def test_comparing(self): 1726 args = [1, 2, 3, 4] 1727 t1 = self.theclass(*args) 1728 t2 = self.theclass(*args) 1729 self.assertTrue(t1 == t2) 1730 self.assertTrue(t1 <= t2) 1731 self.assertTrue(t1 >= t2) 1732 self.assertFalse(t1 != t2) 1733 self.assertFalse(t1 < t2) 1734 self.assertFalse(t1 > t2) 1735 self.assertEqual(cmp(t1, t2), 0) 1736 self.assertEqual(cmp(t2, t1), 0) 1737 1738 for i in range(len(args)): 1739 newargs = args[:] 1740 newargs[i] = args[i] + 1 1741 t2 = self.theclass(*newargs) # this is larger than t1 1742 self.assertTrue(t1 < t2) 1743 self.assertTrue(t2 > t1) 1744 self.assertTrue(t1 <= t2) 1745 self.assertTrue(t2 >= t1) 1746 self.assertTrue(t1 != t2) 1747 self.assertTrue(t2 != t1) 1748 self.assertFalse(t1 == t2) 1749 self.assertFalse(t2 == t1) 1750 self.assertFalse(t1 > t2) 1751 self.assertFalse(t2 < t1) 1752 self.assertFalse(t1 >= t2) 1753 self.assertFalse(t2 <= t1) 1754 self.assertEqual(cmp(t1, t2), -1) 1755 self.assertEqual(cmp(t2, t1), 1) 1756 1757 for badarg in OTHERSTUFF: 1758 self.assertEqual(t1 == badarg, False) 1759 self.assertEqual(t1 != badarg, True) 1760 self.assertEqual(badarg == t1, False) 1761 self.assertEqual(badarg != t1, True) 1762 1763 self.assertRaises(TypeError, lambda: t1 <= badarg) 1764 self.assertRaises(TypeError, lambda: t1 < badarg) 1765 self.assertRaises(TypeError, lambda: t1 > badarg) 1766 self.assertRaises(TypeError, lambda: t1 >= badarg) 1767 self.assertRaises(TypeError, lambda: badarg <= t1) 1768 self.assertRaises(TypeError, lambda: badarg < t1) 1769 self.assertRaises(TypeError, lambda: badarg > t1) 1770 self.assertRaises(TypeError, lambda: badarg >= t1) 1771 1772 def test_bad_constructor_arguments(self): 1773 # bad hours 1774 self.theclass(0, 0) # no exception 1775 self.theclass(23, 0) # no exception 1776 self.assertRaises(ValueError, self.theclass, -1, 0) 1777 self.assertRaises(ValueError, self.theclass, 24, 0) 1778 # bad minutes 1779 self.theclass(23, 0) # no exception 1780 self.theclass(23, 59) # no exception 1781 self.assertRaises(ValueError, self.theclass, 23, -1) 1782 self.assertRaises(ValueError, self.theclass, 23, 60) 1783 # bad seconds 1784 self.theclass(23, 59, 0) # no exception 1785 self.theclass(23, 59, 59) # no exception 1786 self.assertRaises(ValueError, self.theclass, 23, 59, -1) 1787 self.assertRaises(ValueError, self.theclass, 23, 59, 60) 1788 # bad microseconds 1789 self.theclass(23, 59, 59, 0) # no exception 1790 self.theclass(23, 59, 59, 999999) # no exception 1791 self.assertRaises(ValueError, self.theclass, 23, 59, 59, -1) 1792 self.assertRaises(ValueError, self.theclass, 23, 59, 59, 1000000) 1793 1794 def test_hash_equality(self): 1795 d = self.theclass(23, 30, 17) 1796 e = self.theclass(23, 30, 17) 1797 self.assertEqual(d, e) 1798 self.assertEqual(hash(d), hash(e)) 1799 1800 dic = {d: 1} 1801 dic[e] = 2 1802 self.assertEqual(len(dic), 1) 1803 self.assertEqual(dic[d], 2) 1804 self.assertEqual(dic[e], 2) 1805 1806 d = self.theclass(0, 5, 17) 1807 e = self.theclass(0, 5, 17) 1808 self.assertEqual(d, e) 1809 self.assertEqual(hash(d), hash(e)) 1810 1811 dic = {d: 1} 1812 dic[e] = 2 1813 self.assertEqual(len(dic), 1) 1814 self.assertEqual(dic[d], 2) 1815 self.assertEqual(dic[e], 2) 1816 1817 def test_isoformat(self): 1818 t = self.theclass(4, 5, 1, 123) 1819 self.assertEqual(t.isoformat(), "04:05:01.000123") 1820 self.assertEqual(t.isoformat(), str(t)) 1821 1822 t = self.theclass() 1823 self.assertEqual(t.isoformat(), "00:00:00") 1824 self.assertEqual(t.isoformat(), str(t)) 1825 1826 t = self.theclass(microsecond=1) 1827 self.assertEqual(t.isoformat(), "00:00:00.000001") 1828 self.assertEqual(t.isoformat(), str(t)) 1829 1830 t = self.theclass(microsecond=10) 1831 self.assertEqual(t.isoformat(), "00:00:00.000010") 1832 self.assertEqual(t.isoformat(), str(t)) 1833 1834 t = self.theclass(microsecond=100) 1835 self.assertEqual(t.isoformat(), "00:00:00.000100") 1836 self.assertEqual(t.isoformat(), str(t)) 1837 1838 t = self.theclass(microsecond=1000) 1839 self.assertEqual(t.isoformat(), "00:00:00.001000") 1840 self.assertEqual(t.isoformat(), str(t)) 1841 1842 t = self.theclass(microsecond=10000) 1843 self.assertEqual(t.isoformat(), "00:00:00.010000") 1844 self.assertEqual(t.isoformat(), str(t)) 1845 1846 t = self.theclass(microsecond=100000) 1847 self.assertEqual(t.isoformat(), "00:00:00.100000") 1848 self.assertEqual(t.isoformat(), str(t)) 1849 1850 def test_1653736(self): 1851 # verify it doesn't accept extra keyword arguments 1852 t = self.theclass(second=1) 1853 self.assertRaises(TypeError, t.isoformat, foo=3) 1854 1855 def test_strftime(self): 1856 t = self.theclass(1, 2, 3, 4) 1857 self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004") 1858 # A naive object replaces %z and %Z with empty strings. 1859 self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") 1860 1861 def test_format(self): 1862 t = self.theclass(1, 2, 3, 4) 1863 self.assertEqual(t.__format__(''), str(t)) 1864 1865 # check that a derived class's __str__() gets called 1866 class A(self.theclass): 1867 def __str__(self): 1868 return 'A' 1869 a = A(1, 2, 3, 4) 1870 self.assertEqual(a.__format__(''), 'A') 1871 1872 # check that a derived class's strftime gets called 1873 class B(self.theclass): 1874 def strftime(self, format_spec): 1875 return 'B' 1876 b = B(1, 2, 3, 4) 1877 self.assertEqual(b.__format__(''), str(t)) 1878 1879 for fmt in ['%H %M %S', 1880 ]: 1881 self.assertEqual(t.__format__(fmt), t.strftime(fmt)) 1882 self.assertEqual(a.__format__(fmt), t.strftime(fmt)) 1883 self.assertEqual(b.__format__(fmt), 'B') 1884 1885 def test_str(self): 1886 self.assertEqual(str(self.theclass(1, 2, 3, 4)), "01:02:03.000004") 1887 self.assertEqual(str(self.theclass(10, 2, 3, 4000)), "10:02:03.004000") 1888 self.assertEqual(str(self.theclass(0, 2, 3, 400000)), "00:02:03.400000") 1889 self.assertEqual(str(self.theclass(12, 2, 3, 0)), "12:02:03") 1890 self.assertEqual(str(self.theclass(23, 15, 0, 0)), "23:15:00") 1891 1892 def test_repr(self): 1893 name = 'datetime.' + self.theclass.__name__ 1894 self.assertEqual(repr(self.theclass(1, 2, 3, 4)), 1895 "%s(1, 2, 3, 4)" % name) 1896 self.assertEqual(repr(self.theclass(10, 2, 3, 4000)), 1897 "%s(10, 2, 3, 4000)" % name) 1898 self.assertEqual(repr(self.theclass(0, 2, 3, 400000)), 1899 "%s(0, 2, 3, 400000)" % name) 1900 self.assertEqual(repr(self.theclass(12, 2, 3, 0)), 1901 "%s(12, 2, 3)" % name) 1902 self.assertEqual(repr(self.theclass(23, 15, 0, 0)), 1903 "%s(23, 15)" % name) 1904 1905 def test_resolution_info(self): 1906 self.assertIsInstance(self.theclass.min, self.theclass) 1907 self.assertIsInstance(self.theclass.max, self.theclass) 1908 self.assertIsInstance(self.theclass.resolution, timedelta) 1909 self.assertTrue(self.theclass.max > self.theclass.min) 1910 1911 def test_pickling(self): 1912 args = 20, 59, 16, 64**2 1913 orig = self.theclass(*args) 1914 for pickler, unpickler, proto in pickle_choices: 1915 green = pickler.dumps(orig, proto) 1916 derived = unpickler.loads(green) 1917 self.assertEqual(orig, derived) 1918 1919 def test_pickling_subclass_time(self): 1920 args = 20, 59, 16, 64**2 1921 orig = SubclassTime(*args) 1922 for pickler, unpickler, proto in pickle_choices: 1923 green = pickler.dumps(orig, proto) 1924 derived = unpickler.loads(green) 1925 self.assertEqual(orig, derived) 1926 1927 def test_bool(self): 1928 cls = self.theclass 1929 self.assertTrue(cls(1)) 1930 self.assertTrue(cls(0, 1)) 1931 self.assertTrue(cls(0, 0, 1)) 1932 self.assertTrue(cls(0, 0, 0, 1)) 1933 self.assertFalse(cls(0)) 1934 self.assertFalse(cls()) 1935 1936 def test_replace(self): 1937 cls = self.theclass 1938 args = [1, 2, 3, 4] 1939 base = cls(*args) 1940 self.assertEqual(base, base.replace()) 1941 1942 i = 0 1943 for name, newval in (("hour", 5), 1944 ("minute", 6), 1945 ("second", 7), 1946 ("microsecond", 8)): 1947 newargs = args[:] 1948 newargs[i] = newval 1949 expected = cls(*newargs) 1950 got = base.replace(**{name: newval}) 1951 self.assertEqual(expected, got) 1952 i += 1 1953 1954 # Out of bounds. 1955 base = cls(1) 1956 self.assertRaises(ValueError, base.replace, hour=24) 1957 self.assertRaises(ValueError, base.replace, minute=-1) 1958 self.assertRaises(ValueError, base.replace, second=100) 1959 self.assertRaises(ValueError, base.replace, microsecond=1000000) 1960 1961 def test_subclass_time(self): 1962 1963 class C(self.theclass): 1964 theAnswer = 42 1965 1966 def __new__(cls, *args, **kws): 1967 temp = kws.copy() 1968 extra = temp.pop('extra') 1969 result = self.theclass.__new__(cls, *args, **temp) 1970 result.extra = extra 1971 return result 1972 1973 def newmeth(self, start): 1974 return start + self.hour + self.second 1975 1976 args = 4, 5, 6 1977 1978 dt1 = self.theclass(*args) 1979 dt2 = C(*args, **{'extra': 7}) 1980 1981 self.assertEqual(dt2.__class__, C) 1982 self.assertEqual(dt2.theAnswer, 42) 1983 self.assertEqual(dt2.extra, 7) 1984 self.assertEqual(dt1.isoformat(), dt2.isoformat()) 1985 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) 1986 1987 def test_backdoor_resistance(self): 1988 # see TestDate.test_backdoor_resistance(). 1989 base = '2:59.0' 1990 for hour_byte in ' ', '9', chr(24), '\xff': 1991 self.assertRaises(TypeError, self.theclass, 1992 hour_byte + base[1:]) 1993 1994# A mixin for classes with a tzinfo= argument. Subclasses must define 1995# theclass as a class attribute, and theclass(1, 1, 1, tzinfo=whatever) 1996# must be legit (which is true for time and datetime). 1997class TZInfoBase: 1998 1999 def test_argument_passing(self): 2000 cls = self.theclass 2001 # A datetime passes itself on, a time passes None. 2002 class introspective(tzinfo): 2003 def tzname(self, dt): return dt and "real" or "none" 2004 def utcoffset(self, dt): 2005 return timedelta(minutes = dt and 42 or -42) 2006 dst = utcoffset 2007 2008 obj = cls(1, 2, 3, tzinfo=introspective()) 2009 2010 expected = cls is time and "none" or "real" 2011 self.assertEqual(obj.tzname(), expected) 2012 2013 expected = timedelta(minutes=(cls is time and -42 or 42)) 2014 self.assertEqual(obj.utcoffset(), expected) 2015 self.assertEqual(obj.dst(), expected) 2016 2017 def test_bad_tzinfo_classes(self): 2018 cls = self.theclass 2019 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12) 2020 2021 class NiceTry(object): 2022 def __init__(self): pass 2023 def utcoffset(self, dt): pass 2024 self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry) 2025 2026 class BetterTry(tzinfo): 2027 def __init__(self): pass 2028 def utcoffset(self, dt): pass 2029 b = BetterTry() 2030 t = cls(1, 1, 1, tzinfo=b) 2031 self.assertIs(t.tzinfo, b) 2032 2033 def test_utc_offset_out_of_bounds(self): 2034 class Edgy(tzinfo): 2035 def __init__(self, offset): 2036 self.offset = timedelta(minutes=offset) 2037 def utcoffset(self, dt): 2038 return self.offset 2039 2040 cls = self.theclass 2041 for offset, legit in ((-1440, False), 2042 (-1439, True), 2043 (1439, True), 2044 (1440, False)): 2045 if cls is time: 2046 t = cls(1, 2, 3, tzinfo=Edgy(offset)) 2047 elif cls is datetime: 2048 t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset)) 2049 else: 2050 assert 0, "impossible" 2051 if legit: 2052 aofs = abs(offset) 2053 h, m = divmod(aofs, 60) 2054 tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m) 2055 if isinstance(t, datetime): 2056 t = t.timetz() 2057 self.assertEqual(str(t), "01:02:03" + tag) 2058 else: 2059 self.assertRaises(ValueError, str, t) 2060 2061 def test_tzinfo_classes(self): 2062 cls = self.theclass 2063 class C1(tzinfo): 2064 def utcoffset(self, dt): return None 2065 def dst(self, dt): return None 2066 def tzname(self, dt): return None 2067 for t in (cls(1, 1, 1), 2068 cls(1, 1, 1, tzinfo=None), 2069 cls(1, 1, 1, tzinfo=C1())): 2070 self.assertIsNone(t.utcoffset()) 2071 self.assertIsNone(t.dst()) 2072 self.assertIsNone(t.tzname()) 2073 2074 class C3(tzinfo): 2075 def utcoffset(self, dt): return timedelta(minutes=-1439) 2076 def dst(self, dt): return timedelta(minutes=1439) 2077 def tzname(self, dt): return "aname" 2078 t = cls(1, 1, 1, tzinfo=C3()) 2079 self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) 2080 self.assertEqual(t.dst(), timedelta(minutes=1439)) 2081 self.assertEqual(t.tzname(), "aname") 2082 2083 # Wrong types. 2084 class C4(tzinfo): 2085 def utcoffset(self, dt): return "aname" 2086 def dst(self, dt): return 7 2087 def tzname(self, dt): return 0 2088 t = cls(1, 1, 1, tzinfo=C4()) 2089 self.assertRaises(TypeError, t.utcoffset) 2090 self.assertRaises(TypeError, t.dst) 2091 self.assertRaises(TypeError, t.tzname) 2092 2093 # Offset out of range. 2094 class C6(tzinfo): 2095 def utcoffset(self, dt): return timedelta(hours=-24) 2096 def dst(self, dt): return timedelta(hours=24) 2097 t = cls(1, 1, 1, tzinfo=C6()) 2098 self.assertRaises(ValueError, t.utcoffset) 2099 self.assertRaises(ValueError, t.dst) 2100 2101 # Not a whole number of minutes. 2102 class C7(tzinfo): 2103 def utcoffset(self, dt): return timedelta(seconds=61) 2104 def dst(self, dt): return timedelta(microseconds=-81) 2105 t = cls(1, 1, 1, tzinfo=C7()) 2106 self.assertRaises(ValueError, t.utcoffset) 2107 self.assertRaises(ValueError, t.dst) 2108 2109 def test_aware_compare(self): 2110 cls = self.theclass 2111 2112 # Ensure that utcoffset() gets ignored if the comparands have 2113 # the same tzinfo member. 2114 class OperandDependentOffset(tzinfo): 2115 def utcoffset(self, t): 2116 if t.minute < 10: 2117 # d0 and d1 equal after adjustment 2118 return timedelta(minutes=t.minute) 2119 else: 2120 # d2 off in the weeds 2121 return timedelta(minutes=59) 2122 2123 base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) 2124 d0 = base.replace(minute=3) 2125 d1 = base.replace(minute=9) 2126 d2 = base.replace(minute=11) 2127 for x in d0, d1, d2: 2128 for y in d0, d1, d2: 2129 got = cmp(x, y) 2130 expected = cmp(x.minute, y.minute) 2131 self.assertEqual(got, expected) 2132 2133 # However, if they're different members, uctoffset is not ignored. 2134 # Note that a time can't actually have an operand-depedent offset, 2135 # though (and time.utcoffset() passes None to tzinfo.utcoffset()), 2136 # so skip this test for time. 2137 if cls is not time: 2138 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) 2139 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) 2140 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) 2141 for x in d0, d1, d2: 2142 for y in d0, d1, d2: 2143 got = cmp(x, y) 2144 if (x is d0 or x is d1) and (y is d0 or y is d1): 2145 expected = 0 2146 elif x is y is d2: 2147 expected = 0 2148 elif x is d2: 2149 expected = -1 2150 else: 2151 assert y is d2 2152 expected = 1 2153 self.assertEqual(got, expected) 2154 2155 2156# Testing time objects with a non-None tzinfo. 2157class TestTimeTZ(TestTime, TZInfoBase, unittest.TestCase): 2158 theclass = time 2159 2160 def test_empty(self): 2161 t = self.theclass() 2162 self.assertEqual(t.hour, 0) 2163 self.assertEqual(t.minute, 0) 2164 self.assertEqual(t.second, 0) 2165 self.assertEqual(t.microsecond, 0) 2166 self.assertIsNone(t.tzinfo) 2167 2168 def test_zones(self): 2169 est = FixedOffset(-300, "EST", 1) 2170 utc = FixedOffset(0, "UTC", -2) 2171 met = FixedOffset(60, "MET", 3) 2172 t1 = time( 7, 47, tzinfo=est) 2173 t2 = time(12, 47, tzinfo=utc) 2174 t3 = time(13, 47, tzinfo=met) 2175 t4 = time(microsecond=40) 2176 t5 = time(microsecond=40, tzinfo=utc) 2177 2178 self.assertEqual(t1.tzinfo, est) 2179 self.assertEqual(t2.tzinfo, utc) 2180 self.assertEqual(t3.tzinfo, met) 2181 self.assertIsNone(t4.tzinfo) 2182 self.assertEqual(t5.tzinfo, utc) 2183 2184 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) 2185 self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) 2186 self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) 2187 self.assertIsNone(t4.utcoffset()) 2188 self.assertRaises(TypeError, t1.utcoffset, "no args") 2189 2190 self.assertEqual(t1.tzname(), "EST") 2191 self.assertEqual(t2.tzname(), "UTC") 2192 self.assertEqual(t3.tzname(), "MET") 2193 self.assertIsNone(t4.tzname()) 2194 self.assertRaises(TypeError, t1.tzname, "no args") 2195 2196 self.assertEqual(t1.dst(), timedelta(minutes=1)) 2197 self.assertEqual(t2.dst(), timedelta(minutes=-2)) 2198 self.assertEqual(t3.dst(), timedelta(minutes=3)) 2199 self.assertIsNone(t4.dst()) 2200 self.assertRaises(TypeError, t1.dst, "no args") 2201 2202 self.assertEqual(hash(t1), hash(t2)) 2203 self.assertEqual(hash(t1), hash(t3)) 2204 self.assertEqual(hash(t2), hash(t3)) 2205 2206 self.assertEqual(t1, t2) 2207 self.assertEqual(t1, t3) 2208 self.assertEqual(t2, t3) 2209 self.assertRaises(TypeError, lambda: t4 == t5) # mixed tz-aware & naive 2210 self.assertRaises(TypeError, lambda: t4 < t5) # mixed tz-aware & naive 2211 self.assertRaises(TypeError, lambda: t5 < t4) # mixed tz-aware & naive 2212 2213 self.assertEqual(str(t1), "07:47:00-05:00") 2214 self.assertEqual(str(t2), "12:47:00+00:00") 2215 self.assertEqual(str(t3), "13:47:00+01:00") 2216 self.assertEqual(str(t4), "00:00:00.000040") 2217 self.assertEqual(str(t5), "00:00:00.000040+00:00") 2218 2219 self.assertEqual(t1.isoformat(), "07:47:00-05:00") 2220 self.assertEqual(t2.isoformat(), "12:47:00+00:00") 2221 self.assertEqual(t3.isoformat(), "13:47:00+01:00") 2222 self.assertEqual(t4.isoformat(), "00:00:00.000040") 2223 self.assertEqual(t5.isoformat(), "00:00:00.000040+00:00") 2224 2225 d = 'datetime.time' 2226 self.assertEqual(repr(t1), d + "(7, 47, tzinfo=est)") 2227 self.assertEqual(repr(t2), d + "(12, 47, tzinfo=utc)") 2228 self.assertEqual(repr(t3), d + "(13, 47, tzinfo=met)") 2229 self.assertEqual(repr(t4), d + "(0, 0, 0, 40)") 2230 self.assertEqual(repr(t5), d + "(0, 0, 0, 40, tzinfo=utc)") 2231 2232 self.assertEqual(t1.strftime("%H:%M:%S %%Z=%Z %%z=%z"), 2233 "07:47:00 %Z=EST %z=-0500") 2234 self.assertEqual(t2.strftime("%H:%M:%S %Z %z"), "12:47:00 UTC +0000") 2235 self.assertEqual(t3.strftime("%H:%M:%S %Z %z"), "13:47:00 MET +0100") 2236 2237 yuck = FixedOffset(-1439, "%z %Z %%z%%Z") 2238 t1 = time(23, 59, tzinfo=yuck) 2239 self.assertEqual(t1.strftime("%H:%M %%Z='%Z' %%z='%z'"), 2240 "23:59 %Z='%z %Z %%z%%Z' %z='-2359'") 2241 2242 # Check that an invalid tzname result raises an exception. 2243 class Badtzname(tzinfo): 2244 def tzname(self, dt): return 42 2245 t = time(2, 3, 4, tzinfo=Badtzname()) 2246 self.assertEqual(t.strftime("%H:%M:%S"), "02:03:04") 2247 self.assertRaises(TypeError, t.strftime, "%Z") 2248 2249 def test_hash_edge_cases(self): 2250 # Offsets that overflow a basic time. 2251 t1 = self.theclass(0, 1, 2, 3, tzinfo=FixedOffset(1439, "")) 2252 t2 = self.theclass(0, 0, 2, 3, tzinfo=FixedOffset(1438, "")) 2253 self.assertEqual(hash(t1), hash(t2)) 2254 2255 t1 = self.theclass(23, 58, 6, 100, tzinfo=FixedOffset(-1000, "")) 2256 t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, "")) 2257 self.assertEqual(hash(t1), hash(t2)) 2258 2259 def test_pickling(self): 2260 # Try one without a tzinfo. 2261 args = 20, 59, 16, 64**2 2262 orig = self.theclass(*args) 2263 for pickler, unpickler, proto in pickle_choices: 2264 green = pickler.dumps(orig, proto) 2265 derived = unpickler.loads(green) 2266 self.assertEqual(orig, derived) 2267 2268 # Try one with a tzinfo. 2269 tinfo = PicklableFixedOffset(-300, 'cookie') 2270 orig = self.theclass(5, 6, 7, tzinfo=tinfo) 2271 for pickler, unpickler, proto in pickle_choices: 2272 green = pickler.dumps(orig, proto) 2273 derived = unpickler.loads(green) 2274 self.assertEqual(orig, derived) 2275 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) 2276 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) 2277 self.assertEqual(derived.tzname(), 'cookie') 2278 2279 def test_more_bool(self): 2280 # Test cases with non-None tzinfo. 2281 cls = self.theclass 2282 2283 t = cls(0, tzinfo=FixedOffset(-300, "")) 2284 self.assertTrue(t) 2285 2286 t = cls(5, tzinfo=FixedOffset(-300, "")) 2287 self.assertTrue(t) 2288 2289 t = cls(5, tzinfo=FixedOffset(300, "")) 2290 self.assertFalse(t) 2291 2292 t = cls(23, 59, tzinfo=FixedOffset(23*60 + 59, "")) 2293 self.assertFalse(t) 2294 2295 # Mostly ensuring this doesn't overflow internally. 2296 t = cls(0, tzinfo=FixedOffset(23*60 + 59, "")) 2297 self.assertTrue(t) 2298 2299 # But this should yield a value error -- the utcoffset is bogus. 2300 t = cls(0, tzinfo=FixedOffset(24*60, "")) 2301 self.assertRaises(ValueError, lambda: bool(t)) 2302 2303 # Likewise. 2304 t = cls(0, tzinfo=FixedOffset(-24*60, "")) 2305 self.assertRaises(ValueError, lambda: bool(t)) 2306 2307 def test_replace(self): 2308 cls = self.theclass 2309 z100 = FixedOffset(100, "+100") 2310 zm200 = FixedOffset(timedelta(minutes=-200), "-200") 2311 args = [1, 2, 3, 4, z100] 2312 base = cls(*args) 2313 self.assertEqual(base, base.replace()) 2314 2315 i = 0 2316 for name, newval in (("hour", 5), 2317 ("minute", 6), 2318 ("second", 7), 2319 ("microsecond", 8), 2320 ("tzinfo", zm200)): 2321 newargs = args[:] 2322 newargs[i] = newval 2323 expected = cls(*newargs) 2324 got = base.replace(**{name: newval}) 2325 self.assertEqual(expected, got) 2326 i += 1 2327 2328 # Ensure we can get rid of a tzinfo. 2329 self.assertEqual(base.tzname(), "+100") 2330 base2 = base.replace(tzinfo=None) 2331 self.assertIsNone(base2.tzinfo) 2332 self.assertIsNone(base2.tzname()) 2333 2334 # Ensure we can add one. 2335 base3 = base2.replace(tzinfo=z100) 2336 self.assertEqual(base, base3) 2337 self.assertIs(base.tzinfo, base3.tzinfo) 2338 2339 # Out of bounds. 2340 base = cls(1) 2341 self.assertRaises(ValueError, base.replace, hour=24) 2342 self.assertRaises(ValueError, base.replace, minute=-1) 2343 self.assertRaises(ValueError, base.replace, second=100) 2344 self.assertRaises(ValueError, base.replace, microsecond=1000000) 2345 2346 def test_mixed_compare(self): 2347 t1 = time(1, 2, 3) 2348 t2 = time(1, 2, 3) 2349 self.assertEqual(t1, t2) 2350 t2 = t2.replace(tzinfo=None) 2351 self.assertEqual(t1, t2) 2352 t2 = t2.replace(tzinfo=FixedOffset(None, "")) 2353 self.assertEqual(t1, t2) 2354 t2 = t2.replace(tzinfo=FixedOffset(0, "")) 2355 self.assertRaises(TypeError, lambda: t1 == t2) 2356 2357 # In time w/ identical tzinfo objects, utcoffset is ignored. 2358 class Varies(tzinfo): 2359 def __init__(self): 2360 self.offset = timedelta(minutes=22) 2361 def utcoffset(self, t): 2362 self.offset += timedelta(minutes=1) 2363 return self.offset 2364 2365 v = Varies() 2366 t1 = t2.replace(tzinfo=v) 2367 t2 = t2.replace(tzinfo=v) 2368 self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) 2369 self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) 2370 self.assertEqual(t1, t2) 2371 2372 # But if they're not identical, it isn't ignored. 2373 t2 = t2.replace(tzinfo=Varies()) 2374 self.assertTrue(t1 < t2) # t1's offset counter still going up 2375 2376 def test_subclass_timetz(self): 2377 2378 class C(self.theclass): 2379 theAnswer = 42 2380 2381 def __new__(cls, *args, **kws): 2382 temp = kws.copy() 2383 extra = temp.pop('extra') 2384 result = self.theclass.__new__(cls, *args, **temp) 2385 result.extra = extra 2386 return result 2387 2388 def newmeth(self, start): 2389 return start + self.hour + self.second 2390 2391 args = 4, 5, 6, 500, FixedOffset(-300, "EST", 1) 2392 2393 dt1 = self.theclass(*args) 2394 dt2 = C(*args, **{'extra': 7}) 2395 2396 self.assertEqual(dt2.__class__, C) 2397 self.assertEqual(dt2.theAnswer, 42) 2398 self.assertEqual(dt2.extra, 7) 2399 self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) 2400 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.second - 7) 2401 2402 2403# Testing datetime objects with a non-None tzinfo. 2404 2405class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase): 2406 theclass = datetime 2407 2408 def test_trivial(self): 2409 dt = self.theclass(1, 2, 3, 4, 5, 6, 7) 2410 self.assertEqual(dt.year, 1) 2411 self.assertEqual(dt.month, 2) 2412 self.assertEqual(dt.day, 3) 2413 self.assertEqual(dt.hour, 4) 2414 self.assertEqual(dt.minute, 5) 2415 self.assertEqual(dt.second, 6) 2416 self.assertEqual(dt.microsecond, 7) 2417 self.assertEqual(dt.tzinfo, None) 2418 2419 def test_even_more_compare(self): 2420 # The test_compare() and test_more_compare() inherited from TestDate 2421 # and TestDateTime covered non-tzinfo cases. 2422 2423 # Smallest possible after UTC adjustment. 2424 t1 = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) 2425 # Largest possible after UTC adjustment. 2426 t2 = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2427 tzinfo=FixedOffset(-1439, "")) 2428 2429 # Make sure those compare correctly, and w/o overflow. 2430 self.assertTrue(t1 < t2) 2431 self.assertTrue(t1 != t2) 2432 self.assertTrue(t2 > t1) 2433 2434 self.assertTrue(t1 == t1) 2435 self.assertTrue(t2 == t2) 2436 2437 # Equal afer adjustment. 2438 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, "")) 2439 t2 = self.theclass(2, 1, 1, 3, 13, tzinfo=FixedOffset(3*60+13+2, "")) 2440 self.assertEqual(t1, t2) 2441 2442 # Change t1 not to subtract a minute, and t1 should be larger. 2443 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(0, "")) 2444 self.assertTrue(t1 > t2) 2445 2446 # Change t1 to subtract 2 minutes, and t1 should be smaller. 2447 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(2, "")) 2448 self.assertTrue(t1 < t2) 2449 2450 # Back to the original t1, but make seconds resolve it. 2451 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), 2452 second=1) 2453 self.assertTrue(t1 > t2) 2454 2455 # Likewise, but make microseconds resolve it. 2456 t1 = self.theclass(1, 12, 31, 23, 59, tzinfo=FixedOffset(1, ""), 2457 microsecond=1) 2458 self.assertTrue(t1 > t2) 2459 2460 # Make t2 naive and it should fail. 2461 t2 = self.theclass.min 2462 self.assertRaises(TypeError, lambda: t1 == t2) 2463 self.assertEqual(t2, t2) 2464 2465 # It's also naive if it has tzinfo but tzinfo.utcoffset() is None. 2466 class Naive(tzinfo): 2467 def utcoffset(self, dt): return None 2468 t2 = self.theclass(5, 6, 7, tzinfo=Naive()) 2469 self.assertRaises(TypeError, lambda: t1 == t2) 2470 self.assertEqual(t2, t2) 2471 2472 # OTOH, it's OK to compare two of these mixing the two ways of being 2473 # naive. 2474 t1 = self.theclass(5, 6, 7) 2475 self.assertEqual(t1, t2) 2476 2477 # Try a bogus uctoffset. 2478 class Bogus(tzinfo): 2479 def utcoffset(self, dt): 2480 return timedelta(minutes=1440) # out of bounds 2481 t1 = self.theclass(2, 2, 2, tzinfo=Bogus()) 2482 t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) 2483 self.assertRaises(ValueError, lambda: t1 == t2) 2484 2485 def test_pickling(self): 2486 # Try one without a tzinfo. 2487 args = 6, 7, 23, 20, 59, 1, 64**2 2488 orig = self.theclass(*args) 2489 for pickler, unpickler, proto in pickle_choices: 2490 green = pickler.dumps(orig, proto) 2491 derived = unpickler.loads(green) 2492 self.assertEqual(orig, derived) 2493 2494 # Try one with a tzinfo. 2495 tinfo = PicklableFixedOffset(-300, 'cookie') 2496 orig = self.theclass(*args, **{'tzinfo': tinfo}) 2497 derived = self.theclass(1, 1, 1, tzinfo=FixedOffset(0, "", 0)) 2498 for pickler, unpickler, proto in pickle_choices: 2499 green = pickler.dumps(orig, proto) 2500 derived = unpickler.loads(green) 2501 self.assertEqual(orig, derived) 2502 self.assertIsInstance(derived.tzinfo, PicklableFixedOffset) 2503 self.assertEqual(derived.utcoffset(), timedelta(minutes=-300)) 2504 self.assertEqual(derived.tzname(), 'cookie') 2505 2506 def test_extreme_hashes(self): 2507 # If an attempt is made to hash these via subtracting the offset 2508 # then hashing a datetime object, OverflowError results. The 2509 # Python implementation used to blow up here. 2510 t = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "")) 2511 hash(t) 2512 t = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2513 tzinfo=FixedOffset(-1439, "")) 2514 hash(t) 2515 2516 # OTOH, an OOB offset should blow up. 2517 t = self.theclass(5, 5, 5, tzinfo=FixedOffset(-1440, "")) 2518 self.assertRaises(ValueError, hash, t) 2519 2520 def test_zones(self): 2521 est = FixedOffset(-300, "EST") 2522 utc = FixedOffset(0, "UTC") 2523 met = FixedOffset(60, "MET") 2524 t1 = datetime(2002, 3, 19, 7, 47, tzinfo=est) 2525 t2 = datetime(2002, 3, 19, 12, 47, tzinfo=utc) 2526 t3 = datetime(2002, 3, 19, 13, 47, tzinfo=met) 2527 self.assertEqual(t1.tzinfo, est) 2528 self.assertEqual(t2.tzinfo, utc) 2529 self.assertEqual(t3.tzinfo, met) 2530 self.assertEqual(t1.utcoffset(), timedelta(minutes=-300)) 2531 self.assertEqual(t2.utcoffset(), timedelta(minutes=0)) 2532 self.assertEqual(t3.utcoffset(), timedelta(minutes=60)) 2533 self.assertEqual(t1.tzname(), "EST") 2534 self.assertEqual(t2.tzname(), "UTC") 2535 self.assertEqual(t3.tzname(), "MET") 2536 self.assertEqual(hash(t1), hash(t2)) 2537 self.assertEqual(hash(t1), hash(t3)) 2538 self.assertEqual(hash(t2), hash(t3)) 2539 self.assertEqual(t1, t2) 2540 self.assertEqual(t1, t3) 2541 self.assertEqual(t2, t3) 2542 self.assertEqual(str(t1), "2002-03-19 07:47:00-05:00") 2543 self.assertEqual(str(t2), "2002-03-19 12:47:00+00:00") 2544 self.assertEqual(str(t3), "2002-03-19 13:47:00+01:00") 2545 d = 'datetime.datetime(2002, 3, 19, ' 2546 self.assertEqual(repr(t1), d + "7, 47, tzinfo=est)") 2547 self.assertEqual(repr(t2), d + "12, 47, tzinfo=utc)") 2548 self.assertEqual(repr(t3), d + "13, 47, tzinfo=met)") 2549 2550 def test_combine(self): 2551 met = FixedOffset(60, "MET") 2552 d = date(2002, 3, 4) 2553 tz = time(18, 45, 3, 1234, tzinfo=met) 2554 dt = datetime.combine(d, tz) 2555 self.assertEqual(dt, datetime(2002, 3, 4, 18, 45, 3, 1234, 2556 tzinfo=met)) 2557 2558 def test_extract(self): 2559 met = FixedOffset(60, "MET") 2560 dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234, tzinfo=met) 2561 self.assertEqual(dt.date(), date(2002, 3, 4)) 2562 self.assertEqual(dt.time(), time(18, 45, 3, 1234)) 2563 self.assertEqual(dt.timetz(), time(18, 45, 3, 1234, tzinfo=met)) 2564 2565 def test_tz_aware_arithmetic(self): 2566 import random 2567 2568 now = self.theclass.now() 2569 tz55 = FixedOffset(-330, "west 5:30") 2570 timeaware = now.time().replace(tzinfo=tz55) 2571 nowaware = self.theclass.combine(now.date(), timeaware) 2572 self.assertIs(nowaware.tzinfo, tz55) 2573 self.assertEqual(nowaware.timetz(), timeaware) 2574 2575 # Can't mix aware and non-aware. 2576 self.assertRaises(TypeError, lambda: now - nowaware) 2577 self.assertRaises(TypeError, lambda: nowaware - now) 2578 2579 # And adding datetime's doesn't make sense, aware or not. 2580 self.assertRaises(TypeError, lambda: now + nowaware) 2581 self.assertRaises(TypeError, lambda: nowaware + now) 2582 self.assertRaises(TypeError, lambda: nowaware + nowaware) 2583 2584 # Subtracting should yield 0. 2585 self.assertEqual(now - now, timedelta(0)) 2586 self.assertEqual(nowaware - nowaware, timedelta(0)) 2587 2588 # Adding a delta should preserve tzinfo. 2589 delta = timedelta(weeks=1, minutes=12, microseconds=5678) 2590 nowawareplus = nowaware + delta 2591 self.assertIs(nowaware.tzinfo, tz55) 2592 nowawareplus2 = delta + nowaware 2593 self.assertIs(nowawareplus2.tzinfo, tz55) 2594 self.assertEqual(nowawareplus, nowawareplus2) 2595 2596 # that - delta should be what we started with, and that - what we 2597 # started with should be delta. 2598 diff = nowawareplus - delta 2599 self.assertIs(diff.tzinfo, tz55) 2600 self.assertEqual(nowaware, diff) 2601 self.assertRaises(TypeError, lambda: delta - nowawareplus) 2602 self.assertEqual(nowawareplus - nowaware, delta) 2603 2604 # Make up a random timezone. 2605 tzr = FixedOffset(random.randrange(-1439, 1440), "randomtimezone") 2606 # Attach it to nowawareplus. 2607 nowawareplus = nowawareplus.replace(tzinfo=tzr) 2608 self.assertIs(nowawareplus.tzinfo, tzr) 2609 # Make sure the difference takes the timezone adjustments into account. 2610 got = nowaware - nowawareplus 2611 # Expected: (nowaware base - nowaware offset) - 2612 # (nowawareplus base - nowawareplus offset) = 2613 # (nowaware base - nowawareplus base) + 2614 # (nowawareplus offset - nowaware offset) = 2615 # -delta + nowawareplus offset - nowaware offset 2616 expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta 2617 self.assertEqual(got, expected) 2618 2619 # Try max possible difference. 2620 min = self.theclass(1, 1, 1, tzinfo=FixedOffset(1439, "min")) 2621 max = self.theclass(MAXYEAR, 12, 31, 23, 59, 59, 999999, 2622 tzinfo=FixedOffset(-1439, "max")) 2623 maxdiff = max - min 2624 self.assertEqual(maxdiff, self.theclass.max - self.theclass.min + 2625 timedelta(minutes=2*1439)) 2626 2627 def test_tzinfo_now(self): 2628 meth = self.theclass.now 2629 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2630 base = meth() 2631 # Try with and without naming the keyword. 2632 off42 = FixedOffset(42, "42") 2633 another = meth(off42) 2634 again = meth(tz=off42) 2635 self.assertIs(another.tzinfo, again.tzinfo) 2636 self.assertEqual(another.utcoffset(), timedelta(minutes=42)) 2637 # Bad argument with and w/o naming the keyword. 2638 self.assertRaises(TypeError, meth, 16) 2639 self.assertRaises(TypeError, meth, tzinfo=16) 2640 # Bad keyword name. 2641 self.assertRaises(TypeError, meth, tinfo=off42) 2642 # Too many args. 2643 self.assertRaises(TypeError, meth, off42, off42) 2644 2645 # We don't know which time zone we're in, and don't have a tzinfo 2646 # class to represent it, so seeing whether a tz argument actually 2647 # does a conversion is tricky. 2648 weirdtz = FixedOffset(timedelta(hours=15, minutes=58), "weirdtz", 0) 2649 utc = FixedOffset(0, "utc", 0) 2650 for dummy in range(3): 2651 now = datetime.now(weirdtz) 2652 self.assertIs(now.tzinfo, weirdtz) 2653 utcnow = datetime.utcnow().replace(tzinfo=utc) 2654 now2 = utcnow.astimezone(weirdtz) 2655 if abs(now - now2) < timedelta(seconds=30): 2656 break 2657 # Else the code is broken, or more than 30 seconds passed between 2658 # calls; assuming the latter, just try again. 2659 else: 2660 # Three strikes and we're out. 2661 self.fail("utcnow(), now(tz), or astimezone() may be broken") 2662 2663 def test_tzinfo_fromtimestamp(self): 2664 import time 2665 meth = self.theclass.fromtimestamp 2666 ts = time.time() 2667 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2668 base = meth(ts) 2669 # Try with and without naming the keyword. 2670 off42 = FixedOffset(42, "42") 2671 another = meth(ts, off42) 2672 again = meth(ts, tz=off42) 2673 self.assertIs(another.tzinfo, again.tzinfo) 2674 self.assertEqual(another.utcoffset(), timedelta(minutes=42)) 2675 # Bad argument with and w/o naming the keyword. 2676 self.assertRaises(TypeError, meth, ts, 16) 2677 self.assertRaises(TypeError, meth, ts, tzinfo=16) 2678 # Bad keyword name. 2679 self.assertRaises(TypeError, meth, ts, tinfo=off42) 2680 # Too many args. 2681 self.assertRaises(TypeError, meth, ts, off42, off42) 2682 # Too few args. 2683 self.assertRaises(TypeError, meth) 2684 2685 # Try to make sure tz= actually does some conversion. 2686 timestamp = 1000000000 2687 utcdatetime = datetime.utcfromtimestamp(timestamp) 2688 # In POSIX (epoch 1970), that's 2001-09-09 01:46:40 UTC, give or take. 2689 # But on some flavor of Mac, it's nowhere near that. So we can't have 2690 # any idea here what time that actually is, we can only test that 2691 # relative changes match. 2692 utcoffset = timedelta(hours=-15, minutes=39) # arbitrary, but not zero 2693 tz = FixedOffset(utcoffset, "tz", 0) 2694 expected = utcdatetime + utcoffset 2695 got = datetime.fromtimestamp(timestamp, tz) 2696 self.assertEqual(expected, got.replace(tzinfo=None)) 2697 2698 def test_tzinfo_utcnow(self): 2699 meth = self.theclass.utcnow 2700 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2701 base = meth() 2702 # Try with and without naming the keyword; for whatever reason, 2703 # utcnow() doesn't accept a tzinfo argument. 2704 off42 = FixedOffset(42, "42") 2705 self.assertRaises(TypeError, meth, off42) 2706 self.assertRaises(TypeError, meth, tzinfo=off42) 2707 2708 def test_tzinfo_utcfromtimestamp(self): 2709 import time 2710 meth = self.theclass.utcfromtimestamp 2711 ts = time.time() 2712 # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). 2713 base = meth(ts) 2714 # Try with and without naming the keyword; for whatever reason, 2715 # utcfromtimestamp() doesn't accept a tzinfo argument. 2716 off42 = FixedOffset(42, "42") 2717 self.assertRaises(TypeError, meth, ts, off42) 2718 self.assertRaises(TypeError, meth, ts, tzinfo=off42) 2719 2720 def test_tzinfo_timetuple(self): 2721 # TestDateTime tested most of this. datetime adds a twist to the 2722 # DST flag. 2723 class DST(tzinfo): 2724 def __init__(self, dstvalue): 2725 if isinstance(dstvalue, int): 2726 dstvalue = timedelta(minutes=dstvalue) 2727 self.dstvalue = dstvalue 2728 def dst(self, dt): 2729 return self.dstvalue 2730 2731 cls = self.theclass 2732 for dstvalue, flag in (-33, 1), (33, 1), (0, 0), (None, -1): 2733 d = cls(1, 1, 1, 10, 20, 30, 40, tzinfo=DST(dstvalue)) 2734 t = d.timetuple() 2735 self.assertEqual(1, t.tm_year) 2736 self.assertEqual(1, t.tm_mon) 2737 self.assertEqual(1, t.tm_mday) 2738 self.assertEqual(10, t.tm_hour) 2739 self.assertEqual(20, t.tm_min) 2740 self.assertEqual(30, t.tm_sec) 2741 self.assertEqual(0, t.tm_wday) 2742 self.assertEqual(1, t.tm_yday) 2743 self.assertEqual(flag, t.tm_isdst) 2744 2745 # dst() returns wrong type. 2746 self.assertRaises(TypeError, cls(1, 1, 1, tzinfo=DST("x")).timetuple) 2747 2748 # dst() at the edge. 2749 self.assertEqual(cls(1,1,1, tzinfo=DST(1439)).timetuple().tm_isdst, 1) 2750 self.assertEqual(cls(1,1,1, tzinfo=DST(-1439)).timetuple().tm_isdst, 1) 2751 2752 # dst() out of range. 2753 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(1440)).timetuple) 2754 self.assertRaises(ValueError, cls(1,1,1, tzinfo=DST(-1440)).timetuple) 2755 2756 def test_utctimetuple(self): 2757 class DST(tzinfo): 2758 def __init__(self, dstvalue): 2759 if isinstance(dstvalue, int): 2760 dstvalue = timedelta(minutes=dstvalue) 2761 self.dstvalue = dstvalue 2762 def dst(self, dt): 2763 return self.dstvalue 2764 2765 cls = self.theclass 2766 # This can't work: DST didn't implement utcoffset. 2767 self.assertRaises(NotImplementedError, 2768 cls(1, 1, 1, tzinfo=DST(0)).utcoffset) 2769 2770 class UOFS(DST): 2771 def __init__(self, uofs, dofs=None): 2772 DST.__init__(self, dofs) 2773 self.uofs = timedelta(minutes=uofs) 2774 def utcoffset(self, dt): 2775 return self.uofs 2776 2777 # Ensure tm_isdst is 0 regardless of what dst() says: DST is never 2778 # in effect for a UTC time. 2779 for dstvalue in -33, 33, 0, None: 2780 d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue)) 2781 t = d.utctimetuple() 2782 self.assertEqual(d.year, t.tm_year) 2783 self.assertEqual(d.month, t.tm_mon) 2784 self.assertEqual(d.day, t.tm_mday) 2785 self.assertEqual(11, t.tm_hour) # 20mm + 53mm = 1hn + 13mm 2786 self.assertEqual(13, t.tm_min) 2787 self.assertEqual(d.second, t.tm_sec) 2788 self.assertEqual(d.weekday(), t.tm_wday) 2789 self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1, 2790 t.tm_yday) 2791 self.assertEqual(0, t.tm_isdst) 2792 2793 # At the edges, UTC adjustment can normalize into years out-of-range 2794 # for a datetime object. Ensure that a correct timetuple is 2795 # created anyway. 2796 tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439)) 2797 # That goes back 1 minute less than a full day. 2798 t = tiny.utctimetuple() 2799 self.assertEqual(t.tm_year, MINYEAR-1) 2800 self.assertEqual(t.tm_mon, 12) 2801 self.assertEqual(t.tm_mday, 31) 2802 self.assertEqual(t.tm_hour, 0) 2803 self.assertEqual(t.tm_min, 1) 2804 self.assertEqual(t.tm_sec, 37) 2805 self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year 2806 self.assertEqual(t.tm_isdst, 0) 2807 2808 huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439)) 2809 # That goes forward 1 minute less than a full day. 2810 t = huge.utctimetuple() 2811 self.assertEqual(t.tm_year, MAXYEAR+1) 2812 self.assertEqual(t.tm_mon, 1) 2813 self.assertEqual(t.tm_mday, 1) 2814 self.assertEqual(t.tm_hour, 23) 2815 self.assertEqual(t.tm_min, 58) 2816 self.assertEqual(t.tm_sec, 37) 2817 self.assertEqual(t.tm_yday, 1) 2818 self.assertEqual(t.tm_isdst, 0) 2819 2820 def test_tzinfo_isoformat(self): 2821 zero = FixedOffset(0, "+00:00") 2822 plus = FixedOffset(220, "+03:40") 2823 minus = FixedOffset(-231, "-03:51") 2824 unknown = FixedOffset(None, "") 2825 2826 cls = self.theclass 2827 datestr = '0001-02-03' 2828 for ofs in None, zero, plus, minus, unknown: 2829 for us in 0, 987001: 2830 d = cls(1, 2, 3, 4, 5, 59, us, tzinfo=ofs) 2831 timestr = '04:05:59' + (us and '.987001' or '') 2832 ofsstr = ofs is not None and d.tzname() or '' 2833 tailstr = timestr + ofsstr 2834 iso = d.isoformat() 2835 self.assertEqual(iso, datestr + 'T' + tailstr) 2836 self.assertEqual(iso, d.isoformat('T')) 2837 self.assertEqual(d.isoformat('k'), datestr + 'k' + tailstr) 2838 self.assertEqual(str(d), datestr + ' ' + tailstr) 2839 2840 def test_replace(self): 2841 cls = self.theclass 2842 z100 = FixedOffset(100, "+100") 2843 zm200 = FixedOffset(timedelta(minutes=-200), "-200") 2844 args = [1, 2, 3, 4, 5, 6, 7, z100] 2845 base = cls(*args) 2846 self.assertEqual(base, base.replace()) 2847 2848 i = 0 2849 for name, newval in (("year", 2), 2850 ("month", 3), 2851 ("day", 4), 2852 ("hour", 5), 2853 ("minute", 6), 2854 ("second", 7), 2855 ("microsecond", 8), 2856 ("tzinfo", zm200)): 2857 newargs = args[:] 2858 newargs[i] = newval 2859 expected = cls(*newargs) 2860 got = base.replace(**{name: newval}) 2861 self.assertEqual(expected, got) 2862 i += 1 2863 2864 # Ensure we can get rid of a tzinfo. 2865 self.assertEqual(base.tzname(), "+100") 2866 base2 = base.replace(tzinfo=None) 2867 self.assertIsNone(base2.tzinfo) 2868 self.assertIsNone(base2.tzname()) 2869 2870 # Ensure we can add one. 2871 base3 = base2.replace(tzinfo=z100) 2872 self.assertEqual(base, base3) 2873 self.assertIs(base.tzinfo, base3.tzinfo) 2874 2875 # Out of bounds. 2876 base = cls(2000, 2, 29) 2877 self.assertRaises(ValueError, base.replace, year=2001) 2878 2879 def test_more_astimezone(self): 2880 # The inherited test_astimezone covered some trivial and error cases. 2881 fnone = FixedOffset(None, "None") 2882 f44m = FixedOffset(44, "44") 2883 fm5h = FixedOffset(-timedelta(hours=5), "m300") 2884 2885 dt = self.theclass.now(tz=f44m) 2886 self.assertIs(dt.tzinfo, f44m) 2887 # Replacing with degenerate tzinfo raises an exception. 2888 self.assertRaises(ValueError, dt.astimezone, fnone) 2889 # Ditto with None tz. 2890 self.assertRaises(TypeError, dt.astimezone, None) 2891 # Replacing with same tzinfo makes no change. 2892 x = dt.astimezone(dt.tzinfo) 2893 self.assertIs(x.tzinfo, f44m) 2894 self.assertEqual(x.date(), dt.date()) 2895 self.assertEqual(x.time(), dt.time()) 2896 2897 # Replacing with different tzinfo does adjust. 2898 got = dt.astimezone(fm5h) 2899 self.assertIs(got.tzinfo, fm5h) 2900 self.assertEqual(got.utcoffset(), timedelta(hours=-5)) 2901 expected = dt - dt.utcoffset() # in effect, convert to UTC 2902 expected += fm5h.utcoffset(dt) # and from there to local time 2903 expected = expected.replace(tzinfo=fm5h) # and attach new tzinfo 2904 self.assertEqual(got.date(), expected.date()) 2905 self.assertEqual(got.time(), expected.time()) 2906 self.assertEqual(got.timetz(), expected.timetz()) 2907 self.assertIs(got.tzinfo, expected.tzinfo) 2908 self.assertEqual(got, expected) 2909 2910 def test_aware_subtract(self): 2911 cls = self.theclass 2912 2913 # Ensure that utcoffset() is ignored when the operands have the 2914 # same tzinfo member. 2915 class OperandDependentOffset(tzinfo): 2916 def utcoffset(self, t): 2917 if t.minute < 10: 2918 # d0 and d1 equal after adjustment 2919 return timedelta(minutes=t.minute) 2920 else: 2921 # d2 off in the weeds 2922 return timedelta(minutes=59) 2923 2924 base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) 2925 d0 = base.replace(minute=3) 2926 d1 = base.replace(minute=9) 2927 d2 = base.replace(minute=11) 2928 for x in d0, d1, d2: 2929 for y in d0, d1, d2: 2930 got = x - y 2931 expected = timedelta(minutes=x.minute - y.minute) 2932 self.assertEqual(got, expected) 2933 2934 # OTOH, if the tzinfo members are distinct, utcoffsets aren't 2935 # ignored. 2936 base = cls(8, 9, 10, 11, 12, 13, 14) 2937 d0 = base.replace(minute=3, tzinfo=OperandDependentOffset()) 2938 d1 = base.replace(minute=9, tzinfo=OperandDependentOffset()) 2939 d2 = base.replace(minute=11, tzinfo=OperandDependentOffset()) 2940 for x in d0, d1, d2: 2941 for y in d0, d1, d2: 2942 got = x - y 2943 if (x is d0 or x is d1) and (y is d0 or y is d1): 2944 expected = timedelta(0) 2945 elif x is y is d2: 2946 expected = timedelta(0) 2947 elif x is d2: 2948 expected = timedelta(minutes=(11-59)-0) 2949 else: 2950 assert y is d2 2951 expected = timedelta(minutes=0-(11-59)) 2952 self.assertEqual(got, expected) 2953 2954 def test_mixed_compare(self): 2955 t1 = datetime(1, 2, 3, 4, 5, 6, 7) 2956 t2 = datetime(1, 2, 3, 4, 5, 6, 7) 2957 self.assertEqual(t1, t2) 2958 t2 = t2.replace(tzinfo=None) 2959 self.assertEqual(t1, t2) 2960 t2 = t2.replace(tzinfo=FixedOffset(None, "")) 2961 self.assertEqual(t1, t2) 2962 t2 = t2.replace(tzinfo=FixedOffset(0, "")) 2963 self.assertRaises(TypeError, lambda: t1 == t2) 2964 2965 # In datetime w/ identical tzinfo objects, utcoffset is ignored. 2966 class Varies(tzinfo): 2967 def __init__(self): 2968 self.offset = timedelta(minutes=22) 2969 def utcoffset(self, t): 2970 self.offset += timedelta(minutes=1) 2971 return self.offset 2972 2973 v = Varies() 2974 t1 = t2.replace(tzinfo=v) 2975 t2 = t2.replace(tzinfo=v) 2976 self.assertEqual(t1.utcoffset(), timedelta(minutes=23)) 2977 self.assertEqual(t2.utcoffset(), timedelta(minutes=24)) 2978 self.assertEqual(t1, t2) 2979 2980 # But if they're not identical, it isn't ignored. 2981 t2 = t2.replace(tzinfo=Varies()) 2982 self.assertTrue(t1 < t2) # t1's offset counter still going up 2983 2984 def test_subclass_datetimetz(self): 2985 2986 class C(self.theclass): 2987 theAnswer = 42 2988 2989 def __new__(cls, *args, **kws): 2990 temp = kws.copy() 2991 extra = temp.pop('extra') 2992 result = self.theclass.__new__(cls, *args, **temp) 2993 result.extra = extra 2994 return result 2995 2996 def newmeth(self, start): 2997 return start + self.hour + self.year 2998 2999 args = 2002, 12, 31, 4, 5, 6, 500, FixedOffset(-300, "EST", 1) 3000 3001 dt1 = self.theclass(*args) 3002 dt2 = C(*args, **{'extra': 7}) 3003 3004 self.assertEqual(dt2.__class__, C) 3005 self.assertEqual(dt2.theAnswer, 42) 3006 self.assertEqual(dt2.extra, 7) 3007 self.assertEqual(dt1.utcoffset(), dt2.utcoffset()) 3008 self.assertEqual(dt2.newmeth(-7), dt1.hour + dt1.year - 7) 3009 3010# Pain to set up DST-aware tzinfo classes. 3011 3012def first_sunday_on_or_after(dt): 3013 days_to_go = 6 - dt.weekday() 3014 if days_to_go: 3015 dt += timedelta(days_to_go) 3016 return dt 3017 3018ZERO = timedelta(0) 3019HOUR = timedelta(hours=1) 3020DAY = timedelta(days=1) 3021# In the US, DST starts at 2am (standard time) on the first Sunday in April. 3022DSTSTART = datetime(1, 4, 1, 2) 3023# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct, 3024# which is the first Sunday on or after Oct 25. Because we view 1:MM as 3025# being standard time on that day, there is no spelling in local time of 3026# the last hour of DST (that's 1:MM DST, but 1:MM is taken as standard time). 3027DSTEND = datetime(1, 10, 25, 1) 3028 3029class USTimeZone(tzinfo): 3030 3031 def __init__(self, hours, reprname, stdname, dstname): 3032 self.stdoffset = timedelta(hours=hours) 3033 self.reprname = reprname 3034 self.stdname = stdname 3035 self.dstname = dstname 3036 3037 def __repr__(self): 3038 return self.reprname 3039 3040 def tzname(self, dt): 3041 if self.dst(dt): 3042 return self.dstname 3043 else: 3044 return self.stdname 3045 3046 def utcoffset(self, dt): 3047 return self.stdoffset + self.dst(dt) 3048 3049 def dst(self, dt): 3050 if dt is None or dt.tzinfo is None: 3051 # An exception instead may be sensible here, in one or more of 3052 # the cases. 3053 return ZERO 3054 assert dt.tzinfo is self 3055 3056 # Find first Sunday in April. 3057 start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) 3058 assert start.weekday() == 6 and start.month == 4 and start.day <= 7 3059 3060 # Find last Sunday in October. 3061 end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) 3062 assert end.weekday() == 6 and end.month == 10 and end.day >= 25 3063 3064 # Can't compare naive to aware objects, so strip the timezone from 3065 # dt first. 3066 if start <= dt.replace(tzinfo=None) < end: 3067 return HOUR 3068 else: 3069 return ZERO 3070 3071Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") 3072Central = USTimeZone(-6, "Central", "CST", "CDT") 3073Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") 3074Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") 3075utc_real = FixedOffset(0, "UTC", 0) 3076# For better test coverage, we want another flavor of UTC that's west of 3077# the Eastern and Pacific timezones. 3078utc_fake = FixedOffset(-12*60, "UTCfake", 0) 3079 3080class TestTimezoneConversions(unittest.TestCase): 3081 # The DST switch times for 2002, in std time. 3082 dston = datetime(2002, 4, 7, 2) 3083 dstoff = datetime(2002, 10, 27, 1) 3084 3085 theclass = datetime 3086 3087 # Check a time that's inside DST. 3088 def checkinside(self, dt, tz, utc, dston, dstoff): 3089 self.assertEqual(dt.dst(), HOUR) 3090 3091 # Conversion to our own timezone is always an identity. 3092 self.assertEqual(dt.astimezone(tz), dt) 3093 3094 asutc = dt.astimezone(utc) 3095 there_and_back = asutc.astimezone(tz) 3096 3097 # Conversion to UTC and back isn't always an identity here, 3098 # because there are redundant spellings (in local time) of 3099 # UTC time when DST begins: the clock jumps from 1:59:59 3100 # to 3:00:00, and a local time of 2:MM:SS doesn't really 3101 # make sense then. The classes above treat 2:MM:SS as 3102 # daylight time then (it's "after 2am"), really an alias 3103 # for 1:MM:SS standard time. The latter form is what 3104 # conversion back from UTC produces. 3105 if dt.date() == dston.date() and dt.hour == 2: 3106 # We're in the redundant hour, and coming back from 3107 # UTC gives the 1:MM:SS standard-time spelling. 3108 self.assertEqual(there_and_back + HOUR, dt) 3109 # Although during was considered to be in daylight 3110 # time, there_and_back is not. 3111 self.assertEqual(there_and_back.dst(), ZERO) 3112 # They're the same times in UTC. 3113 self.assertEqual(there_and_back.astimezone(utc), 3114 dt.astimezone(utc)) 3115 else: 3116 # We're not in the redundant hour. 3117 self.assertEqual(dt, there_and_back) 3118 3119 # Because we have a redundant spelling when DST begins, there is 3120 # (unfortunately) an hour when DST ends that can't be spelled at all in 3121 # local time. When DST ends, the clock jumps from 1:59 back to 1:00 3122 # again. The hour 1:MM DST has no spelling then: 1:MM is taken to be 3123 # standard time. 1:MM DST == 0:MM EST, but 0:MM is taken to be 3124 # daylight time. The hour 1:MM daylight == 0:MM standard can't be 3125 # expressed in local time. Nevertheless, we want conversion back 3126 # from UTC to mimic the local clock's "repeat an hour" behavior. 3127 nexthour_utc = asutc + HOUR 3128 nexthour_tz = nexthour_utc.astimezone(tz) 3129 if dt.date() == dstoff.date() and dt.hour == 0: 3130 # We're in the hour before the last DST hour. The last DST hour 3131 # is ineffable. We want the conversion back to repeat 1:MM. 3132 self.assertEqual(nexthour_tz, dt.replace(hour=1)) 3133 nexthour_utc += HOUR 3134 nexthour_tz = nexthour_utc.astimezone(tz) 3135 self.assertEqual(nexthour_tz, dt.replace(hour=1)) 3136 else: 3137 self.assertEqual(nexthour_tz - dt, HOUR) 3138 3139 # Check a time that's outside DST. 3140 def checkoutside(self, dt, tz, utc): 3141 self.assertEqual(dt.dst(), ZERO) 3142 3143 # Conversion to our own timezone is always an identity. 3144 self.assertEqual(dt.astimezone(tz), dt) 3145 3146 # Converting to UTC and back is an identity too. 3147 asutc = dt.astimezone(utc) 3148 there_and_back = asutc.astimezone(tz) 3149 self.assertEqual(dt, there_and_back) 3150 3151 def convert_between_tz_and_utc(self, tz, utc): 3152 dston = self.dston.replace(tzinfo=tz) 3153 # Because 1:MM on the day DST ends is taken as being standard time, 3154 # there is no spelling in tz for the last hour of daylight time. 3155 # For purposes of the test, the last hour of DST is 0:MM, which is 3156 # taken as being daylight time (and 1:MM is taken as being standard 3157 # time). 3158 dstoff = self.dstoff.replace(tzinfo=tz) 3159 for delta in (timedelta(weeks=13), 3160 DAY, 3161 HOUR, 3162 timedelta(minutes=1), 3163 timedelta(microseconds=1)): 3164 3165 self.checkinside(dston, tz, utc, dston, dstoff) 3166 for during in dston + delta, dstoff - delta: 3167 self.checkinside(during, tz, utc, dston, dstoff) 3168 3169 self.checkoutside(dstoff, tz, utc) 3170 for outside in dston - delta, dstoff + delta: 3171 self.checkoutside(outside, tz, utc) 3172 3173 def test_easy(self): 3174 # Despite the name of this test, the endcases are excruciating. 3175 self.convert_between_tz_and_utc(Eastern, utc_real) 3176 self.convert_between_tz_and_utc(Pacific, utc_real) 3177 self.convert_between_tz_and_utc(Eastern, utc_fake) 3178 self.convert_between_tz_and_utc(Pacific, utc_fake) 3179 # The next is really dancing near the edge. It works because 3180 # Pacific and Eastern are far enough apart that their "problem 3181 # hours" don't overlap. 3182 self.convert_between_tz_and_utc(Eastern, Pacific) 3183 self.convert_between_tz_and_utc(Pacific, Eastern) 3184 # OTOH, these fail! Don't enable them. The difficulty is that 3185 # the edge case tests assume that every hour is representable in 3186 # the "utc" class. This is always true for a fixed-offset tzinfo 3187 # class (lke utc_real and utc_fake), but not for Eastern or Central. 3188 # For these adjacent DST-aware time zones, the range of time offsets 3189 # tested ends up creating hours in the one that aren't representable 3190 # in the other. For the same reason, we would see failures in the 3191 # Eastern vs Pacific tests too if we added 3*HOUR to the list of 3192 # offset deltas in convert_between_tz_and_utc(). 3193 # 3194 # self.convert_between_tz_and_utc(Eastern, Central) # can't work 3195 # self.convert_between_tz_and_utc(Central, Eastern) # can't work 3196 3197 def test_tricky(self): 3198 # 22:00 on day before daylight starts. 3199 fourback = self.dston - timedelta(hours=4) 3200 ninewest = FixedOffset(-9*60, "-0900", 0) 3201 fourback = fourback.replace(tzinfo=ninewest) 3202 # 22:00-0900 is 7:00 UTC == 2:00 EST == 3:00 DST. Since it's "after 3203 # 2", we should get the 3 spelling. 3204 # If we plug 22:00 the day before into Eastern, it "looks like std 3205 # time", so its offset is returned as -5, and -5 - -9 = 4. Adding 4 3206 # to 22:00 lands on 2:00, which makes no sense in local time (the 3207 # local clock jumps from 1 to 3). The point here is to make sure we 3208 # get the 3 spelling. 3209 expected = self.dston.replace(hour=3) 3210 got = fourback.astimezone(Eastern).replace(tzinfo=None) 3211 self.assertEqual(expected, got) 3212 3213 # Similar, but map to 6:00 UTC == 1:00 EST == 2:00 DST. In that 3214 # case we want the 1:00 spelling. 3215 sixutc = self.dston.replace(hour=6, tzinfo=utc_real) 3216 # Now 6:00 "looks like daylight", so the offset wrt Eastern is -4, 3217 # and adding -4-0 == -4 gives the 2:00 spelling. We want the 1:00 EST 3218 # spelling. 3219 expected = self.dston.replace(hour=1) 3220 got = sixutc.astimezone(Eastern).replace(tzinfo=None) 3221 self.assertEqual(expected, got) 3222 3223 # Now on the day DST ends, we want "repeat an hour" behavior. 3224 # UTC 4:MM 5:MM 6:MM 7:MM checking these 3225 # EST 23:MM 0:MM 1:MM 2:MM 3226 # EDT 0:MM 1:MM 2:MM 3:MM 3227 # wall 0:MM 1:MM 1:MM 2:MM against these 3228 for utc in utc_real, utc_fake: 3229 for tz in Eastern, Pacific: 3230 first_std_hour = self.dstoff - timedelta(hours=2) # 23:MM 3231 # Convert that to UTC. 3232 first_std_hour -= tz.utcoffset(None) 3233 # Adjust for possibly fake UTC. 3234 asutc = first_std_hour + utc.utcoffset(None) 3235 # First UTC hour to convert; this is 4:00 when utc=utc_real & 3236 # tz=Eastern. 3237 asutcbase = asutc.replace(tzinfo=utc) 3238 for tzhour in (0, 1, 1, 2): 3239 expectedbase = self.dstoff.replace(hour=tzhour) 3240 for minute in 0, 30, 59: 3241 expected = expectedbase.replace(minute=minute) 3242 asutc = asutcbase.replace(minute=minute) 3243 astz = asutc.astimezone(tz) 3244 self.assertEqual(astz.replace(tzinfo=None), expected) 3245 asutcbase += HOUR 3246 3247 3248 def test_bogus_dst(self): 3249 class ok(tzinfo): 3250 def utcoffset(self, dt): return HOUR 3251 def dst(self, dt): return HOUR 3252 3253 now = self.theclass.now().replace(tzinfo=utc_real) 3254 # Doesn't blow up. 3255 now.astimezone(ok()) 3256 3257 # Does blow up. 3258 class notok(ok): 3259 def dst(self, dt): return None 3260 self.assertRaises(ValueError, now.astimezone, notok()) 3261 3262 def test_fromutc(self): 3263 self.assertRaises(TypeError, Eastern.fromutc) # not enough args 3264 now = datetime.utcnow().replace(tzinfo=utc_real) 3265 self.assertRaises(ValueError, Eastern.fromutc, now) # wrong tzinfo 3266 now = now.replace(tzinfo=Eastern) # insert correct tzinfo 3267 enow = Eastern.fromutc(now) # doesn't blow up 3268 self.assertEqual(enow.tzinfo, Eastern) # has right tzinfo member 3269 self.assertRaises(TypeError, Eastern.fromutc, now, now) # too many args 3270 self.assertRaises(TypeError, Eastern.fromutc, date.today()) # wrong type 3271 3272 # Always converts UTC to standard time. 3273 class FauxUSTimeZone(USTimeZone): 3274 def fromutc(self, dt): 3275 return dt + self.stdoffset 3276 FEastern = FauxUSTimeZone(-5, "FEastern", "FEST", "FEDT") 3277 3278 # UTC 4:MM 5:MM 6:MM 7:MM 8:MM 9:MM 3279 # EST 23:MM 0:MM 1:MM 2:MM 3:MM 4:MM 3280 # EDT 0:MM 1:MM 2:MM 3:MM 4:MM 5:MM 3281 3282 # Check around DST start. 3283 start = self.dston.replace(hour=4, tzinfo=Eastern) 3284 fstart = start.replace(tzinfo=FEastern) 3285 for wall in 23, 0, 1, 3, 4, 5: 3286 expected = start.replace(hour=wall) 3287 if wall == 23: 3288 expected -= timedelta(days=1) 3289 got = Eastern.fromutc(start) 3290 self.assertEqual(expected, got) 3291 3292 expected = fstart + FEastern.stdoffset 3293 got = FEastern.fromutc(fstart) 3294 self.assertEqual(expected, got) 3295 3296 # Ensure astimezone() calls fromutc() too. 3297 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) 3298 self.assertEqual(expected, got) 3299 3300 start += HOUR 3301 fstart += HOUR 3302 3303 # Check around DST end. 3304 start = self.dstoff.replace(hour=4, tzinfo=Eastern) 3305 fstart = start.replace(tzinfo=FEastern) 3306 for wall in 0, 1, 1, 2, 3, 4: 3307 expected = start.replace(hour=wall) 3308 got = Eastern.fromutc(start) 3309 self.assertEqual(expected, got) 3310 3311 expected = fstart + FEastern.stdoffset 3312 got = FEastern.fromutc(fstart) 3313 self.assertEqual(expected, got) 3314 3315 # Ensure astimezone() calls fromutc() too. 3316 got = fstart.replace(tzinfo=utc_real).astimezone(FEastern) 3317 self.assertEqual(expected, got) 3318 3319 start += HOUR 3320 fstart += HOUR 3321 3322 3323############################################################################# 3324# oddballs 3325 3326class Oddballs(unittest.TestCase): 3327 3328 def test_bug_1028306(self): 3329 # Trying to compare a date to a datetime should act like a mixed- 3330 # type comparison, despite that datetime is a subclass of date. 3331 as_date = date.today() 3332 as_datetime = datetime.combine(as_date, time()) 3333 self.assertTrue(as_date != as_datetime) 3334 self.assertTrue(as_datetime != as_date) 3335 self.assertFalse(as_date == as_datetime) 3336 self.assertFalse(as_datetime == as_date) 3337 self.assertRaises(TypeError, lambda: as_date < as_datetime) 3338 self.assertRaises(TypeError, lambda: as_datetime < as_date) 3339 self.assertRaises(TypeError, lambda: as_date <= as_datetime) 3340 self.assertRaises(TypeError, lambda: as_datetime <= as_date) 3341 self.assertRaises(TypeError, lambda: as_date > as_datetime) 3342 self.assertRaises(TypeError, lambda: as_datetime > as_date) 3343 self.assertRaises(TypeError, lambda: as_date >= as_datetime) 3344 self.assertRaises(TypeError, lambda: as_datetime >= as_date) 3345 3346 # Nevertheless, comparison should work with the base-class (date) 3347 # projection if use of a date method is forced. 3348 self.assertTrue(as_date.__eq__(as_datetime)) 3349 different_day = (as_date.day + 1) % 20 + 1 3350 self.assertFalse(as_date.__eq__(as_datetime.replace(day=different_day))) 3351 3352 # And date should compare with other subclasses of date. If a 3353 # subclass wants to stop this, it's up to the subclass to do so. 3354 date_sc = SubclassDate(as_date.year, as_date.month, as_date.day) 3355 self.assertEqual(as_date, date_sc) 3356 self.assertEqual(date_sc, as_date) 3357 3358 # Ditto for datetimes. 3359 datetime_sc = SubclassDatetime(as_datetime.year, as_datetime.month, 3360 as_date.day, 0, 0, 0) 3361 self.assertEqual(as_datetime, datetime_sc) 3362 self.assertEqual(datetime_sc, as_datetime) 3363 3364def test_main(): 3365 test_support.run_unittest(__name__) 3366 3367if __name__ == "__main__": 3368 test_main() 3369