Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ca94f55

Browse files
committed
Issue #6641: The datetime.strptime method now supports the %z directive.
1 parent f4112e2 commit ca94f55

5 files changed

Lines changed: 101 additions & 74 deletions

File tree

Doc/library/datetime.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,3 +1760,10 @@ Notes:
17601760
(5)
17611761
For example, if :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``,
17621762
``%z`` is replaced with the string ``'-0330'``.
1763+
1764+
.. versionadded:: 3.2
1765+
1766+
When the ``%z`` directive is provided to the :meth:`strptime`
1767+
method, an aware :class:`datetime` object will be produced. The
1768+
``tzinfo`` of the result will be set to a :class:`timezone`
1769+
instance.

Lib/_strptime.py

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
from re import compile as re_compile
1717
from re import IGNORECASE, ASCII
1818
from re import escape as re_escape
19-
from datetime import date as datetime_date
19+
from datetime import (date as datetime_date,
20+
datetime as datetime_datetime,
21+
timedelta as datetime_timedelta,
22+
timezone as datetime_timezone)
2023
try:
2124
from _thread import allocate_lock as _thread_allocate_lock
2225
except:
@@ -204,6 +207,7 @@ def __init__(self, locale_time=None):
204207
#XXX: Does 'Y' need to worry about having less or more than
205208
# 4 digits?
206209
'Y': r"(?P<Y>\d\d\d\d)",
210+
'z': r"(?P<z>[+-]\d\d[0-5]\d)",
207211
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
208212
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
209213
'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):
293297

294298

295299
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
296-
"""Return a time struct based on the input string and the format string."""
300+
"""Return a 2-tuple consisting of a time struct and an int containg
301+
the number of microseconds based on the input string and the
302+
format string."""
297303

298304
for index, arg in enumerate([data_string, format]):
299305
if not isinstance(arg, str):
@@ -333,10 +339,12 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
333339
if len(data_string) != found.end():
334340
raise ValueError("unconverted data remains: %s" %
335341
data_string[found.end():])
342+
336343
year = 1900
337344
month = day = 1
338345
hour = minute = second = fraction = 0
339346
tz = -1
347+
tzoffset = None
340348
# Default to -1 to signify that values not known; not critical to have,
341349
# though
342350
week_of_year = -1
@@ -417,6 +425,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
417425
else:
418426
# W starts week on Monday.
419427
week_of_year_start = 0
428+
elif group_key == 'z':
429+
z = found_dict['z']
430+
tzoffset = int(z[1:3]) * 60 + int(z[3:5])
431+
if z.startswith("-"):
432+
tzoffset = -tzoffset
420433
elif group_key == 'Z':
421434
# Since -1 is default value only need to worry about setting tz if
422435
# it can be something other than -1.
@@ -453,9 +466,35 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
453466
day = datetime_result.day
454467
if weekday == -1:
455468
weekday = datetime_date(year, month, day).weekday()
456-
return (time.struct_time((year, month, day,
457-
hour, minute, second,
458-
weekday, julian, tz)), fraction)
469+
# Add timezone info
470+
tzname = found_dict.get("Z")
471+
if tzoffset is not None:
472+
gmtoff = tzoffset * 60
473+
else:
474+
gmtoff = None
475+
476+
return (year, month, day,
477+
hour, minute, second,
478+
weekday, julian, tz, gmtoff, tzname), fraction
459479

460480
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
461-
return _strptime(data_string, format)[0]
481+
"""Return a time struct based on the input string and the
482+
format string."""
483+
tt = _strptime(data_string, format)[0]
484+
return time.struct_time(tt[:9])
485+
486+
def _strptime_datetime(data_string, format="%a %b %d %H:%M:%S %Y"):
487+
"""Return a datetime instace based on the input string and the
488+
format string."""
489+
tt, fraction = _strptime(data_string, format)
490+
gmtoff, tzname = tt[-2:]
491+
args = tt[:6] + (fraction,)
492+
if gmtoff is not None:
493+
tzdelta = datetime_timedelta(seconds=gmtoff)
494+
if tzname:
495+
tz = datetime_timezone(tzdelta, tzname)
496+
else:
497+
tz = datetime_timezone(tzdelta)
498+
args += (tz,)
499+
500+
return datetime_datetime(*args)

