Kaydet (Commit) fc070d27 authored tarafından Skip Montanaro's avatar Skip Montanaro

add %f format to datetime - issue 1158

üst 75e51680
...@@ -1489,9 +1489,31 @@ For :class:`time` objects, the format codes for year, month, and day should not ...@@ -1489,9 +1489,31 @@ For :class:`time` objects, the format codes for year, month, and day should not
be used, as time objects have no such values. If they're used anyway, ``1900`` be used, as time objects have no such values. If they're used anyway, ``1900``
is substituted for the year, and ``0`` for the month and day. is substituted for the year, and ``0`` for the month and day.
For :class:`date` objects, the format codes for hours, minutes, and seconds For :class:`date` objects, the format codes for hours, minutes, seconds, and
should not be used, as :class:`date` objects have no such values. If they're microseconds should not be used, as :class:`date` objects have no such
used anyway, ``0`` is substituted for them. values. If they're used anyway, ``0`` is substituted for them.
:class:`time` and :class:`datetime` objects support a ``%f`` format code
which expands to the number of microseconds in the object, zero-padded on
the left to six places.
.. versionadded:: 2.6
For a naive object, the ``%z`` and ``%Z`` format codes are replaced by empty
strings.
For an aware object:
``%z``
:meth:`utcoffset` is transformed into a 5-character string of the form +HHMM or
-HHMM, where HH is a 2-digit string giving the number of UTC offset hours, and
MM is a 2-digit string giving the number of UTC offset minutes. For example, if
:meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is
replaced with the string ``'-0330'``.
``%Z``
If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty string.
Otherwise ``%Z`` is replaced by the returned value, which must be a string.
The full set of format codes supported varies across platforms, because Python The full set of format codes supported varies across platforms, because Python
calls the platform C library's :func:`strftime` function, and platform calls the platform C library's :func:`strftime` function, and platform
...@@ -1524,6 +1546,10 @@ platforms. Regardless of platform, years before 1900 cannot be used. ...@@ -1524,6 +1546,10 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%d`` | Day of the month as a decimal | | | ``%d`` | Day of the month as a decimal | |
| | number [01,31]. | | | | number [01,31]. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%f`` | Microsecond as a decimal | \(1) |
| | number [0,999999], zero-padded | |
| | on the left | |
+-----------+--------------------------------+-------+
| ``%H`` | Hour (24-hour clock) as a | | | ``%H`` | Hour (24-hour clock) as a | |
| | decimal number [00,23]. | | | | decimal number [00,23]. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
...@@ -1539,13 +1565,13 @@ platforms. Regardless of platform, years before 1900 cannot be used. ...@@ -1539,13 +1565,13 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%M`` | Minute as a decimal number | | | ``%M`` | Minute as a decimal number | |
| | [00,59]. | | | | [00,59]. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%p`` | Locale's equivalent of either | \(1) | | ``%p`` | Locale's equivalent of either | \(2) |
| | AM or PM. | | | | AM or PM. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%S`` | Second as a decimal number | \(2) | | ``%S`` | Second as a decimal number | \(3) |
| | [00,61]. | | | | [00,61]. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%U`` | Week number of the year | \(3) | | ``%U`` | Week number of the year | \(4) |
| | (Sunday as the first day of | | | | (Sunday as the first day of | |
| | the week) as a decimal number | | | | the week) as a decimal number | |
| | [00,53]. All days in a new | | | | [00,53]. All days in a new | |
...@@ -1556,7 +1582,7 @@ platforms. Regardless of platform, years before 1900 cannot be used. ...@@ -1556,7 +1582,7 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%w`` | Weekday as a decimal number | | | ``%w`` | Weekday as a decimal number | |
| | [0(Sunday),6]. | | | | [0(Sunday),6]. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%W`` | Week number of the year | \(3) | | ``%W`` | Week number of the year | \(4) |
| | (Monday as the first day of | | | | (Monday as the first day of | |
| | the week) as a decimal number | | | | the week) as a decimal number | |
| | [00,53]. All days in a new | | | | [00,53]. All days in a new | |
...@@ -1576,7 +1602,7 @@ platforms. Regardless of platform, years before 1900 cannot be used. ...@@ -1576,7 +1602,7 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%Y`` | Year with century as a decimal | | | ``%Y`` | Year with century as a decimal | |
| | number. | | | | number. | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
| ``%z`` | UTC offset in the form +HHMM | \(4) | | ``%z`` | UTC offset in the form +HHMM | \(5) |
| | or -HHMM (empty string if the | | | | or -HHMM (empty string if the | |
| | the object is naive). | | | | the object is naive). | |
+-----------+--------------------------------+-------+ +-----------+--------------------------------+-------+
...@@ -1589,17 +1615,22 @@ platforms. Regardless of platform, years before 1900 cannot be used. ...@@ -1589,17 +1615,22 @@ platforms. Regardless of platform, years before 1900 cannot be used.
Notes: Notes:
(1) (1)
When used with the :func:`strptime` function, the ``%f`` directive
accepts from one to six digits and zero pads on the right. ``%f`` is
an extension to the set of format characters in the C standard.
(2)
When used with the :func:`strptime` function, the ``%p`` directive only affects When used with the :func:`strptime` function, the ``%p`` directive only affects
the output hour field if the ``%I`` directive is used to parse the hour. the output hour field if the ``%I`` directive is used to parse the hour.
(2) (3)
The range really is ``0`` to ``61``; this accounts for leap seconds and the The range really is ``0`` to ``61``; this accounts for leap seconds and the
(very rare) double leap seconds. (very rare) double leap seconds.
(3) (4)
When used with the :func:`strptime` function, ``%U`` and ``%W`` are only used in When used with the :func:`strptime` function, ``%U`` and ``%W`` are only used in
calculations when the day of the week and the year are specified. calculations when the day of the week and the year are specified.
(4) (5)
For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
``%z`` is replaced with the string ``'-0330'``. ``%z`` is replaced with the string ``'-0330'``.
...@@ -22,7 +22,7 @@ try: ...@@ -22,7 +22,7 @@ try:
except: except:
from dummy_thread import allocate_lock as _thread_allocate_lock from dummy_thread import allocate_lock as _thread_allocate_lock
__all__ = ['strptime'] __all__ = []
def _getlang(): def _getlang():
# Figure out what the current language is set to. # Figure out what the current language is set to.
...@@ -190,6 +190,7 @@ class TimeRE(dict): ...@@ -190,6 +190,7 @@ class TimeRE(dict):
base.__init__({ base.__init__({
# The " \d" part of the regex is to make %c from ANSI C work # The " \d" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])", 'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'f': r"(?P<f>[0-9]{1,6})",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)", 'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])", 'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])", 'j': r"(?P<j>36[0-6]|3[0-5]\d|[1-2]\d\d|0[1-9]\d|00[1-9]|[1-9]\d|0[1-9]|[1-9])",
...@@ -291,7 +292,7 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon): ...@@ -291,7 +292,7 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
return 1 + days_to_week + day_of_week return 1 + days_to_week + day_of_week
def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a time struct based on the input string and the format string.""" """Return a time struct based on the input string and the format string."""
global _TimeRE_cache, _regex_cache global _TimeRE_cache, _regex_cache
with _cache_lock: with _cache_lock:
...@@ -327,7 +328,7 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): ...@@ -327,7 +328,7 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
data_string[found.end():]) data_string[found.end():])
year = 1900 year = 1900
month = day = 1 month = day = 1
hour = minute = second = 0 hour = minute = second = fraction = 0
tz = -1 tz = -1
# Default to -1 to signify that values not known; not critical to have, # Default to -1 to signify that values not known; not critical to have,
# though # though
...@@ -384,6 +385,11 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): ...@@ -384,6 +385,11 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
minute = int(found_dict['M']) minute = int(found_dict['M'])
elif group_key == 'S': elif group_key == 'S':
second = int(found_dict['S']) second = int(found_dict['S'])
elif group_key == 'f':
s = found_dict['f']
# Pad to always return microseconds.
s += "0" * (6 - len(s))
fraction = int(s)
elif group_key == 'A': elif group_key == 'A':
weekday = locale_time.f_weekday.index(found_dict['A'].lower()) weekday = locale_time.f_weekday.index(found_dict['A'].lower())
elif group_key == 'a': elif group_key == 'a':
...@@ -440,6 +446,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): ...@@ -440,6 +446,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
day = datetime_result.day day = datetime_result.day
if weekday == -1: if weekday == -1:
weekday = datetime_date(year, month, day).weekday() weekday = datetime_date(year, month, day).weekday()
return time.struct_time((year, month, day, return (time.struct_time((year, month, day,
hour, minute, second, hour, minute, second,
weekday, julian, tz)) weekday, julian, tz)), fraction)
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
return _strptime(data_string, format)[0]
...@@ -1507,11 +1507,12 @@ class TestDateTime(TestDate): ...@@ -1507,11 +1507,12 @@ class TestDateTime(TestDate):
self.failUnless(abs(from_timestamp - from_now) <= tolerance) self.failUnless(abs(from_timestamp - from_now) <= tolerance)
def test_strptime(self): def test_strptime(self):
import time import _strptime
string = '2004-12-01 13:02:47' string = '2004-12-01 13:02:47.197'
format = '%Y-%m-%d %H:%M:%S' format = '%Y-%m-%d %H:%M:%S.%f'
expected = self.theclass(*(time.strptime(string, format)[0:6])) result, frac = _strptime._strptime(string, format)
expected = self.theclass(*(result[0:6]+(frac,)))
got = self.theclass.strptime(string, format) got = self.theclass.strptime(string, format)
self.assertEqual(expected, got) self.assertEqual(expected, got)
...@@ -1539,9 +1540,9 @@ class TestDateTime(TestDate): ...@@ -1539,9 +1540,9 @@ class TestDateTime(TestDate):
def test_more_strftime(self): def test_more_strftime(self):
# This tests fields beyond those tested by the TestDate.test_strftime. # This tests fields beyond those tested by the TestDate.test_strftime.
t = self.theclass(2004, 12, 31, 6, 22, 33) t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
self.assertEqual(t.strftime("%m %d %y %S %M %H %j"), self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
"12 31 04 33 22 06 366") "12 31 04 000047 33 22 06 366")
def test_extract(self): def test_extract(self):
dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234) dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
...@@ -1814,7 +1815,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase): ...@@ -1814,7 +1815,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
def test_strftime(self): def test_strftime(self):
t = self.theclass(1, 2, 3, 4) t = self.theclass(1, 2, 3, 4)
self.assertEqual(t.strftime('%H %M %S'), "01 02 03") self.assertEqual(t.strftime('%H %M %S %f'), "01 02 03 000004")
# A naive object replaces %z and %Z with empty strings. # A naive object replaces %z and %Z with empty strings.
self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''") self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
......
This diff is collapsed.
...@@ -1130,10 +1130,24 @@ format_utcoffset(char *buf, size_t buflen, const char *sep, ...@@ -1130,10 +1130,24 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
return 0; return 0;
} }
static PyObject *
make_freplacement(PyObject *object)
{
char freplacement[7];
if (PyTime_Check(object))
sprintf(freplacement, "%06d", TIME_GET_MICROSECOND(object));
else if (PyDateTime_Check(object))
sprintf(freplacement, "%06d", DATE_GET_MICROSECOND(object));
else
sprintf(freplacement, "%06d", 0);
return PyString_FromStringAndSize(freplacement, strlen(freplacement));
}
/* I sure don't want to reproduce the strftime code from the time module, /* I sure don't want to reproduce the strftime code from the time module,
* so this imports the module and calls it. All the hair is due to * so this imports the module and calls it. All the hair is due to
* giving special meanings to the %z and %Z format codes via a preprocessing * giving special meanings to the %z, %Z and %f format codes via a
* step on the format string. * preprocessing step on the format string.
* tzinfoarg is the argument to pass to the object's tzinfo method, if * tzinfoarg is the argument to pass to the object's tzinfo method, if
* needed. * needed.
*/ */
...@@ -1145,6 +1159,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ...@@ -1145,6 +1159,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
PyObject *zreplacement = NULL; /* py string, replacement for %z */ PyObject *zreplacement = NULL; /* py string, replacement for %z */
PyObject *Zreplacement = NULL; /* py string, replacement for %Z */ PyObject *Zreplacement = NULL; /* py string, replacement for %Z */
PyObject *freplacement = NULL; /* py string, replacement for %f */
char *pin; /* pointer to next char in input format */ char *pin; /* pointer to next char in input format */
char ch; /* next char in input format */ char ch; /* next char in input format */
...@@ -1186,11 +1201,11 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ...@@ -1186,11 +1201,11 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
} }
} }
/* Scan the input format, looking for %z and %Z escapes, building /* Scan the input format, looking for %z/%Z/%f escapes, building
* a new format. Since computing the replacements for those codes * a new format. Since computing the replacements for those codes
* is expensive, don't unless they're actually used. * is expensive, don't unless they're actually used.
*/ */
totalnew = PyString_Size(format) + 1; /* realistic if no %z/%Z */ totalnew = PyString_Size(format) + 1; /* realistic if no %z/%Z/%f */
newfmt = PyString_FromStringAndSize(NULL, totalnew); newfmt = PyString_FromStringAndSize(NULL, totalnew);
if (newfmt == NULL) goto Done; if (newfmt == NULL) goto Done;
pnew = PyString_AsString(newfmt); pnew = PyString_AsString(newfmt);
...@@ -1272,6 +1287,18 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ...@@ -1272,6 +1287,18 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
ptoappend = PyString_AS_STRING(Zreplacement); ptoappend = PyString_AS_STRING(Zreplacement);
ntoappend = PyString_GET_SIZE(Zreplacement); ntoappend = PyString_GET_SIZE(Zreplacement);
} }
else if (ch == 'f') {
/* format microseconds */
if (freplacement == NULL) {
freplacement = make_freplacement(object);
if (freplacement == NULL)
goto Done;
}
assert(freplacement != NULL);
assert(PyString_Check(freplacement));
ptoappend = PyString_AS_STRING(freplacement);
ntoappend = PyString_GET_SIZE(freplacement);
}
else { else {
/* percent followed by neither z nor Z */ /* percent followed by neither z nor Z */
ptoappend = pin - 2; ptoappend = pin - 2;
...@@ -1313,6 +1340,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, ...@@ -1313,6 +1340,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
Py_DECREF(time); Py_DECREF(time);
} }
Done: Done:
Py_XDECREF(freplacement);
Py_XDECREF(zreplacement); Py_XDECREF(zreplacement);
Py_XDECREF(Zreplacement); Py_XDECREF(Zreplacement);
Py_XDECREF(newfmt); Py_XDECREF(newfmt);
...@@ -3853,26 +3881,39 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args) ...@@ -3853,26 +3881,39 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
static PyObject * static PyObject *
datetime_strptime(PyObject *cls, PyObject *args) datetime_strptime(PyObject *cls, PyObject *args)
{ {
PyObject *result = NULL, *obj, *module; static PyObject *module = NULL;
PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
const char *string, *format; const char *string, *format;
if (!PyArg_ParseTuple(args, "ss:strptime", &string, &format)) if (!PyArg_ParseTuple(args, "ss:strptime", &string, &format))
return NULL; return NULL;
if ((module = PyImport_ImportModuleNoBlock("time")) == NULL) if (module == NULL &&
(module = PyImport_ImportModuleNoBlock("_strptime")) == NULL)
return NULL; return NULL;
obj = PyObject_CallMethod(module, "strptime", "ss", string, format);
Py_DECREF(module);
/* _strptime._strptime returns a two-element tuple. The first
element is a time.struct_time object. The second is the
microseconds (which are not defined for time.struct_time). */
obj = PyObject_CallMethod(module, "_strptime", "ss", string, format);
if (obj != NULL) { if (obj != NULL) {
int i, good_timetuple = 1; int i, good_timetuple = 1;
long int ia[6]; long int ia[7];
if (PySequence_Check(obj) && PySequence_Size(obj) >= 6) if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
st = PySequence_GetItem(obj, 0);
frac = PySequence_GetItem(obj, 1);
if (st == NULL || frac == NULL)
good_timetuple = 0;
/* copy y/m/d/h/m/s values out of the
time.struct_time */
if (good_timetuple &&
PySequence_Check(st) &&
PySequence_Size(st) >= 6) {
for (i=0; i < 6; i++) { for (i=0; i < 6; i++) {
PyObject *p = PySequence_GetItem(obj, i); PyObject *p = PySequence_GetItem(st, i);
if (p == NULL) { if (p == NULL) {
Py_DECREF(obj); good_timetuple = 0;
return NULL; break;
} }
if (PyInt_Check(p)) if (PyInt_Check(p))
ia[i] = PyInt_AsLong(p); ia[i] = PyInt_AsLong(p);
...@@ -3880,16 +3921,29 @@ datetime_strptime(PyObject *cls, PyObject *args) ...@@ -3880,16 +3921,29 @@ datetime_strptime(PyObject *cls, PyObject *args)
good_timetuple = 0; good_timetuple = 0;
Py_DECREF(p); Py_DECREF(p);
} }
}
else
good_timetuple = 0;
/* follow that up with a little dose of microseconds */
if (PyInt_Check(frac))
ia[6] = PyInt_AsLong(frac);
else
good_timetuple = 0;
}
else else
good_timetuple = 0; good_timetuple = 0;
if (good_timetuple) if (good_timetuple)
result = PyObject_CallFunction(cls, "iiiiii", result = PyObject_CallFunction(cls, "iiiiiii",
ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]); ia[0], ia[1], ia[2],
ia[3], ia[4], ia[5],
ia[6]);
else else
PyErr_SetString(PyExc_ValueError, PyErr_SetString(PyExc_ValueError,
"unexpected value from time.strptime"); "unexpected value from _strptime._strptime");
Py_DECREF(obj);
} }
Py_XDECREF(obj);
Py_XDECREF(st);
Py_XDECREF(frac);
return result; return result;
} }
......
...@@ -520,7 +520,7 @@ time_strptime(PyObject *self, PyObject *args) ...@@ -520,7 +520,7 @@ time_strptime(PyObject *self, PyObject *args)
if (!strptime_module) if (!strptime_module)
return NULL; return NULL;
strptime_result = PyObject_CallMethod(strptime_module, "strptime", "O", args); strptime_result = PyObject_CallMethod(strptime_module, "_strptime_time", "O", args);
Py_DECREF(strptime_module); Py_DECREF(strptime_module);
return strptime_result; return strptime_result;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment