From 73ea29cb039318a70b3cbc9ab2697308a470b5ba Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 8 Jan 2011 01:56:31 +0000 Subject: [PATCH] Issue #1777412: strftime() accepts year >= 1 instead of year >= 1900 * With Visual Studio, year have to be in [1; 9999] * Add more tests on the year field --- Lib/test/test_time.py | 100 +++++++++++++++++++++++++++++------------- Modules/timemodule.c | 18 +++++--- 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index d3de933cc8a..c2fd9ff1b51 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -43,14 +43,8 @@ class TimeTestCase(unittest.TestCase): # Make sure that strftime() checks the bounds of the various parts #of the time tuple (0 is valid for *all* values). - # Check year [1900, max(int)] - self.assertRaises(ValueError, func, - (999, 1, 1, 0, 0, 0, 0, 1, -1)) - if time.accept2dyear: - self.assertRaises(ValueError, func, - (-1, 1, 1, 0, 0, 0, 0, 1, -1)) - self.assertRaises(ValueError, func, - (100, 1, 1, 0, 0, 0, 0, 1, -1)) + # The year field is tested by other test cases above + # Check month [1, 12] + zero support self.assertRaises(ValueError, func, (1900, -1, 1, 0, 0, 0, 0, 1, -1)) @@ -267,8 +261,10 @@ class TestLocale(unittest.TestCase): # This should not cause an exception time.strftime("%B", (2009,2,1,0,0,0,0,0,0)) -class TestAccept2Year(unittest.TestCase): - accept2dyear = 1 + +class _BaseYearTest(unittest.TestCase): + accept2dyear = None + def setUp(self): self.saved_accept2dyear = time.accept2dyear time.accept2dyear = self.accept2dyear @@ -277,10 +273,37 @@ class TestAccept2Year(unittest.TestCase): time.accept2dyear = self.saved_accept2dyear def yearstr(self, y): - # return time.strftime('%Y', (y,) + (0,) * 8) + raise NotImplementedError() + +class _TestAsctimeYear: + def yearstr(self, y): return time.asctime((y,) + (0,) * 8).split()[-1] - def test_2dyear(self): + def test_large_year(self): + # Check that it doesn't crash with year > 9999 + self.assertEqual(self.yearstr(12345), '12345') + self.assertEqual(self.yearstr(123456789), '123456789') + +class _TestStrftimeYear: + def yearstr(self, y): + return time.strftime('%Y', (y,) + (0,) * 8).split()[-1] + + def test_large_year(self): + # Just check that it doesn't crash with year > 9999: it may or may not + # raise an error depending on the OS and compiler + try: + self.yearstr(12345) + except ValueError: + pass + try: + self.yearstr(123456) + except ValueError: + pass + +class _Test2dYear(_BaseYearTest): + accept2dyear = 1 + + def test_year(self): with support.check_warnings(): self.assertEqual(self.yearstr(0), '2000') self.assertEqual(self.yearstr(69), '1969') @@ -288,27 +311,41 @@ class TestAccept2Year(unittest.TestCase): self.assertEqual(self.yearstr(99), '1999') def test_invalid(self): - self.assertRaises(ValueError, self.yearstr, 999) - self.assertRaises(ValueError, self.yearstr, 100) self.assertRaises(ValueError, self.yearstr, -1) + self.assertRaises(ValueError, self.yearstr, 100) + self.assertRaises(ValueError, self.yearstr, 999) -class TestAccept2YearBool(TestAccept2Year): - accept2dyear = True - -class TestDontAccept2Year(TestAccept2Year): +class _Test4dYear(_BaseYearTest): accept2dyear = 0 - def test_2dyear(self): - self.assertEqual(self.yearstr(0), '0') + + def test_year(self): + self.assertEqual(self.yearstr(1), '1') self.assertEqual(self.yearstr(69), '69') self.assertEqual(self.yearstr(68), '68') self.assertEqual(self.yearstr(99), '99') self.assertEqual(self.yearstr(999), '999') self.assertEqual(self.yearstr(9999), '9999') - def test_invalid(self): - pass +class TestAsctimeAccept2dYear(_TestAsctimeYear, _Test2dYear): + pass -class TestAccept2YearBad(TestAccept2Year): +class TestStrftimeAccept2dYear(_TestStrftimeYear, _Test2dYear): + pass + +class TestAsctime4dyear(_TestAsctimeYear, _Test4dYear): + pass + +class TestStrftime4dyear(_TestStrftimeYear, _Test4dYear): + def test_bounds(self): + self.assertRaises(ValueError, self.yearstr, 0) + +class Test2dyearBool(_TestAsctimeYear, _Test2dYear): + accept2dyear = True + +class Test4dyearBool(_TestAsctimeYear, _Test4dYear): + accept2dyear = False + +class TestAccept2YearBad(_TestAsctimeYear, _BaseYearTest): class X: def __bool__(self): raise RuntimeError('boo') @@ -319,14 +356,17 @@ class TestAccept2YearBad(TestAccept2Year): self.assertRaises(RuntimeError, self.yearstr, 200) -class TestDontAccept2YearBool(TestDontAccept2Year): - accept2dyear = False - - def test_main(): - support.run_unittest(TimeTestCase, TestLocale, - TestAccept2Year, TestAccept2YearBool, TestAccept2YearBad, - TestDontAccept2Year, TestDontAccept2YearBool) + support.run_unittest( + TimeTestCase, + TestLocale, + TestAsctimeAccept2dYear, + TestStrftimeAccept2dYear, + TestAsctime4dyear, + TestStrftime4dyear, + Test2dyearBool, + Test4dyearBool, + TestAccept2YearBad) if __name__ == "__main__": test_main() diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 46c90ecdc97..de1588f0e14 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -474,15 +474,21 @@ time_strftime(PyObject *self, PyObject *args) else if (!gettmarg(tup, &buf) || !checktm(&buf)) return NULL; - /* XXX: Reportedly, some systems have issues formating dates prior to year - * 1000. These systems should be identified and this check should be - * moved to appropriate system specific section below. */ - if (buf.tm_year < -900) { - PyErr_Format(PyExc_ValueError, "year=%d is before 1900; " - "the strftime() method requires year >= 1900", +#ifdef _MSC_VER + if (buf.tm_year + 1900 < 1 || 9999 < buf.tm_year + 1900) { + PyErr_Format(PyExc_ValueError, + "strftime() requires year in [1; 9999]", buf.tm_year + 1900); return NULL; } +#else + if (buf.tm_year + 1900 < 1) { + PyErr_Format(PyExc_ValueError, + "strftime() requires year >= 1", + buf.tm_year + 1900); + return NULL; + } +#endif /* Normalize tm_isdst just in case someone foolishly implements %Z based on the assumption that tm_isdst falls within the range of