Issue #6641: The datetime.strptime method now supports the %z directive.

This commit is contained in:
Alexander Belopolsky 2010-06-17 18:30:34 +00:00
parent f4112e2653
commit ca94f55758
5 changed files with 101 additions and 74 deletions

View File

@ -1760,3 +1760,10 @@ Notes:
(5) (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'``.
.. versionadded:: 3.2
When the ``%z`` directive is provided to the :meth:`strptime`
method, an aware :class:`datetime` object will be produced. The
``tzinfo`` of the result will be set to a :class:`timezone`
instance.

View File

@ -16,7 +16,10 @@ import calendar
from re import compile as re_compile from re import compile as re_compile
from re import IGNORECASE, ASCII from re import IGNORECASE, ASCII
from re import escape as re_escape from re import escape as re_escape
from datetime import date as datetime_date from datetime import (date as datetime_date,
datetime as datetime_datetime,
timedelta as datetime_timedelta,
timezone as datetime_timezone)
try: try:
from _thread import allocate_lock as _thread_allocate_lock from _thread import allocate_lock as _thread_allocate_lock
except: except:
@ -204,6 +207,7 @@ class TimeRE(dict):
#XXX: Does 'Y' need to worry about having less or more than #XXX: Does 'Y' need to worry about having less or more than
# 4 digits? # 4 digits?
'Y': r"(?P<Y>\d\d\d\d)", 'Y': r"(?P<Y>\d\d\d\d)",
'z': r"(?P<z>[+-]\d\d[0-5]\d)",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'), 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'), 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'), 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
@ -293,7 +297,9 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
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 2-tuple consisting of a time struct and an int containg
the number of microseconds based on the input string and the
format string."""
for index, arg in enumerate([data_string, format]): for index, arg in enumerate([data_string, format]):
if not isinstance(arg, str): if not isinstance(arg, str):
@ -333,10 +339,12 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
if len(data_string) != found.end(): if len(data_string) != found.end():
raise ValueError("unconverted data remains: %s" % raise ValueError("unconverted data remains: %s" %
data_string[found.end():]) data_string[found.end():])
year = 1900 year = 1900
month = day = 1 month = day = 1
hour = minute = second = fraction = 0 hour = minute = second = fraction = 0
tz = -1 tz = -1
tzoffset = None
# 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
week_of_year = -1 week_of_year = -1
@ -417,6 +425,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
else: else:
# W starts week on Monday. # W starts week on Monday.
week_of_year_start = 0 week_of_year_start = 0
elif group_key == 'z':
z = found_dict['z']
tzoffset = int(z[1:3]) * 60 + int(z[3:5])
if z.startswith("-"):
tzoffset = -tzoffset
elif group_key == 'Z': elif group_key == 'Z':
# Since -1 is default value only need to worry about setting tz if # Since -1 is default value only need to worry about setting tz if
# it can be something other than -1. # it can be something other than -1.
@ -453,9 +466,35 @@ 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, # Add timezone info
tzname = found_dict.get("Z")
if tzoffset is not None:
gmtoff = tzoffset * 60
else:
gmtoff = None
return (year, month, day,
hour, minute, second, hour, minute, second,
weekday, julian, tz)), fraction) weekday, julian, tz, gmtoff, tzname), fraction
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"): def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
return _strptime(data_string, format)[0] """Return a time struct based on the input string and the
format string."""
tt = _strptime(data_string, format)[0]
return time.struct_time(tt[:9])
def _strptime_datetime(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a datetime instace based on the input string and the
format string."""
tt, fraction = _strptime(data_string, format)
gmtoff, tzname = tt[-2:]
args = tt[:6] + (fraction,)
if gmtoff is not None:
tzdelta = datetime_timedelta(seconds=gmtoff)
if tzname:
tz = datetime_timezone(tzdelta, tzname)
else:
tz = datetime_timezone(tzdelta)
args += (tz,)
return datetime_datetime(*args)

View File