Lib/test/test_datetime.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from datetime import time
1818
from datetime import timezone
1919
from datetime import date, datetime
20+
import time as _time
2021

2122
pickle_choices = [(pickle, pickle, proto) for proto in range(3)]
2223
assert len(pickle_choices) == 3
@@ -1731,11 +1732,41 @@ def test_strptime(self):
17311732

17321733
string = '2004-12-01 13:02:47.197'
17331734
format = '%Y-%m-%d %H:%M:%S.%f'
1734-
result, frac = _strptime._strptime(string, format)
1735-
expected = self.theclass(*(result[0:6]+(frac,)))
1735+
expected = _strptime._strptime_datetime(string, format)
17361736
got = self.theclass.strptime(string, format)
17371737
self.assertEqual(expected, got)
17381738

1739+
strptime = self.theclass.strptime
1740+
self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
1741+
self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
1742+
# Only local timezone and UTC are supported
1743+
for tzseconds, tzname in ((0, 'UTC'), (0, 'GMT'),
1744+
(-_time.timezone, _time.tzname[0])):
1745+
if tzseconds < 0:
1746+
sign = '-'
1747+
seconds = -tzseconds
1748+
else:
1749+
sign ='+'
1750+
seconds = tzseconds
1751+
hours, minutes = divmod(seconds//60, 60)
1752+
dtstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
1753+
dt = strptime(dtstr, "%z %Z")
1754+
self.assertEqual(dt.utcoffset(), timedelta(seconds=tzseconds))
1755+
self.assertEqual(dt.tzname(), tzname)
1756+
# Can produce inconsistent datetime
1757+
dtstr, fmt = "+1234 UTC", "%z %Z"
1758+
dt = strptime(dtstr, fmt)
1759+
self.assertEqual(dt.utcoffset(), 12 * HOUR + 34 * MINUTE)
1760+
self.assertEqual(dt.tzname(), 'UTC')
1761+
# yet will roundtrip
1762+
self.assertEqual(dt.strftime(fmt), dtstr)
1763+
1764+
# Produce naive datetime if no %z is provided
1765+
self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
1766+
1767+
with self.assertRaises(ValueError): strptime("-2400", "%z")
1768+
with self.assertRaises(ValueError): strptime("-000", "%z")
1769+
17391770
def test_more_timetuple(self):
17401771
# This tests fields beyond those tested by the TestDate.test_timetuple.
17411772
t = self.theclass(2004, 12, 31, 6, 22, 33)
@@ -3196,6 +3227,7 @@ def first_sunday_on_or_after(dt):
31963227
return dt
31973228

31983229
ZERO = timedelta(0)
3230+
MINUTE = timedelta(minutes=1)
31993231
HOUR = timedelta(hours=1)
32003232
DAY = timedelta(days=1)
32013233
# In the US, DST starts at 2am (standard time) on the first Sunday in April.

Misc/NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,14 @@ Library
13221322
Extension Modules
13231323
-----------------
13241324

1325+
- Issue #6641: The ``datetime.strptime`` method now supports the
1326+
``%z`` directive. When the ``%z`` directive is present in the
1327+
format string, an aware ``datetime`` object is returned with
1328+
``tzinfo`` bound to a ``datetime.timezone`` instance constructed
1329+
from the parsed offset. If both ``%z`` and ``%Z`` are present, the
1330+
data in ``%Z`` field is used for timezone name, but ``%Z`` data
1331+
without ``%z`` is discarded.
1332+
13251333
- Issue #5094: The ``datetime`` module now has a simple concrete class
13261334
implementing ``datetime.tzinfo`` interface. Instances of the new
13271335
class, ``datetime.timezone``, return fixed name and UTC offset from

Modules/datetimemodule.c

Lines changed: 7 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4362,82 +4362,23 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
43624362
return result;
43634363
}
43644364

