Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-41431: Add datetime.time.strptime and datetime.date.strptime #12

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,15 @@ Other constructors, all class methods:
date.max.toordinal()``. For any date *d*,
``date.fromordinal(d.toordinal()) == d``.

.. classmethod:: date.strptime(date_string, format)

Return a :class:`date` corresponding to *date_string*, parsed according to
*format*. :exc:`ValueError` is raised if the date string and format can't be
parsed by :meth:`datetime.time.strptime`, or if time components are present
in the format string. For a complete list of formatting directives, see
:ref:`strftime-strptime-behavior`.

.. versionadded:: 3.8

.. classmethod:: date.fromisoformat(date_string)

Expand Down Expand Up @@ -1737,6 +1746,19 @@ day, and subject to adjustment via a :class:`tzinfo` object.
If an argument outside those ranges is given, :exc:`ValueError` is raised. All
default to 0 except *tzinfo*, which defaults to ``None``.


Other constructors, all class methods:

.. classmethod:: time.strptime(date_string, format)

Return a :class:`datetime.time` corresponding to *date_string*, parsed
according to *format*. :exc:`ValueError` is raised if the date string and
format can't be parsed by :meth:`time.strptime`. For a complete list of
formatting directives, see :ref:`strftime-strptime-behavior`.

.. versionadded:: 3.8


Class attributes:


Expand Down Expand Up @@ -2363,7 +2385,20 @@ 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.
corresponding format string. ``datetime.strptime(date_string, format)`` is
equivalent to ``datetime(*(time.strptime(date_string, format)[0:6]))``, except
when the format includes sub-second components or timezone offset information,
which are supported in ``datetime.strptime`` but are discarded by
:meth:`time.strptime`.

The :meth:`date.strptime` class method creates a :class:`date` object from a
string representing a date and a corresponding format string. :exc:`ValueError`
is raised if the format codes for hours, minutes, seconds, or microseconds are
used.

The :meth:`.time.strptime` class method creates a :class:`.time` object from a
string representing a time and a corresponding format string. :exc:`ValueError`
raised if the format codes for year, month, and day are used.

The table below provides a high-level comparison of :meth:`~.datetime.strftime`
versus :meth:`~.datetime.strptime`:
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ ast
Added :func:`ast.compare` for comparing two ASTs.
(Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.)

datetime
--------

Added :func:`~datetime.date.strptime` and :func:`~datetime.time.strptime`.
(Patch by Alexander Belopolsky, Amaury Forgeot d'Arc, Berker Peksag, Josh-sf,
Juraez Bochi, Maciej Szulik, Matheus Vieira Portela. Contributed by Stéphane
Wirtel)

os
--

Expand Down
28 changes: 27 additions & 1 deletion Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,7 @@ class date:
fromtimestamp()
today()
fromordinal()
strptime()

Operators:

Expand Down Expand Up @@ -1008,6 +1009,16 @@ 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):
"""string, format -> new date instance parsed from a string.

>>> datetime.date.strptime('2012/07/20', '%Y/%m/%d')
datetime.date(2012, 7, 20)
"""
import _strptime
return _strptime._strptime_datetime_date(date_string, format)

# Conversions to string

def __repr__(self):
Expand Down Expand Up @@ -1328,6 +1339,7 @@ class time:
Constructors:

__new__()
strptime()

Operators:

Expand Down Expand Up @@ -1386,6 +1398,16 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold
self._fold = fold
return self

@staticmethod
def strptime(time_string, format):
"""string, format -> new time instance parsed from a string.

>>> datetime.time.strptime('10:40am', '%H:%M%p')
datetime.time(10, 40)
"""
import _strptime
return _strptime._strptime_datetime_time(time_string, format)

# Read-only field accessors
@property
def hour(self):
Expand Down Expand Up @@ -2090,7 +2112,11 @@ def __str__(self):

@classmethod
def strptime(cls, date_string, format):
'string, format -> new datetime parsed from a string (like time.strptime()).'
"""string, format -> new datetime parsed from a string.

>>> datetime.datetime.strptime('2012/07/20 10:40am', '%Y/%m/%d %H:%M%p')
datetime.datetime(2012, 7, 20, 10, 40)
"""
import _strptime
return _strptime._strptime_datetime(cls, date_string, format)

