Issue #6641: The datetime.strptime method now supports the %z directive.
This commit is contained in:
parent
f4112e2653
commit
ca94f55758
|
@ -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.
|
|
@ -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
|
||||||
hour, minute, second,
|
tzname = found_dict.get("Z")
|
||||||
weekday, julian, tz)), fraction)
|
if tzoffset is not None:
|
||||||
|
gmtoff = tzoffset * 60
|
||||||
|
else:
|
||||||
|
gmtoff = None
|
||||||
|
|
||||||
|
return (year, month, day,
|
||||||
|
hour, minute, second,
|
||||||
|
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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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");
|
||||||
return NULL;
|
if(module == 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))
|
|
||||||
ia[i] = PyLong_AsLong(p);
|
|
||||||
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);
|
return PyObject_CallMethod(module, "_strptime_datetime", "uu",
|
||||||
Py_XDECREF(st);
|
string, format);
|
||||||
Py_XDECREF(frac);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return new datetime from date/datetime and time arguments. */
|
/* Return new datetime from date/datetime and time arguments. */
|
||||||
|
|
Loading…
Reference in New Issue