4365-
/* Return new datetime from time.strptime(). */
4365+
/* Return new datetime from _strptime.strptime_datetime(). */
43664366
static PyObject *
43674367
datetime_strptime(PyObject *cls, PyObject *args)
43684368
{
43694369
static PyObject *module = NULL;
4370-
PyObject *result = NULL, *obj, *st = NULL, *frac = NULL;
43714370
const Py_UNICODE *string, *format;
43724371

43734372
if (!PyArg_ParseTuple(args, "uu:strptime", &string, &format))
43744373
return NULL;
43754374

4376-
if (module == NULL &&
4377-
(module = PyImport_ImportModuleNoBlock("_strptime")) == NULL)
4378-
return NULL;
4379-
4380-
/* _strptime._strptime returns a two-element tuple. The first
4381-
element is a time.struct_time object. The second is the
4382-
microseconds (which are not defined for time.struct_time). */
4383-
obj = PyObject_CallMethod(module, "_strptime", "uu", string, format);
4384-
if (obj != NULL) {
4385-
int i, good_timetuple = 1;
4386-
long int ia[7];
4387-
if (PySequence_Check(obj) && PySequence_Size(obj) == 2) {
4388-
st = PySequence_GetItem(obj, 0);
4389-
frac = PySequence_GetItem(obj, 1);
4390-
if (st == NULL || frac == NULL)
4391-
good_timetuple = 0;
4392-
/* copy y/m/d/h/m/s values out of the
4393-
time.struct_time */
4394-
if (good_timetuple &&
4395-
PySequence_Check(st) &&
4396-
PySequence_Size(st) >= 6) {
4397-
for (i=0; i < 6; i++) {
4398-
PyObject *p = PySequence_GetItem(st, i);
4399-
if (p == NULL) {
4400-
good_timetuple = 0;
4401-
break;
4402-
}
4403-
if (PyLong_Check(p))
4404-
ia[i] = PyLong_AsLong(p);
4405-
else
4406-
good_timetuple = 0;
4407-
Py_DECREF(p);
4408-
}
4409-
/* if (PyLong_CheckExact(p)) {
4410-
ia[i] = PyLong_AsLongAndOverflow(p, &overflow);
4411-
if (overflow)
4412-
good_timetuple = 0;
4413-
}
4414-
else
4415-
good_timetuple = 0;
4416-
Py_DECREF(p);
4417-
*/ }
4418-
else
4419-
good_timetuple = 0;
4420-
/* follow that up with a little dose of microseconds */
4421-
if (PyLong_Check(frac))
4422-
ia[6] = PyLong_AsLong(frac);
4423-
else
4424-
good_timetuple = 0;
4425-
}
4426-
else
4427-
good_timetuple = 0;
4428-
if (good_timetuple)
4429-
result = PyObject_CallFunction(cls, "iiiiiii",
4430-
ia[0], ia[1], ia[2],
4431-
ia[3], ia[4], ia[5],
4432-
ia[6]);
4433-
else
4434-
PyErr_SetString(PyExc_ValueError,
4435-
"unexpected value from _strptime._strptime");
4375+
if (module == NULL) {
4376+
module = PyImport_ImportModuleNoBlock("_strptime");
4377+
if(module == NULL)
4378+
return NULL;
44364379
}
4437-
Py_XDECREF(obj);
4438-
Py_XDECREF(st);
4439-
Py_XDECREF(frac);
4440-
return result;
4380+
return PyObject_CallMethod(module, "_strptime_datetime", "uu",
4381+
string, format);
44414382
}
44424383

44434384
/* Return new datetime from date/datetime and time arguments. */

0 commit comments

Comments
 (0)