Expand Down
25 changes: 23 additions & 2 deletions Lib/_strptime.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from re import IGNORECASE
from re import escape as re_escape
from datetime import (date as datetime_date,
datetime as datetime_datetime,
timedelta as datetime_timedelta,
timezone as datetime_timezone)
from _thread import allocate_lock as _thread_allocate_lock
Expand Down Expand Up @@ -310,9 +311,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 2-tuple consisting of a time struct and an int containing
"""Return a 3-tuple consisting of a time struct and an int containing
the number of microseconds based on the input string and the
format string."""
format string, and the UTC offset."""

for index, arg in enumerate([data_string, format]):
if not isinstance(arg, str):
Expand Down Expand Up @@ -561,6 +562,10 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
hour, minute, second,
weekday, julian, tz, tzname, gmtoff), fraction, gmtoff_fraction

date_specs = ('%a', '%A', '%b', '%B', '%c', '%d', '%j', '%m', '%U', '%G',
'%u', '%V', '%w', '%W', '%x', '%y', '%Y', '%G', '%u', '%V',)
time_specs = ('%H', '%I', '%M', '%S', '%f',)

def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
"""Return a time struct based on the input string and the
format string."""
Expand All @@ -582,3 +587,19 @@ def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
args += (tz,)

return cls(*args)

def _strptime_datetime_date(data_string, format):
"""Return a date based on the input string and the format string."""
msg = "'{!s}' {} not valid in date format specification."
from _datetime import _check_invalid_datetime_specs
if _check_invalid_datetime_specs(format, time_specs, msg):
_date = _strptime_datetime(datetime_datetime, data_string, format)
return _date.date()

def _strptime_datetime_time(data_string, format):
"""Return a time based on the input string and the format string."""
msg = "'{!s}' {} not valid in time format specification."
from _datetime import _check_invalid_datetime_specs
if _check_invalid_datetime_specs(format, date_specs, msg):
_time = _strptime_datetime(datetime_datetime, data_string, format)
return _time.time()
43 changes: 43 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,30 @@ def test_delta_non_days_ignored(self):
dt2 = dt - delta
self.assertEqual(dt2, dt - days)

def test_strptime_valid_format(self):
tests = [
('2004-12-01', '%Y-%m-%d', date(2004, 12, 1)),
('2004', '%Y', date(2004, 1, 1)),
]
for date_string, date_format, expected in tests:
with self.subTest(date_string=date_string,
date_format=date_format,
expected=expected):
self.assertEqual(expected, date.strptime(date_string, date_format))

def test_strptime_invalid_format(self):
tests = [
('2004-12-01 13:02:47.197', '%Y-%m-%d %H:%M:%S.%f'),
('2018-01-01', ''),
('01', '%M'),
('02', '%H'),
]
for hour, format in tests:
with self.subTest(hour=hour, format=format):
with self.assertRaises(ValueError):
date.strptime(hour, format)


class SubclassDate(date):
sub_var = 1

Expand Down Expand Up @@ -3628,6 +3652,25 @@ def test_strftime(self):
# gh-85432: The parameter was named "fmt" in the pure-Python impl.
t.strftime(format="%f")

def test_strptime_invalid(self):
tests = [
('2004-12-01 13:02:47.197', '%Y-%m-%d %H:%M:%S.%f'),
('2004-12-01', '%Y-%m-%d'),
('12:30:15', ''),
]
for date_string, date_format in tests:
with self.subTest(date_string=date_string, date_format=date_format):
with self.assertRaises(ValueError):
time.strptime(date_string, date_format)

def test_strptime_valid(self):
string = '13:02:47.197'
format = '%H:%M:%S.%f'
result, frac, gmtoff = _strptime._strptime(string, format)
expected = self.theclass(*(result[3:6] + (frac, )))
got = time.strptime(string, format)
self.assertEqual(expected, got)

def test_format(self):
t = self.theclass(1, 2, 3, 4)
self.assertEqual(t.__format__(''), str(t))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :func:`datetime.date.strptime` and :func:`datetime.time.strptime` class methods.
Loading
Loading