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
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.
For :class:`date` objects, the format codes for hours, minutes, and seconds
should not be used, as :class:`date` objects have no such values. If they're
used anyway, ``0`` is substituted for them.
For :class:`date` objects, the format codes for hours, minutes, seconds, and
microseconds should not be used, as :class:`date` objects have no such
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
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.
| ``%d`` | Day of the month as a decimal | |
| | number [01,31]. | |
+-----------+--------------------------------+-------+
| ``%f`` | Microsecond as a decimal | \(1) |
| | number [0,999999], zero-padded | |
| | on the left | |
+-----------+--------------------------------+-------+
| ``%H`` | Hour (24-hour clock) as a | |
| | decimal number [00,23]. | |
+-----------+--------------------------------+-------+
......@@ -1539,13 +1565,13 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%M`` | Minute as a decimal number | |
| | [00,59]. | |
+-----------+--------------------------------+-------+
| ``%p`` | Locale's equivalent of either | \(1) |
| ``%p`` | Locale's equivalent of either | \(2) |
| | AM or PM. | |
+-----------+--------------------------------+-------+
| ``%S`` | Second as a decimal number | \(2) |
| ``%S`` | Second as a decimal number | \(3) |
| | [00,61]. | |
+-----------+--------------------------------+-------+
| ``%U`` | Week number of the year | \(3) |
| ``%U`` | Week number of the year | \(4) |
| | (Sunday as the first day of | |
| | the week) as a decimal number | |
| | [00,53]. All days in a new | |
......@@ -1556,7 +1582,7 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%w`` | Weekday as a decimal number | |
| | [0(Sunday),6]. | |
+-----------+--------------------------------+-------+
| ``%W`` | Week number of the year | \(3) |
| ``%W`` | Week number of the year | \(4) |
| | (Monday as the first day of | |
| | the week) as a decimal number | |
| | [00,53]. All days in a new | |
......@@ -1576,7 +1602,7 @@ platforms. Regardless of platform, years before 1900 cannot be used.
| ``%Y`` | Year with century as a decimal | |
| | number. | |
+-----------+--------------------------------+-------+
| ``%z`` | UTC offset in the form +HHMM | \(4) |
| ``%z`` | UTC offset in the form +HHMM | \(5) |
| | or -HHMM (empty string if the | |
| | the object is naive). | |
+-----------+--------------------------------+-------+
......@@ -1589,17 +1615,22 @@ platforms. Regardless of platform, years before 1900 cannot be used.
Notes:
(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
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
(very rare) double leap seconds.
(3)
(4)
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.
(4)
(5)
For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
``%z`` is replaced with the string ``'-0330'``.
......@@ -22,7 +22,7 @@ try:
except:
from dummy_thread import allocate_lock as _thread_allocate_lock
__all__ = ['strptime']
__all__ = []
def _getlang():
# Figure out what the current language is set to.
......@@ -190,6 +190,7 @@ class TimeRE(dict):
base.__init__({
# 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])",
'f': r"(?P<f>[0-9]{1,6})",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'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])",
......@@ -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
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."""
global _TimeRE_cache, _regex_cache
with _cache_lock:
......@@ -327,7 +328,7 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
data_string[found.end():])
year = 1900
month = day = 1
hour = minute = second = 0
hour = minute = second = fraction = 0
tz = -1
# Default to -1 to signify that values not known; not critical to have,
# though
......@@ -384,6 +385,11 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
minute = int(found_dict['M'])
elif group_key == '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':
weekday = locale_time.f_weekday.index(found_dict['A'].lower())
elif group_key == 'a':
......@@ -440,6 +446,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
day = datetime_result.day
if weekday == -1:
weekday = datetime_date(year, month, day).weekday()
return time.struct_time((year, month, day,
hour, minute, second,
weekday, julian, tz))
return (time.struct_time((year, month, day,
hour, minute, second,
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):
self.failUnless(abs(from_timestamp - from_now) <= tolerance)
def test_strptime(self):
import time
import _strptime
string = '2004-12-01 13:02:47'
format = '%Y-%m-%d %H:%M:%S'
expected = self.theclass(*(time.strptime(string, format)[0:6]))
string = '2004-12-01 13:02:47.197'
format = '%Y-%m-%d %H:%M:%S.%f'
result, frac = _strptime._strptime(string, format)
expected = self.theclass(*(result[0:6]+(frac,)))
got = self.theclass.strptime(string, format)
self.assertEqual(expected, got)
......@@ -1539,9 +1540,9 @@ class TestDateTime(TestDate):
def test_more_strftime(self):
# This tests fields beyond those tested by the TestDate.test_strftime.
t = self.theclass(2004, 12, 31, 6, 22, 33)
self.assertEqual(t.strftime("%m %d %y %S %M %H %j"),
"12 31 04 33 22 06 366")
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
"12 31 04 000047 33 22 06 366")
def test_extract(self):
dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
......@@ -1814,7 +1815,7 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
def test_strftime(self):
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.
self.assertEqual(t.strftime("'%z' '%Z'"), "'' ''")
......
This diff is collapsed.
......@@ -1130,10 +1130,24 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
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,
* 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
* step on the format string.
* giving special meanings to the %z, %Z and %f format codes via a
* preprocessing step on the format string.
* tzinfoarg is the argument to pass to the object's tzinfo method, if
* needed.
*/
......@@ -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 *freplacement = NULL; /* py string, replacement for %f */
char *pin; /* pointer to next char in input format */
char ch; /* next char in input format */
......@@ -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
* 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);
if (newfmt == NULL) goto Done;
pnew = PyString_AsString(newfmt);
......@@ -1272,6 +1287,18 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
ptoappend = PyString_AS_STRING(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 {
/* percent followed by neither z nor Z */
ptoappend = pin - 2;
......@@ -1313,6 +1340,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
Py_DECREF(time);
}
Done:
Py_XDECREF(freplacement);
Py_XDECREF(zreplacement);
Py_XDECREF(Zreplacement);
Py_XDECREF(newfmt);
......@@ -3853,43 +3881,69 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
static PyObject *
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;
if (!PyArg_ParseTuple(args, "ss:strptime", &string, &format))
return NULL;
if ((module = PyImport_ImportModuleNoBlock("time")) == NULL)
if (module == NULL &&
(module = PyImport_ImportModuleNoBlock("_strptime")) == 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) {
int i, good_timetuple = 1;
long int ia[6];
if (PySequence_Check(obj) && PySequence_Size(obj) >= 6)
for (i=0; i < 6; i++) {
PyObject *p = PySequence_GetItem(obj, i);
if (p == NULL) {
Py_DECREF(obj);
return NULL;
long int ia[7];
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++) {
PyObject *p = PySequence_GetItem(st, i);
if (p == NULL) {
good_timetuple = 0;
break;
}
if (PyInt_Check(p))
ia[i] = PyInt_AsLong(p);
else
good_timetuple = 0;
Py_DECREF(p);
}
if (PyInt_Check(p))
ia[i] = PyInt_AsLong(p);
else
good_timetuple = 0;
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
good_timetuple = 0;
if (good_timetuple)
result = PyObject_CallFunction(cls, "iiiiii",
ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]);
result = PyObject_CallFunction(cls, "iiiiiii",
ia[0], ia[1], ia[2],
ia[3], ia[4], ia[5],
ia[6]);
else
PyErr_SetString(PyExc_ValueError,
"unexpected value from time.strptime");
Py_DECREF(obj);
"unexpected value from _strptime._strptime");
}
Py_XDECREF(obj);
Py_XDECREF(st);
Py_XDECREF(frac);
return result;
}
......
......@@ -520,7 +520,7 @@ time_strptime(PyObject *self, PyObject *args)
if (!strptime_module)
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);
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