diff -r e7ed7db521e9 Doc/library/datetime.rst --- a/Doc/library/datetime.rst Fri Dec 18 19:37:02 2015 +0200 +++ b/Doc/library/datetime.rst Sun Dec 20 13:18:57 2015 +0100 @@ -1133,7 +1133,7 @@ ``self.date().isocalendar()``. -.. method:: datetime.isoformat(sep='T') +.. method:: datetime.isoformat(sep='T', timespec='auto') Return a string representing the date and time in ISO 8601 format, YYYY-MM-DDTHH:MM:SS.mmmmmm or, if :attr:`microsecond` is 0, @@ -1154,6 +1154,35 @@ >>> datetime(2002, 12, 25, tzinfo=TZ()).isoformat(' ') '2002-12-25 00:00:00-06:39' + The optional argument *timespec* specifies the number of additional + terms of the time to include. It can be one of the following: + + - 'auto': Append microseconds except if microsecond is 0. + - 'hours': Append the hour of the day to the date. + - 'minutes': Append the hours and minutes. + - 'seconds': Append the hours, minutes and seconds. + - 'milliseconds': Append the hours, minutes, seconds and milliseconds. + - 'microseconds': Append the hours, minutes, seconds and microseconds. + - 'nanoseconds': Append the hours, minutes, seconds and nanoseconds. + + >>> from datetime import datetime + >>> datetime.now().isoformat(timespec='minutes') + '2002-12-25T00:00' + >>> dt = datetime(2015, 1, 1, 12, 30, 59, 123456) + >>> dt.isoformat(timespec='milliseconds') + '2015-01-01T12:30:59.123' + >>> dt.isoformat(timespec='microseconds') + '2015-01-01T12:30:59.123456' + >>> dt.isoformat(timespec='nanoseconds') + + Milliseconds, microseconds and nanoseconds are displayed only if microsecond + is not 0. + + >>> from datetime import datetime + >>> dt = datetime(2015, 1, 1, 12, 30, 59, 0) + >>> dt.isoformat(timespec='nanoseconds') + '2015-01-01T12:30:59' + .. method:: datetime.__str__() diff -r e7ed7db521e9 Lib/datetime.py --- a/Lib/datetime.py Fri Dec 18 19:37:02 2015 +0200 +++ b/Lib/datetime.py Sun Dec 20 13:18:57 2015 +0100 @@ -152,13 +152,37 @@ dnum = _days_before_month(y, m) + d return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag)) -def _format_time(hh, mm, ss, us): - # Skip trailing microseconds when us==0. + +def _format_time(hh, mm, ss, us, timespec=None): + # Skip trailing microseconds, milliseconds and nanoseconds when us==0. + _us_FORMAT = { + 'milliseconds': (".%03d", (us // 1000)), + 'microseconds': (".%06d", us), + 'auto': (".%06d", us), + 'nanoseconds': (".%09d", (us * 1000)), + } result = "%02d:%02d:%02d" % (hh, mm, ss) if us: result += ".%06d" % us + + if timespec: + result = "%02d" % hh + if timespec == 'hours': + return result + result += ":%02d" % mm + if timespec == 'minutes': + return result + result += ":%02d" % ss + if timespec == 'seconds': + return result + if us: + if timespec in _us_FORMAT: + fmt, value = _us_FORMAT[timespec] + result += fmt % value + return result + # Correctly substitute for %z and %Z escapes in strftime formats. def _wrap_strftime(object, format, timetuple): # Don't call utcoffset() or tzname() unless actually needed. @@ -1549,21 +1573,37 @@ self._hour, self._minute, self._second, self._year) - def isoformat(self, sep='T'): + def isoformat(self, sep='T', timespec='auto'): """Return the time formatted according to ISO. - This is 'YYYY-MM-DD HH:MM:SS.mmmmmm', or 'YYYY-MM-DD HH:MM:SS' if + This is 'YYYY-MM-DD HH:MM:SS.mmm[mmm[000]]]', or 'YYYY-MM-DD HH:MM:SS' if self.microsecond == 0. If self.tzinfo is not None, the UTC offset is also attached, giving - 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'. + 'YYYY-MM-DD HH:MM:SS.mmm[mmm[000]]+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'. Optional argument sep specifies the separator between date and time, default 'T'. + + The optional argument timespec specifies the number of additional + terms of the time to include. It can be one of the following: + + 'auto': Append microseconds except if microsecond is 0. + 'hours': Append the hour of the day to the date. + 'minutes': Append the hours and minutes. + 'seconds': Append the hours, minutes and seconds. + 'milliseconds': Append the hours, minutes, seconds and milliseconds. + 'microseconds': Append the hours, minutes, seconds and microseconds. + 'nanoseconds': Append the hours, minutes, seconds and nanoseconds. """ + if timespec not in ('auto', 'hours', 'minutes', 'seconds', + 'milliseconds', 'microseconds', 'nanoseconds'): + raise ValueError("Unknown timespec value") + s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) + _format_time(self._hour, self._minute, self._second, - self._microsecond)) + self._microsecond, timespec)) + off = self.utcoffset() if off is not None: if off.days < 0: diff -r e7ed7db521e9 Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py Fri Dec 18 19:37:02 2015 +0200 +++ b/Lib/test/datetimetester.py Sun Dec 20 13:18:57 2015 +0100 @@ -1556,13 +1556,25 @@ self.assertEqual(dt, dt2) def test_isoformat(self): - t = self.theclass(2, 3, 2, 4, 5, 1, 123) - self.assertEqual(t.isoformat(), "0002-03-02T04:05:01.000123") - self.assertEqual(t.isoformat('T'), "0002-03-02T04:05:01.000123") - self.assertEqual(t.isoformat(' '), "0002-03-02 04:05:01.000123") - self.assertEqual(t.isoformat('\x00'), "0002-03-02\x0004:05:01.000123") + t = self.theclass(1, 2, 3, 4, 5, 1, 123) + self.assertEqual(t.isoformat(), "0001-02-03T04:05:01.000123") + self.assertEqual(t.isoformat('T'), "0001-02-03T04:05:01.000123") + self.assertEqual(t.isoformat(' '), "0001-02-03 04:05:01.000123") + self.assertEqual(t.isoformat('\x00'), "0001-02-03\x0004:05:01.000123") + self.assertEqual(t.isoformat(timespec='hours'), "0001-02-03T04") + self.assertEqual(t.isoformat(timespec='minutes'), "0001-02-03T04:05") + self.assertEqual(t.isoformat(timespec='seconds'), "0001-02-03T04:05:01") + self.assertEqual(t.isoformat(timespec='milliseconds'), "0001-02-03T04:05:01.000") + self.assertEqual(t.isoformat(timespec='microseconds'), "0001-02-03T04:05:01.000123") + self.assertEqual(t.isoformat(timespec='nanoseconds'), "0001-02-03T04:05:01.000123000") + self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01.000123") + self.assertEqual(t.isoformat(sep=' ', timespec='minutes'), "0001-02-03 04:05") + self.assertRaises(ValueError, t.isoformat, timespec='monkey') # str is ISO format with the separator forced to a blank. - self.assertEqual(str(t), "0002-03-02 04:05:01.000123") + self.assertEqual(str(t), "0001-02-03 04:05:01.000123") + + t = self.theclass(1, 2, 3, 4, 5, 1) + self.assertEqual(t.isoformat(timespec='auto'), "0001-02-03T04:05:01") t = self.theclass(2, 3, 2) self.assertEqual(t.isoformat(), "0002-03-02T00:00:00") diff -r e7ed7db521e9 Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Fri Dec 18 19:37:02 2015 +0200 +++ b/Modules/_datetimemodule.c Sun Dec 20 13:18:57 2015 +0100 @@ -4488,25 +4488,80 @@ datetime_isoformat(PyDateTime_DateTime *self, PyObject *args, PyObject *kw) { int sep = 'T'; - static char *keywords[] = {"sep", NULL}; + char *timespec = "auto"; + static char *keywords[] = {"sep", "timespec", NULL}; char buffer[100]; - PyObject *result; + PyObject *result = NULL; int us = DATE_GET_MICROSECOND(self); - if (!PyArg_ParseTupleAndKeywords(args, kw, "|C:isoformat", keywords, &sep)) + if (!PyArg_ParseTupleAndKeywords(args, kw, "|Cs:isoformat", keywords, &sep, ×pec)) return NULL; - if (us) - result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", + if (strcmp(timespec, "hours") == 0) + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d", GET_YEAR(self), GET_MONTH(self), GET_DAY(self), (int)sep, - DATE_GET_HOUR(self), DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), us); - else + DATE_GET_HOUR(self)); + else if (strcmp(timespec, "minutes") == 0) + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self)); + else if (strcmp(timespec, "seconds") == 0) result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", GET_YEAR(self), GET_MONTH(self), GET_DAY(self), (int)sep, DATE_GET_HOUR(self), DATE_GET_MINUTE(self), DATE_GET_SECOND(self)); + else if (strcmp(timespec, "milliseconds") == 0) { + if (us) { + int milliseconds = us / 1000; + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%03d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), milliseconds); + } + else + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else if (strcmp(timespec, "nanoseconds") == 0) { + if (us) { + int nanoseconds = us * 1000; + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%09d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), nanoseconds); + } + else + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else if (strcmp(timespec, "microseconds") == 0 || (strcmp(timespec, "auto") == 0)) { + if (us) + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d.%06d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), us); + else + result = PyUnicode_FromFormat("%04d-%02d-%02d%c%02d:%02d:%02d", + GET_YEAR(self), GET_MONTH(self), + GET_DAY(self), (int)sep, + DATE_GET_HOUR(self), DATE_GET_MINUTE(self), + DATE_GET_SECOND(self)); + } + else { + PyErr_Format(PyExc_ValueError, "Unknown timespec value"); + return NULL; + } if (!result || !HASTZINFO(self)) return result; @@ -5040,9 +5095,21 @@ {"isoformat", (PyCFunction)datetime_isoformat, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("[sep] -> string in ISO 8601 format, " - "YYYY-MM-DDTHH:MM:SS[.mmmmmm][+HH:MM].\n\n" + "YYYY-MM-DDTHH:MM:SS[.mmm[mmm[000]]][+HH:MM].\n\n" "sep is used to separate the year from the time, and " - "defaults to 'T'.")}, + "defaults to 'T'.\n\n" + "The optional argument timespec specifies the number of " + "additional terms of the time to include. It can be one " + "of the following:\n" + "auto: Append microseconds except if microsecond is 0.\n" + "hours: Append the hour of the day to the date.\n" + "minutes: Append the hours and minutes.\n" + "seconds: Append the hours, minutes and seconds.\n" + "milliseconds: Append the hours, minutes, seconds and milliseconds.\n" + "microseconds: Append the hours, minutes, seconds and microseconds.\n" + "nanoseconds: Append the hours, minutes, seconds and nanoseconds.\n\n" + "If microsecond is 0, milliseconds, microseconds and nanoseconds will " + "display the time value up to seconds precision.\n\n")}, {"utcoffset", (PyCFunction)datetime_utcoffset, METH_NOARGS, PyDoc_STR("Return self.tzinfo.utcoffset(self).")},