summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/datetime.rst7
-rw-r--r--Lib/_strptime.py51
-rw-r--r--Lib/test/test_datetime.py36
-rw-r--r--Misc/NEWS8
-rw-r--r--Modules/datetimemodule.c73
5 files changed, 101 insertions, 74 deletions
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index cb55afb..ae73e16 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -1760,3 +1760,10 @@ Notes:
(5)
For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
``%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. \ No newline at end of file
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index ee30b42..728a9dc 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -16,7 +16,10 @@ import calendar
from re import compile as re_compile
from re import IGNORECASE, ASCII
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:
from _thread import allocate_lock as _thread_allocate_lock
except:
@@ -204,6 +207,7 @@ class TimeRE(dict):
#XXX: Does 'Y' need to worry about having less or more than
# 4 digits?
'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.a_weekday, 'a'),
'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"):
- """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]):
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():
raise ValueError("unconverted data remains: %s" %
data_string[found.end():])
+
year = 1900
month = day = 1
hour = minute = second = fraction = 0
tz = -1
+ tzoffset = None
# Default to -1 to signify that values not known; not critical to have,
# though
week_of_year = -1
@@ -417,6 +425,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
else:
# W starts week on Monday.
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':
# Since -1 is default value only need to worry about setting tz if
# 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
if weekday == -1:
weekday = datetime_date(year, month, day).weekday()
- return (time.struct_time((year, month, day,
- hour, minute, second,
- weekday, julian, tz)), fraction)
+ # 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,
+ weekday, julian, tz, gmtoff, tzname), fraction
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)
diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py
index de7cf0f..b320e1f 100644
--- a/Lib/test/test_datetime.py
+++ b/Lib/test/test_datetime.py
@@ -17,6 +17,7 @@ from datetime import tzinfo
from datetime import time
from datetime import timezone
from datetime import date, datetime
+import time as _time
pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
assert len(pickle_choices) == 3
@@ -1731,11 +1732,41 @@ class TestDateTime(TestDate):
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,)))
+ expected = _strptime._strptime_datetime(string, format)
got = self.theclass.strptime(string, format)
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):
# This tests fields beyond those tested by the TestDate.test_timetuple.
t = self.theclass(2004, 12, 31, 6, 22, 33)
@@ -3196,6 +3227,7 @@ def first_sunday_on_or_after(dt):
return dt
ZERO = timedelta(0)
+MINUTE = timedelta(minutes=1)
HOUR = timedelta(hours=1)
DAY = timedelta(days=1)
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
diff --git a/Misc/NEWS b/Misc/NEWS
index 9c5ea77..41ac42d 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -1322,6 +1322,14 @@ Library
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
implementing ``datetime.tzinfo`` interface. Instances of the new
class, ``datetime.timezone``, return fixed name and UTC offset from
diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c
index 71c5cf5..ed04520 100644
--- a/Modules/datetimemodule.c
+++ b/Modules/datetimemodule.c
@@ -4362,82 +4362,23 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
return result;
}
-/* Return new datetime from time.strptime(). */
+/* Return new datetime from _strptime.strptime_datetime(). */
static PyObject *
datetime_strptime(PyObject *cls, PyObject *args)
{
static PyObject *module = NULL;
- PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
const Py_UNICODE *string, *format;
if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format))
return NULL;
- if (module == NULL &&
- (module = PyImport_ImportModuleNoBlock("_strptime")) == 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");
+ if (module == NULL) {
+ module = PyImport_ImportModuleNoBlock("_strptime");
+ if(module == NULL)
+ return NULL;
}
- Py_XDECREF(obj);
- Py_XDECREF(st);
- Py_XDECREF(frac);
- return result;
+ return PyObject_CallMethod(module, "_strptime_datetime", "uu",
+ string, format);
}
/* Return new datetime from date/datetime and time arguments. */