@ -17,6 +17,7 @@ from datetime import tzinfo
from datetime import time from datetime import time
from datetime import timezone from datetime import timezone
from datetime import date, datetime from datetime import date, datetime
import time as _time
pickle_choices = [(pickle, pickle, proto) for proto in range(3)] pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
assert len(pickle_choices) == 3 assert len(pickle_choices) == 3
@ -1731,11 +1732,41 @@ class TestDateTime(TestDate):
string = '2004-12-01 13:02:47.197' string = '2004-12-01 13:02:47.197'
format = '%Y-%m-%d %H:%M:%S.%f' format = '%Y-%m-%d %H:%M:%S.%f'
result, frac = _strptime._strptime(string, format) expected = _strptime._strptime_datetime(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)
strptime = self.theclass.strptime
self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
# Only local timezone and UTC are supported
for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
(-_time.timezone, _time.tzname[0])):
if tzseconds < 0:
sign = '-'
seconds = -tzseconds
else:
sign ='+'
seconds = tzseconds
hours, minutes = divmod(seconds//60, 60)
dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
dt = strptime(dtstr, "%z %Z")
self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
self.assertEqual(dt.tzname(), tzname)
# Can produce inconsistent datetime
dtstr, fmt = "+1234 UTC", "%z %Z"
dt = strptime(dtstr, fmt)
self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
self.assertEqual(dt.tzname(), 'UTC')
# yet will roundtrip
self.assertEqual(dt.strftime(fmt), dtstr)
# Produce naive datetime if no %z is provided
self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
with self.assertRaises(ValueError): strptime("-2400", "%z")
with self.assertRaises(ValueError): strptime("-000", "%z")
def test_more_timetuple(self): def test_more_timetuple(self):
# This tests fields beyond those tested by the TestDate.test_timetuple. # This tests fields beyond those tested by the TestDate.test_timetuple.
t = self.theclass(2004, 12, 31, 6, 22, 33) t = self.theclass(2004, 12, 31, 6, 22, 33)
@ -3196,6 +3227,7 @@ def first_sunday_on_or_after(dt):
return dt return dt
ZERO = timedelta(0) ZERO = timedelta(0)
MINUTE = timedelta(minutes=1)
HOUR = timedelta(hours=1) HOUR = timedelta(hours=1)
DAY = timedelta(days=1) DAY = timedelta(days=1)
# In the US, DST starts at 2am (standard time) on the first Sunday in April. # In the US, DST starts at 2am (standard time) on the first Sunday in April.

View File

@ -1322,6 +1322,14 @@ Library
Extension Modules Extension Modules
----------------- -----------------
- Issue #6641: The ``datetime.strptime`` method now supports the
``%z`` directive. When the ``%z`` directive is present in the
format string, an aware ``datetime`` object is returned with
``tzinfo`` bound to a ``datetime.timezone`` instance constructed
from the parsed offset. If both ``%z`` and ``%Z`` are present, the
data in ``%Z`` field is used for timezone name, but ``%Z`` data
without ``%z`` is discarded.
- Issue #5094: The ``datetime`` module now has a simple concrete class - Issue #5094: The ``datetime`` module now has a simple concrete class
implementing ``datetime.tzinfo`` interface. Instances of the new implementing ``datetime.tzinfo`` interface. Instances of the new
class, ``datetime.timezone``, return fixed name and UTC offset from class, ``datetime.timezone``, return fixed name and UTC offset from

View File

@ -4362,82 +4362,23 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
return result; return result;
} }
/* Return new datetime from time.strptime(). */ /* Return new datetime from _strptime.strptime_datetime(). */
static PyObject * static PyObject *
datetime_strptime(PyObject *cls, PyObject *args) datetime_strptime(PyObject *cls, PyObject *args)
{ {
static PyObject *module = NULL; static PyObject *module = NULL;
PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
const Py_UNICODE *string, *format; const Py_UNICODE *string, *format;
if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format)) if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format))
return NULL; return NULL;
if (module == NULL && if (module == NULL) {
(module = PyImport_ImportModuleNoBlock("_strptime")) == NULL) module = PyImport_ImportModuleNoBlock("_strptime");
if(module == NULL)
return NULL; return NULL;
/* _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", "uu", string, format);
if (obj != NULL) {
int i, good_timetuple = 1;
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 (PyLong_Check(p)) return PyObject_CallMethod(module, "_strptime_datetime", "uu",
ia[i] = PyLong_AsLong(p); string, format);
else
good_timetuple = 0;
Py_DECREF(p);
}
/* if (PyLong_CheckExact(p)) {
ia[i] = PyLong_AsLongAndOverflow(p, &overflow);
if (overflow)
good_timetuple = 0;
}
else
good_timetuple = 0;
Py_DECREF(p);
*/ }
else
good_timetuple = 0;
/* follow that up with a little dose of microseconds */
if (PyLong_Check(frac))
ia[6] = PyLong_AsLong(frac);
else
good_timetuple = 0;
}
else
good_timetuple = 0;
if (good_timetuple)
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 _strptime._strptime");
}
Py_XDECREF(obj);
Py_XDECREF(st);
Py_XDECREF(frac);
return result;
} }
/* Return new datetime from date/datetime and time arguments. */ /* Return new datetime from date/datetime and time arguments. */