diff --git a/Doc/library/time.rst b/Doc/library/time.rst index dc102d6..870713d 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -216,7 +216,7 @@ The module defines the following functions and data items: amount because of the scheduling of other activity in the system. -.. function:: strftime(format[, t]) +.. function:: strftime(format[, t, accept2dyear=None]) Convert a tuple or :class:`struct_time` representing a time as returned by :func:`gmtime` or :func:`localtime` to a string as specified by the *format* diff --git a/Lib/datetime.py b/Lib/datetime.py index 47e54ec..5693dde 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -172,10 +172,6 @@ def _format_time(hh, mm, ss, us): # Correctly substitute for %z and %Z escapes in strftime formats. def _wrap_strftime(object, format, timetuple): - year = timetuple[0] - if year < 1000: - raise ValueError("year=%d is before 1000; the datetime strftime() " - "methods require year >= 1000" % year) # Don't call utcoffset() or tzname() unless actually needed. freplace = None # the string to use for %f zreplace = None # the string to use for %z @@ -230,7 +226,7 @@ def _wrap_strftime(object, format, timetuple): else: push(ch) newformat = "".join(newformat) - return _time.strftime(newformat, timetuple) + return _time.strftime(newformat, timetuple, accept2dyear=False) def _call_tzinfo_method(tzinfo, methname, tzinfoarg): if tzinfo is None: @@ -1189,8 +1185,6 @@ class time: """Format using strftime(). The date part of the timestamp passed to underlying strftime should not be used. """ - # The year must be >= 1000 else Python's strftime implementation - # can raise a bogus exception. timetuple = (1900, 1, 1, self._hour, self._minute, self._second, 0, 1, -1) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 4b5c890..fa3adff 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1283,13 +1283,6 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase): self.assertTrue(self.theclass.min) self.assertTrue(self.theclass.max) - def test_strftime_out_of_range(self): - # For nasty technical reasons, we can't handle years before 1000. - cls = self.theclass - self.assertEqual(cls(1000, 1, 1).strftime("%Y"), "1000") - for y in 1, 49, 51, 99, 100, 999: - self.assertRaises(ValueError, cls(y, 1, 1).strftime, "%Y") - def test_replace(self): cls = self.theclass args = [1, 2, 3] @@ -1843,6 +1836,10 @@ class TestDateTime(TestDate): self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"), "12 31 04 000047 33 22 06 366") + for year in (1, 567, 1234, 2010, 8765, 9999): + t = self.theclass(year, 1, 1, 12, 0, 0) + self.assertEqual(t.strftime("%Y"), str(year)) + def test_extract(self): dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) self.assertEqual(dt.date(), date(2002, 3, 4)) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 6195940..f80ad64 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -766,7 +766,7 @@ typedef struct PyObject *name; } PyDateTime_TimeZone; -/* The interned UTC timezone instance */ +/* The interned UTC timezone instance */ static PyObject *PyDateTime_TimeZone_UTC; /* Create new timezone instance checking offset range. This @@ -1166,31 +1166,6 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, if (!pin) return NULL; - /* Give up if the year is before 1000. - * Python strftime() plays games with the year, and different - * games depending on whether envar PYTHON2K is set. This makes - * years before 1000 a nightmare, even if the platform strftime - * supports them (and not all do). - * We could get a lot farther here by avoiding Python's strftime - * wrapper and calling the C strftime() directly, but that isn't - * an option in the Python implementation of this module. - */ - { - long year; - PyObject *pyyear = PySequence_GetItem(timetuple, 0); - if (pyyear == NULL) return NULL; - assert(PyLong_Check(pyyear)); - year = PyLong_AsLong(pyyear); - Py_DECREF(pyyear); - if (year < 1000) { - PyErr_Format(PyExc_ValueError, "year=%ld is before " - "1000; the datetime strftime() " - "methods require year >= 1000", - year); - return NULL; - } - } - /* Scan the input format, looking for %z/%Z/%f escapes, building * a new format. Since computing the replacements for those codes * is expensive, don't unless they're actually used. @@ -1311,8 +1286,8 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, goto Done; format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt)); if (format != NULL) { - result = PyObject_CallMethod(time, "strftime", "OO", - format, timetuple, NULL); + result = PyObject_CallMethod(time, "strftime", "OOi", + format, timetuple, 0, NULL); Py_DECREF(format); } Py_DECREF(time); diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 5732f15..d20d34e 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -294,7 +294,7 @@ When 'seconds' is not passed in, convert the current time instead."); * an exception and return 0 on error. */ static int -gettmarg(PyObject *args, struct tm *p) +gettmarg(PyObject *args, struct tm *p, int accept2dyear) { int y; @@ -330,12 +330,17 @@ gettmarg(PyObject *args, struct tm *p) * values are interpreted as given. */ if (y < 1000) { - PyObject *accept = PyDict_GetItemString(moddict, - "accept2dyear"); - int acceptval = accept != NULL && PyObject_IsTrue(accept); - if (acceptval == -1) - return 0; - if (acceptval) { + PyObject *accept; + if (accept2dyear < 0) { + accept = PyDict_GetItemString(moddict, + "accept2dyear"); + if (accept == NULL) + return 0; + accept2dyear = PyObject_IsTrue(accept); + if (accept2dyear == -1) + return 0; + } + if (accept2dyear) { if (0 <= y && y < 69) y += 2000; else if (69 <= y && y < 100) @@ -439,7 +444,7 @@ checktm(struct tm* buf) #endif static PyObject * -time_strftime(PyObject *self, PyObject *args) +time_strftime(PyObject *self, PyObject *args, PyObject *kwds) { PyObject *tup = NULL; struct tm buf; @@ -454,28 +459,31 @@ time_strftime(PyObject *self, PyObject *args) time_char *outbuf = NULL; size_t i; PyObject *ret = NULL; + int accept2dyear = -1; + static char *kwlist[] = {"format", "t", "accept2dyear", NULL}; memset((void *) &buf, '\0', sizeof(buf)); /* Will always expect a unicode string to be passed as format. Given that there's no str type anymore in py3k this seems safe. */ - if (!PyArg_ParseTuple(args, "U|O:strftime", &format_arg, &tup)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "U|Oi:strftime", kwlist, + &format_arg, &tup, &accept2dyear)) return NULL; if (tup == NULL) { time_t tt = time(NULL); buf = *localtime(&tt); } - else if (!gettmarg(tup, &buf) || !checktm(&buf)) + else if (!gettmarg(tup, &buf, accept2dyear) || !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", + if (buf.tm_year < -1899) { + PyErr_Format(PyExc_ValueError, + "strftime() requires year in [1; 9999], year=%d", buf.tm_year + 1900); } @@ -636,7 +644,7 @@ time_asctime(PyObject *self, PyObject *args) if (tup == NULL) { time_t tt = time(NULL); buf = *localtime(&tt); - } else if (!gettmarg(tup, &buf) || !checktm(&buf)) + } else if (!gettmarg(tup, &buf, -1) || !checktm(&buf)) return NULL; return _asctime(&buf); } @@ -688,7 +696,7 @@ time_mktime(PyObject *self, PyObject *tup) { struct tm buf; time_t tt; - if (!gettmarg(tup, &buf)) + if (!gettmarg(tup, &buf, -1)) return NULL; tt = mktime(&buf); if (tt == (time_t)(-1)) { @@ -847,7 +855,8 @@ static PyMethodDef time_methods[] = { {"mktime", time_mktime, METH_O, mktime_doc}, #endif #ifdef HAVE_STRFTIME - {"strftime", time_strftime, METH_VARARGS, strftime_doc}, + {"strftime", time_strftime, METH_VARARGS | METH_KEYWORDS, + strftime_doc}, #endif {"strptime", time_strptime, METH_VARARGS, strptime_doc}, #ifdef HAVE_WORKING_TZSET