]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-41431: Add `datetime.time.strptime()` and `datetime.date.strptime()` (#120752)
authorNice Zombies <nineteendo19d0@gmail.com>
Wed, 25 Sep 2024 21:43:58 +0000 (23:43 +0200)
committerGitHub <noreply@github.com>
Wed, 25 Sep 2024 21:43:58 +0000 (14:43 -0700)
* 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>
Doc/library/datetime.rst
Doc/whatsnew/3.14.rst
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/_pydatetime.py
Lib/_strptime.py
Lib/test/datetimetester.py
Misc/NEWS.d/next/Library/2024-06-19-19-53-42.gh-issue-41431.gnkUc5.rst [new file with mode: 0644]
Modules/_datetimemodule.c

index 0e7dc4f262bab4b5d0cc66bf31f90c54c3fba7d2..59e2dbd6847538f6f3909b46c020a0a3720500e9 100644 (file)
@@ -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:
index 4d22f7ac58a5b777bfc37544f04d062274a0b5c0..09d096c3eae33941910195c583e4ba90a020694e 100644 (file)
@@ -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
 --
 
index a5f13692c627e49dae8784eec62fffd7fb3a3521..28a76c36801b4bd0609ed00c8b041b724b4b9148 100644 (file)
@@ -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));
index dd958dcd9fe3ace331be3f79060e16f9eaf1d26c..ac789b06fb8a616daefa22a9f420d64dcd2b7a17 100644 (file)
@@ -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)
index 8d7da8befc94c18159da63ff144d5246bc6845bb..7847a5c63ebf3f17a17ae74bbdad7593740539f5 100644 (file)
@@ -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), \
index d5ad61d4c62e224139f3f07a597bbdaf89cd5f6a..a688f70a2ba36ffd8f774c5dd3c8d3abbc4be40a 100644 (file)
@@ -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);
index 154e6ebb9c51311b4936d1b0e081b024f0d72674..78e03e328967402b271974171f726e1c1770c038 100644 (file)
@@ -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
index 3f868bcab4244695cda6a7c88d1df823dea5f36b..a3f8bb544d518dc9c62993272b247d7fe60791e0 100644 (file)
@@ -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)
index 16aff186eb69f7b25095dd8a4d1ad0beac420d21..c81408b344968de449d08ccb0d75a1bb09361641 100644 (file)
@@ -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 (file)
index 0000000..18e3506
--- /dev/null
@@ -0,0 +1,2 @@
+Add :meth:`datetime.time.strptime` and :meth:`datetime.date.strptime`.\r
+Contributed by Wannes Boeykens.\r
index 58b365334869da9dd03c50fabfbc2ad61b41f042..0d91bef2ff9bdac7ac803cc05e3dac938b652903 100644 (file)
@@ -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;