Skip to content

Commit

Permalink
pythongh-41431: Add datetime.time.strptime() and `datetime.date.str…
Browse files Browse the repository at this point in the history
…ptime()` (python#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 <[email protected]>

* Remove parentheses

* Use helper function

* Remove bad return

* Link to github issue

* Fix directive

* Apply suggestions from code review

Co-authored-by: Paul Ganssle <[email protected]>

* Fix test cases

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Paul Ganssle <[email protected]>
  • Loading branch information
4 people authored Sep 25, 2024
1 parent b0c6cf5 commit 9968caa
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 36 deletions.
77 changes: 62 additions & 15 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
--

Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,7 @@ class date:
fromtimestamp()
today()
fromordinal()
strptime()
Operators:
Expand Down Expand Up @@ -1051,6 +1052,12 @@ def fromisocalendar(cls, year, week, day):
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):
Expand Down Expand Up @@ -1371,6 +1378,7 @@ class time:
Constructors:
__new__()
strptime()
Operators:
Expand Down Expand Up @@ -1429,6 +1437,12 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold
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):
Expand Down Expand Up @@ -2152,7 +2166,7 @@ def __str__(self):
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
Expand Down
44 changes: 33 additions & 11 deletions Lib/_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading

0 comments on commit 9968caa

Please sign in to comment.