diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 2f838c44555..d729c7efd52 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1351,6 +1351,17 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase): #check that this standard extension works t.strftime("%f") + def test_strftime_trailing_percent(self): + # bpo-35066: make sure trailing '%' doesn't cause + # datetime's strftime to complain + t = self.theclass(2005, 3, 2) + try: + _time.strftime('%') + except ValueError: + self.skipTest('time module does not support trailing %') + self.assertEqual(t.strftime('%'), '%') + self.assertEqual(t.strftime("m:%m d:%d y:%y %"), "m:03 d:02 y:05 %") + def test_format(self): dt = self.theclass(2007, 9, 10) self.assertEqual(dt.__format__(''), str(dt)) diff --git a/Misc/NEWS.d/next/Library/2018-11-29-09-38-40.bpo-35066.Nwej2s.rst b/Misc/NEWS.d/next/Library/2018-11-29-09-38-40.bpo-35066.Nwej2s.rst new file mode 100644 index 00000000000..b0c39bd8638 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-29-09-38-40.bpo-35066.Nwej2s.rst @@ -0,0 +1,5 @@ +Previously, calling the strftime() method on a datetime object with a +trailing '%' in the format string would result in an exception. However, +this only occured when the datetime C module was being used; the python +implementation did not match this behavior. Datetime is now PEP-399 +compliant, and will not throw an exception on a trailing '%'. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 16c7bffda8f..7997758908b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -1528,10 +1528,13 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ntoappend = 1; } else if ((ch = *pin++) == '\0') { - /* There's a lone trailing %; doesn't make sense. */ - PyErr_SetString(PyExc_ValueError, "strftime format " - "ends with raw %"); - goto Done; + /* Null byte follows %, copy only '%'. + * + * Back the pin up one char so that we catch the null check + * the next time through the loop.*/ + pin--; + ptoappend = pin - 1; + ntoappend = 1; } /* A % has been seen and ch is the character after it. */ else if (ch == 'z') { @@ -1616,7 +1619,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, usednew += ntoappend; assert(usednew <= totalnew); } /* end while() */ - + if (_PyBytes_Resize(&newfmt, usednew) < 0) goto Done; {