From 75f94c210a9c270dda64135030a29e7791827473 Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Mon, 21 Jun 2010 15:21:14 +0000 Subject: [PATCH] Issue #9005: Prevent utctimetuple() from producing year 0 or year 10,000. --- Doc/library/datetime.rst | 8 ++++---- Lib/test/test_datetime.py | 42 ++++++++++++++++++--------------------- Misc/NEWS | 5 +++++ Modules/datetimemodule.c | 12 +++-------- 4 files changed, 31 insertions(+), 36 deletions(-) diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst index 90c14c12abd..f626e357c43 100644 --- a/Doc/library/datetime.rst +++ b/Doc/library/datetime.rst @@ -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. diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 42c18e69531..5b415af2ba9 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -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") diff --git a/Misc/NEWS b/Misc/NEWS index a1d3a838f06..3d109f1569a 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -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 diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index ddb5a054cfb..8b3cc06e7c7 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -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); }