Issue #9005: Prevent utctimetuple() from producing year 0 or year 10,000.

This commit is contained in:
Alexander Belopolsky 2010-06-21 15:21:14 +00:00
parent c56b094bab
commit 75f94c210a
4 changed files with 31 additions and 36 deletions

View File

@ -974,10 +974,10 @@ Instance methods:
``d.dst()`` returns. DST is never in effect for a UTC time.
If *d* is aware, *d* is normalized to UTC time, by subtracting
``d.utcoffset()``, and a :class:`time.struct_time` for the normalized time is
returned. :attr:`tm_isdst` is forced to 0. Note that the result's
:attr:`tm_year` member may be :const:`MINYEAR`\ -1 or :const:`MAXYEAR`\ +1, if
*d*.year was ``MINYEAR`` or ``MAXYEAR`` and UTC adjustment spills over a year
``d.utcoffset()``, and a :class:`time.struct_time` for the
normalized time is returned. :attr:`tm_isdst` is forced to 0. Note
that an :exc:`OverflowError` may be raised if *d*.year was
``MINYEAR`` or ``MAXYEAR`` and UTC adjustment spills over a year
boundary.

View File

@ -2997,8 +2997,6 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
def utcoffset(self, dt):
return self.uofs
# Ensure tm_isdst is 0 regardless of what dst() says: DST is never
# in effect for a UTC time.
for dstvalue in -33, 33, 0, None:
d = cls(1, 2, 3, 10, 20, 30, 40, tzinfo=UOFS(-53, dstvalue))
t = d.utctimetuple()
@ -3011,34 +3009,32 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase, unittest.TestCase):
self.assertEqual(d.weekday(), t.tm_wday)
self.assertEqual(d.toordinal() - date(1, 1, 1).toordinal() + 1,
t.tm_yday)
# Ensure tm_isdst is 0 regardless of what dst() says: DST
# is never in effect for a UTC time.
self.assertEqual(0, t.tm_isdst)
# At the edges, UTC adjustment can normalize into years out-of-range
# for a datetime object. Ensure that a correct timetuple is
# created anyway.
# Check that utctimetuple() is the same as
# astimezone(utc).timetuple()
d = cls(2010, 11, 13, 14, 15, 16, 171819)
for tz in [timezone.min, timezone.utc, timezone.max]:
dtz = d.replace(tzinfo=tz)
self.assertEqual(dtz.utctimetuple()[:-1],
dtz.astimezone(timezone.utc).timetuple()[:-1])
# At the edges, UTC adjustment can produce years out-of-range
# for a datetime object. Ensure that an OverflowError is
# raised.
tiny = cls(MINYEAR, 1, 1, 0, 0, 37, tzinfo=UOFS(1439))
# That goes back 1 minute less than a full day.
t = tiny.utctimetuple()
self.assertEqual(t.tm_year, MINYEAR-1)
self.assertEqual(t.tm_mon, 12)
self.assertEqual(t.tm_mday, 31)
self.assertEqual(t.tm_hour, 0)
self.assertEqual(t.tm_min, 1)
self.assertEqual(t.tm_sec, 37)
self.assertEqual(t.tm_yday, 366) # "year 0" is a leap year
self.assertEqual(t.tm_isdst, 0)
self.assertRaises(OverflowError, tiny.utctimetuple)
huge = cls(MAXYEAR, 12, 31, 23, 59, 37, 999999, tzinfo=UOFS(-1439))
# That goes forward 1 minute less than a full day.
t = huge.utctimetuple()
self.assertEqual(t.tm_year, MAXYEAR+1)
self.assertEqual(t.tm_mon, 1)
self.assertEqual(t.tm_mday, 1)
self.assertEqual(t.tm_hour, 23)
self.assertEqual(t.tm_min, 58)
self.assertEqual(t.tm_sec, 37)
self.assertEqual(t.tm_yday, 1)
self.assertEqual(t.tm_isdst, 0)
self.assertRaises(OverflowError, huge.utctimetuple)
# More overflow cases
tiny = cls.min.replace(tzinfo=timezone(MINUTE))
self.assertRaises(OverflowError, tiny.utctimetuple)
huge = cls.max.replace(tzinfo=timezone(-MINUTE))
self.assertRaises(OverflowError, huge.utctimetuple)
def test_tzinfo_isoformat(self):
zero = FixedOffset(0, "+00:00")

View File

@ -1330,6 +1330,11 @@ Library
Extension Modules
-----------------
- Issue #9005: Prevent utctimetuple() from producing year 0 or year
10,000. Prior to this change, timezone adjustment in utctimetuple()
could produce tm_year value of 0 or 10,000. Now an OverflowError is
raised in these edge cases.
- Issue #6641: The ``datetime.strptime`` method now supports the
``%z`` directive. When the ``%z`` directive is present in the
format string, an aware ``datetime`` object is returned with

View File

@ -4932,15 +4932,9 @@ datetime_utctimetuple(PyDateTime_DateTime *self)
mm -= offset;
stat = normalize_datetime(&y, &m, &d, &hh, &mm, &ss, &us);
if (stat < 0) {
/* At the edges, it's possible we overflowed
* beyond MINYEAR or MAXYEAR.
*/
if (PyErr_ExceptionMatches(PyExc_OverflowError))
PyErr_Clear();
else
return NULL;
}
/* OverflowError may be raised in the edge cases. */
if (stat < 0)
return NULL;
}
return build_struct_time(y, m, d, hh, mm, ss, 0);
}