summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNice Zombies <nineteendo19d0@gmail.com>2024-09-25 21:43:58 (GMT)
committerGitHub <noreply@github.com>2024-09-25 21:43:58 (GMT)
commit9968caa0cc8a4d07595f0e0abe5f28ba9b6f7a96 (patch)
tree34514828a85ef128027d75c737858a5eebc6fbbf
parentb0c6cf5f17f0be13aa927cf141a289f7b76ae6b1 (diff)
downloadcpython-9968caa0cc8a4d07595f0e0abe5f28ba9b6f7a96.zip
cpython-9968caa0cc8a4d07595f0e0abe5f28ba9b6f7a96.tar.gz
cpython-9968caa0cc8a4d07595f0e0abe5f28ba9b6f7a96.tar.bz2
gh-41431: Add `datetime.time.strptime()` and `datetime.date.strptime()` (#120752)
* Python implementation * C implementation * Test `date.strptime` * Test `time.strptime` * 📜🤖 Added by blurb_it. * Update whatsnew * Update documentation * Add leap year note * Update 2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst * Apply suggestions from code review Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> * Remove parentheses * Use helper function * Remove bad return * Link to github issue * Fix directive * Apply suggestions from code review Co-authored-by: Paul Ganssle <1377457+pganssle@users.noreply.github.com> * Fix test cases --------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Paul Ganssle <1377457+pganssle@users.noreply.github.com>
-rw-r--r--Doc/library/datetime.rst77
-rw-r--r--Doc/whatsnew/3.14.rst6
-rw-r--r--Include/internal/pycore_global_objects_fini_generated.h4
-rw-r--r--Include/internal/pycore_global_strings.h4
-rw-r--r--Include/internal/pycore_runtime_init_generated.h4
-rw-r--r--Include/internal/pycore_unicodeobject_generated.h10
-rw-r--r--Lib/_pydatetime.py16
-rw-r--r--Lib/_strptime.py44
-rw-r--r--Lib/test/datetimetester.py158
-rw-r--r--Misc/NEWS.d/next/Library/2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst2
-rw-r--r--Modules/_datetimemodule.c61
11 files changed, 350 insertions, 36 deletions
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index 0e7dc4f..59e2dbd 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -548,6 +548,39 @@ Other constructors, all class methods:
.. versionadded:: 3.8
+.. classmethod:: date.strptime(date_string, format)
+
+ Return a :class:`.date` corresponding to *date_string*, parsed according to
+ *format*. This is equivalent to::
+
+ date(*(time.strptime(date_string, format)[0:3]))
+
+ :exc:`ValueError` is raised if the date_string and format
+ can't be parsed by :func:`time.strptime` or if it returns a value which isn't a
+ time tuple. See also :ref:`strftime-strptime-behavior` and
+ :meth:`date.fromisoformat`.
+
+ .. note::
+
+ If *format* specifies a day of month without a year a
+ :exc:`DeprecationWarning` is emitted. This is to avoid a quadrennial
+ leap year bug in code seeking to parse only a month and day as the
+ default year used in absence of one in the format is not a leap year.
+ Such *format* values may raise an error as of Python 3.15. The
+ workaround is to always include a year in your *format*. If parsing
+ *date_string* values that do not have a year, explicitly add a year that
+ is a leap year before parsing:
+
+ .. doctest::
+
+ >>> from datetime import date
+ >>> date_string = "02/29"
+ >>> when = date.strptime(f"{date_string};1984", "%m/%d;%Y") # Avoids leap year bug.
+ >>> when.strftime("%B %d") # doctest: +SKIP
+ 'February 29'
+
+ .. versionadded:: 3.14
+
Class attributes:
@@ -1827,7 +1860,7 @@ In Boolean contexts, a :class:`.time` object is always considered to be true.
details.
-Other constructor:
+Other constructors:
.. classmethod:: time.fromisoformat(time_string)
@@ -1869,6 +1902,22 @@ Other constructor:
Previously, this method only supported formats that could be emitted by
:meth:`time.isoformat`.
+.. classmethod:: time.strptime(date_string, format)
+
+ Return a :class:`.time` corresponding to *date_string*, parsed according to
+ *format*.
+
+ If *format* does not contain microseconds or timezone information, this is equivalent to::
+
+ time(*(time.strptime(date_string, format)[3:6]))
+
+ :exc:`ValueError` is raised if the *date_string* and *format*
+ cannot be parsed by :func:`time.strptime` or if it returns a value which is not a
+ time tuple. See also :ref:`strftime-strptime-behavior` and
+ :meth:`time.fromisoformat`.
+
+ .. versionadded:: 3.14
+
Instance methods:
@@ -2367,24 +2416,22 @@ Class attributes:
``strftime(format)`` method, to create a string representing the time under the
control of an explicit format string.
-Conversely, the :meth:`datetime.strptime` class method creates a
-:class:`.datetime` object from a string representing a date and time and a
-corresponding format string.
+Conversely, the :meth:`date.strptime`, :meth:`datetime.strptime` and
+:meth:`time.strptime` class methods create an object from a string
+representing the time and a corresponding format string.
The table below provides a high-level comparison of :meth:`~.datetime.strftime`
versus :meth:`~.datetime.strptime`:
-+----------------+--------------------------------------------------------+------------------------------------------------------------------------------+
-| | ``strftime`` | ``strptime`` |
-+================+========================================================+==============================================================================+
-| Usage | Convert object to a string according to a given format | Parse a string into a :class:`.datetime` object given a corresponding format |
-+----------------+--------------------------------------------------------+------------------------------------------------------------------------------+
-| Type of method | Instance method | Class method |
-+----------------+--------------------------------------------------------+------------------------------------------------------------------------------+
-| Method of | :class:`date`; :class:`.datetime`; :class:`.time` | :class:`.datetime` |
-+----------------+--------------------------------------------------------+------------------------------------------------------------------------------+
-| Signature | ``strftime(format)`` | ``strptime(date_string, format)`` |
-+----------------+--------------------------------------------------------+------------------------------------------------------------------------------+
++----------------+--------------------------------------------------------+------------------------------------------------------------+
+| | ``strftime`` | ``strptime`` |
++================+========================================================+============================================================+
+| Usage | Convert object to a string according to a given format | Parse a string into an object given a corresponding format |
++----------------+--------------------------------------------------------+------------------------------------------------------------+
+| Type of method | Instance method | Class method |
++----------------+--------------------------------------------------------+------------------------------------------------------------+
+| Signature | ``strftime(format)`` | ``strptime(date_string, format)`` |
++----------------+--------------------------------------------------------+------------------------------------------------------------+
.. _format-codes:
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 4d22f7a..09d096c 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -285,6 +285,12 @@ operator
(Contributed by Raymond Hettinger and Nico Mexis in :gh:`115808`.)
+datetime
+--------
+
+Add :meth:`datetime.time.strptime` and :meth:`datetime.date.strptime`.
+(Contributed by Wannes Boeykens in :gh:`41431`.)
+
os
--
diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h
index a5f1369..28a76c3 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -768,7 +768,9 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_shutdown));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_slotnames));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime));
- _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime_datetime));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime_datetime_date));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime_datetime_datetime));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_strptime_datetime_time));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_type_));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_uninitialized_submodules));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(_warn_unawaited_coroutine));
diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h
index dd958dc..ac789b0 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -257,7 +257,9 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_shutdown)
STRUCT_FOR_ID(_slotnames)
STRUCT_FOR_ID(_strptime)
- STRUCT_FOR_ID(_strptime_datetime)
+ STRUCT_FOR_ID(_strptime_datetime_date)
+ STRUCT_FOR_ID(_strptime_datetime_datetime)
+ STRUCT_FOR_ID(_strptime_datetime_time)
STRUCT_FOR_ID(_type_)
STRUCT_FOR_ID(_uninitialized_submodules)
STRUCT_FOR_ID(_warn_unawaited_coroutine)
diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h
index 8d7da8b..7847a5c 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -766,7 +766,9 @@ extern "C" {
INIT_ID(_shutdown), \
INIT_ID(_slotnames), \
INIT_ID(_strptime), \
- INIT_ID(_strptime_datetime), \
+ INIT_ID(_strptime_datetime_date), \
+ INIT_ID(_strptime_datetime_datetime), \
+ INIT_ID(_strptime_datetime_time), \
INIT_ID(_type_), \
INIT_ID(_uninitialized_submodules), \
INIT_ID(_warn_unawaited_coroutine), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h
index d5ad61d..a688f70 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -828,7 +828,15 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
- string = &_Py_ID(_strptime_datetime);
+ string = &_Py_ID(_strptime_datetime_date);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(_strptime_datetime_datetime);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(_strptime_datetime_time);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py
index 154e6eb..78e03e3 100644
--- a/Lib/_pydatetime.py
+++ b/Lib/_pydatetime.py
@@ -951,6 +951,7 @@ class date:
fromtimestamp()
today()
fromordinal()
+ strptime()
Operators:
@@ -1051,6 +1052,12 @@ class date:
This is the inverse of the date.isocalendar() function"""
return cls(*_isoweek_to_gregorian(year, week, day))
+ @classmethod
+ def strptime(cls, date_string, format):
+ """Parse a date string according to the given format (like time.strptime())."""
+ import _strptime
+ return _strptime._strptime_datetime_date(cls, date_string, format)
+
# Conversions to string
def __repr__(self):
@@ -1371,6 +1378,7 @@ class time:
Constructors:
__new__()
+ strptime()
Operators:
@@ -1429,6 +1437,12 @@ class time:
self._fold = fold
return self
+ @classmethod
+ def strptime(cls, date_string, format):
+ """string, format -> new time parsed from a string (like time.strptime())."""
+ import _strptime
+ return _strptime._strptime_datetime_time(cls, date_string, format)
+
# Read-only field accessors
@property
def hour(self):
@@ -2152,7 +2166,7 @@ class datetime(date):
def strptime(cls, date_string, format):
'string, format -> new datetime parsed from a string (like time.strptime()).'
import _strptime
- return _strptime._strptime_datetime(cls, date_string, format)
+ return _strptime._strptime_datetime_datetime(cls, date_string, format)
def utcoffset(self):
"""Return the timezone offset as timedelta positive east of UTC (negative west of
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index 3f868bc..a3f8bb5 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -567,18 +567,40 @@ def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
tt = _strptime(data_string, format)[0]
return time.struct_time(tt[:time._STRUCT_TM_ITEMS])
-def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
- """Return a class cls instance based on the input string and the
+def _strptime_datetime_date(cls, data_string, format="%a %b %d %Y"):
+ """Return a date instance based on the input string and the
+ format string."""
+ tt, _, _ = _strptime(data_string, format)
+ args = tt[:3]
+ return cls(*args)
+
+def _parse_tz(tzname, gmtoff, gmtoff_fraction):
+ tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
+ if tzname:
+ return datetime_timezone(tzdelta, tzname)
+ else:
+ return datetime_timezone(tzdelta)
+
+def _strptime_datetime_time(cls, data_string, format="%H:%M:%S"):
+ """Return a time instance based on the input string and the
format string."""
tt, fraction, gmtoff_fraction = _strptime(data_string, format)
tzname, gmtoff = tt[-2:]
- args = tt[:6] + (fraction,)
- if gmtoff is not None:
- tzdelta = datetime_timedelta(seconds=gmtoff, microseconds=gmtoff_fraction)
- if tzname:
- tz = datetime_timezone(tzdelta, tzname)
- else:
- tz = datetime_timezone(tzdelta)
- args += (tz,)
+ args = tt[3:6] + (fraction,)
+ if gmtoff is None:
+ return cls(*args)
+ else:
+ tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
+ return cls(*args, tz)
- return cls(*args)
+def _strptime_datetime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
+ """Return a datetime instance based on the input string and the
+ format string."""
+ tt, fraction, gmtoff_fraction = _strptime(data_string, format)
+ tzname, gmtoff = tt[-2:]
+ args = tt[:6] + (fraction,)
+ if gmtoff is None:
+ return cls(*args)
+ else:
+ tz = _parse_tz(tzname, gmtoff, gmtoff_fraction)
+ return cls(*args, tz)
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 16aff18..c81408b 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -1106,6 +1106,85 @@ class TestDateOnly(unittest.TestCase):
dt2 = dt - delta
self.assertEqual(dt2, dt - days)
+ def test_strptime(self):
+ inputs = [
+ # Basic valid cases
+ (date(1998, 2, 3), '1998-02-03', '%Y-%m-%d'),
+ (date(2004, 12, 2), '2004-12-02', '%Y-%m-%d'),
+
+ # Edge cases: Leap year
+ (date(2020, 2, 29), '2020-02-29', '%Y-%m-%d'), # Valid leap year date
+
+ # bpo-34482: Handle surrogate pairs
+ (date(2004, 12, 2), '2004-12\ud80002', '%Y-%m\ud800%d'),
+ (date(2004, 12, 2), '2004\ud80012-02', '%Y\ud800%m-%d'),
+
+ # Month/day variations
+ (date(2004, 2, 1), '2004-02', '%Y-%m'), # No day provided
+ (date(2004, 2, 1), '02-2004', '%m-%Y'), # Month and year swapped
+
+ # Different day-month-year formats
+ (date(2004, 12, 2), '02/12/2004', '%d/%m/%Y'), # Day/Month/Year
+ (date(2004, 12, 2), '12/02/2004', '%m/%d/%Y'), # Month/Day/Year
+
+ # Different separators
+ (date(2023, 9, 24), '24.09.2023', '%d.%m.%Y'), # Dots as separators
+ (date(2023, 9, 24), '24-09-2023', '%d-%m-%Y'), # Dashes
+ (date(2023, 9, 24), '2023/09/24', '%Y/%m/%d'), # Slashes
+
+ # Handling years with fewer digits
+ (date(127, 2, 3), '0127-02-03', '%Y-%m-%d'),
+ (date(99, 2, 3), '0099-02-03', '%Y-%m-%d'),
+ (date(5, 2, 3), '0005-02-03', '%Y-%m-%d'),
+
+ # Variations on ISO 8601 format
+ (date(2023, 9, 25), '2023-W39-1', '%G-W%V-%u'), # ISO week date (Week 39, Monday)
+ (date(2023, 9, 25), '2023-268', '%Y-%j'), # Year and day of the year (Julian)
+ ]
+ for expected, string, format in inputs:
+ with self.subTest(string=string, format=format):
+ got = date.strptime(string, format)
+ self.assertEqual(expected, got)
+ self.assertIs(type(got), date)
+
+ def test_strptime_single_digit(self):
+ # bpo-34903: Check that single digit dates are allowed.
+ strptime = date.strptime
+ with self.assertRaises(ValueError):
+ # %y does require two digits.
+ newdate = strptime('01/02/3', '%d/%m/%y')
+
+ d1 = date(2003, 2, 1)
+ d2 = date(2003, 1, 2)
+ d3 = date(2003, 1, 25)
+ inputs = [
+ ('%d', '1/02/03', '%d/%m/%y', d1),
+ ('%m', '01/2/03', '%d/%m/%y', d1),
+ ('%j', '2/03', '%j/%y', d2),
+ ('%w', '6/04/03', '%w/%U/%y', d1),
+ # %u requires a single digit.
+ ('%W', '6/4/2003', '%u/%W/%Y', d1),
+ ('%V', '6/4/2003', '%u/%V/%G', d3),
+ ]
+ for reason, string, format, target in inputs:
+ reason = 'test single digit ' + reason
+ with self.subTest(reason=reason,
+ string=string,
+ format=format,
+ target=target):
+ newdate = strptime(string, format)
+ self.assertEqual(newdate, target, msg=reason)
+
+ @warnings_helper.ignore_warnings(category=DeprecationWarning)
+ def test_strptime_leap_year(self):
+ # GH-70647: warns if parsing a format with a day and no year.
+ with self.assertRaises(ValueError):
+ # The existing behavior that GH-70647 seeks to change.
+ date.strptime('02-29', '%m-%d')
+ with self._assertNotWarns(DeprecationWarning):
+ date.strptime('20-03-14', '%y-%m-%d')
+ date.strptime('02-29,2024', '%m-%d,%Y')
+
class SubclassDate(date):
sub_var = 1
@@ -2732,7 +2811,8 @@ class TestDateTime(TestDate):
def test_strptime(self):
string = '2004-12-01 13:02:47.197'
format = '%Y-%m-%d %H:%M:%S.%f'
- expected = _strptime._strptime_datetime(self.theclass, string, format)
+ expected = _strptime._strptime_datetime_datetime(self.theclass, string,
+ format)
got = self.theclass.strptime(string, format)
self.assertEqual(expected, got)
self.assertIs(type(expected), self.theclass)
@@ -2746,8 +2826,8 @@ class TestDateTime(TestDate):
]
for string, format in inputs:
with self.subTest(string=string, format=format):
- expected = _strptime._strptime_datetime(self.theclass, string,
- format)
+ expected = _strptime._strptime_datetime_datetime(self.theclass,
+ string, format)
got = self.theclass.strptime(string, format)
self.assertEqual(expected, got)
@@ -3749,6 +3829,78 @@ class TestTime(HarmlessMixedComparison, unittest.TestCase):
derived = loads(data, encoding='latin1')
self.assertEqual(derived, expected)
+ def test_strptime(self):
+ # bpo-34482: Check that surrogates are handled properly.
+ inputs = [
+ (self.theclass(13, 2, 47, 197000), '13:02:47.197', '%H:%M:%S.%f'),
+ (self.theclass(13, 2, 47, 197000), '13:02\ud80047.197', '%H:%M\ud800%S.%f'),
+ (self.theclass(13, 2, 47, 197000), '13\ud80002:47.197', '%H\ud800%M:%S.%f'),
+ ]
+ for expected, string, format in inputs:
+ with self.subTest(string=string, format=format):
+ got = self.theclass.strptime(string, format)
+ self.assertEqual(expected, got)
+ self.assertIs(type(got), self.theclass)
+
+ def test_strptime_tz(self):
+ strptime = self.theclass.strptime
+ self.assertEqual(strptime("+0002", "%z").utcoffset(), 2 * MINUTE)
+ self.assertEqual(strptime("-0002", "%z").utcoffset(), -2 * MINUTE)
+ self.assertEqual(
+ strptime("-00:02:01.000003", "%z").utcoffset(),
+ -timedelta(minutes=2, seconds=1, microseconds=3)
+ )
+ # 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)
+ tstr = "{}{:02d}{:02d} {}".format(sign, hours, minutes, tzname)
+ with self.subTest(tstr=tstr):
+ t = strptime(tstr, "%z %Z")
+ self.assertEqual(t.utcoffset(), timedelta(seconds=tzseconds))
+ self.assertEqual(t.tzname(), tzname)
+ self.assertIs(type(t), self.theclass)
+
+ # Can produce inconsistent time
+ tstr, fmt = "+1234 UTC", "%z %Z"
+ t = strptime(tstr, fmt)
+ self.assertEqual(t.utcoffset(), 12 * HOUR + 34 * MINUTE)
+ self.assertEqual(t.tzname(), 'UTC')
+ # yet will roundtrip
+ self.assertEqual(t.strftime(fmt), tstr)
+
+ # Produce naive time if no %z is provided
+ self.assertEqual(strptime("UTC", "%Z").tzinfo, None)
+
+ def test_strptime_errors(self):
+ for tzstr in ("-2400", "-000", "z"):
+ with self.assertRaises(ValueError):
+ self.theclass.strptime(tzstr, "%z")
+
+ def test_strptime_single_digit(self):
+ # bpo-34903: Check that single digit times are allowed.
+ t = self.theclass(4, 5, 6)
+ inputs = [
+ ('%H', '4:05:06', '%H:%M:%S', t),
+ ('%M', '04:5:06', '%H:%M:%S', t),
+ ('%S', '04:05:6', '%H:%M:%S', t),
+ ('%I', '4am:05:06', '%I%p:%M:%S', t),
+ ]
+ for reason, string, format, target in inputs:
+ reason = 'test single digit ' + reason
+ with self.subTest(reason=reason,
+ string=string,
+ format=format,
+ target=target):
+ newdate = self.theclass.strptime(string, format)
+ self.assertEqual(newdate, target, msg=reason)
+
def test_bool(self):
# time is always True.
cls = self.theclass
diff --git a/Misc/NEWS.d/next/Library/2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst b/Misc/NEWS.d/next/Library/2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst
new file mode 100644
index 0000000..18e3506
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst
@@ -0,0 +1,2 @@
+Add :meth:`datetime.time.strptime` and :meth:`datetime.date.strptime`.
+Contributed by Wannes Boeykens.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 58b3653..0d91bef 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -3445,6 +3445,27 @@ date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw)
return new_date_subclass_ex(year, month, day, cls);
}
+/* Return new date from _strptime.strptime_datetime_date(). */
+static PyObject *
+date_strptime(PyObject *cls, PyObject *args)
+{
+ PyObject *string, *format, *result;
+
+ if (!PyArg_ParseTuple(args, "UU:strptime", &string, &format)) {
+ return NULL;
+ }
+
+ PyObject *module = PyImport_Import(&_Py_ID(_strptime));
+ if (module == NULL) {
+ return NULL;
+ }
+ result = PyObject_CallMethodObjArgs(module,
+ &_Py_ID(_strptime_datetime_date), cls,
+ string, format, NULL);
+ Py_DECREF(module);
+ return result;
+}
+
/*
* Date arithmetic.
@@ -3910,6 +3931,11 @@ static PyMethodDef date_methods[] = {
"number and weekday.\n\n"
"This is the inverse of the date.isocalendar() function")},
+ {"strptime", (PyCFunction)date_strptime,
+ METH_VARARGS | METH_CLASS,
+ PyDoc_STR("string, format -> new date parsed from a string "
+ "(like time.strptime()).")},
+
{"today", (PyCFunction)date_today, METH_NOARGS | METH_CLASS,
PyDoc_STR("Current date or datetime: same as "
"self.__class__.fromtimestamp(time.time()).")},
@@ -4644,6 +4670,27 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw)
return self;
}
+/* Return new time from _strptime.strptime_datetime_time(). */
+static PyObject *
+time_strptime(PyObject *cls, PyObject *args)
+{
+ PyObject *string, *format, *result;
+
+ if (!PyArg_ParseTuple(args, "UU:strptime", &string, &format)) {
+ return NULL;
+ }
+
+ PyObject *module = PyImport_Import(&_Py_ID(_strptime));
+ if (module == NULL) {
+ return NULL;
+ }
+ result = PyObject_CallMethodObjArgs(module,
+ &_Py_ID(_strptime_datetime_time), cls,
+ string, format, NULL);
+ Py_DECREF(module);
+ return result;
+}
+
/*
* Destructor.
*/
@@ -5079,6 +5126,15 @@ time_reduce(PyDateTime_Time *self, PyObject *arg)
static PyMethodDef time_methods[] = {
+ /* Class method: */
+
+ {"strptime", (PyCFunction)time_strptime,
+ METH_VARARGS | METH_CLASS,
+ PyDoc_STR("string, format -> new time parsed from a string "
+ "(like time.strptime()).")},
+
+ /* Instance methods: */
+
{"isoformat", _PyCFunction_CAST(time_isoformat), METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("Return string in ISO 8601 format, [HH[:MM[:SS[.mmm[uuu]]]]]"
"[+HH:MM].\n\n"
@@ -5586,7 +5642,7 @@ datetime_utcfromtimestamp(PyObject *cls, PyObject *args)
return result;
}
-/* Return new datetime from _strptime.strptime_datetime(). */
+/* Return new datetime from _strptime.strptime_datetime_datetime(). */
static PyObject *
datetime_strptime(PyObject *cls, PyObject *args)
{
@@ -5599,7 +5655,8 @@ datetime_strptime(PyObject *cls, PyObject *args)
if (module == NULL) {
return NULL;
}
- result = PyObject_CallMethodObjArgs(module, &_Py_ID(_strptime_datetime),
+ result = PyObject_CallMethodObjArgs(module,
+ &_Py_ID(_strptime_datetime_datetime),
cls, string, format, NULL);
Py_DECREF(module);
return result;