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() #120752

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

nineteendo
Copy link
Contributor

@nineteendo nineteendo commented Jun 19, 2024

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)
Copy link
Contributor Author

@nineteendo nineteendo Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check the format here? The previous pull request used a C function for that, but that obviously wouldn't work if the C implementation isn't available. I think it would be a bit weird if strftime() worked, but strptime() didn't.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do need to check that this doesn't have any non-date components in it, no? We might need to push that into _strptime, though. We could check that it returns all 0s, but then things like 2024-01-01T00:00 will fail to raise an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't strptime() be able to parse the output produced by strftime()? I would prefer if both raised an error for the format or neither, but checking for the fallback date or time seems fine to me.

>>> datetime.date(9999, 12, 31).strftime("%Y-%m-%dT%H:%M:%S")
'9999-12-31T00:00:00'
>>> datetime.time(23, 59, 59).strftime("%Y-%m-%dT%H:%M:%S")
'1900-01-01T23:59:59'

@nineteendo nineteendo changed the title gh-41431: Add datetime.time.strptime and datetime.date.strptime gh-41431: Add datetime.time.strptime() and datetime.date.strptime() Jun 19, 2024
Lib/_strptime.py Outdated Show resolved Hide resolved
@nineteendo nineteendo marked this pull request as ready for review June 19, 2024 20:36
@nineteendo

This comment was marked as resolved.

time tuple. See also :ref:`strftime-strptime-behavior` and
:meth:`date.fromisoformat`.

.. note
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it fine to have this note twice in the documentation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think documentation experts will know better than me, but I think there's a way to define a substitution for this, like this:

|format-note|

.. |format-note| replace::
    If *format* specifies a day of month without a year a :exc:`DeprecationWarning` is emitted. This is to avoid a quadrenial leap year bug in code seeking to parse only a month and day, as the default year used when unspecified in the format is not a leap year. Such *format* values may raise an error in 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

Then you put |format-note| in the other place as well.

Two other options:

  1. move this to a small fragment file (seems like overkill), and use an ..include directive with it.
  2. put the note in one place, then link to it in the other place (e.g. "See the note on :func:datetime.strptime").

I don't think there's a simple way to do it with a parameter, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just wondering if it was a problem it was mentioned twice. Do I link to datetime.strptime or is this fine?

@vstinner
Copy link
Member

@vstinner, would you like to review this? (You requested changes on the old pull request)

I skip my turn :-)

Copy link
Contributor

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I had a pending review that I forgot to send. Here are some comments!

Modules/_datetimemodule.c Outdated Show resolved Hide resolved
Doc/library/datetime.rst Show resolved Hide resolved
Doc/library/datetime.rst Outdated Show resolved Hide resolved
Doc/library/datetime.rst Outdated Show resolved Hide resolved
Doc/library/datetime.rst Show resolved Hide resolved
Doc/library/datetime.rst Outdated Show resolved Hide resolved
Lib/_pydatetime.py Outdated Show resolved Hide resolved
Lib/_pydatetime.py Outdated Show resolved Hide resolved
Lib/_strptime.py Outdated Show resolved Hide resolved
Modules/_datetimemodule.c Outdated Show resolved Hide resolved
Copy link
Member

@pganssle pganssle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great so far! I have some suggestions here mostly about the tests.

I'm also working on some hypothesis based tests for this, because there are a couple of properties that we can easily and usefully test here, specifically:

  1. dt == dt.strptime(dt.strftime(format), format) == dt
  2. my_datetime.strftime(format_with_just_dates).date() == my_datetime.date().strftime(format_with_just_dates)
  3. my_datetime.strftime(format_with_just_times).time() == my_datetime.time().strftime(format_with_just_times)

I think the key will just be getting a good hypothesis strategy that generates the format strings.

Lib/test/datetimetester.py Outdated Show resolved Hide resolved
Lib/test/datetimetester.py Outdated Show resolved Hide resolved
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do need to check that this doesn't have any non-date components in it, no? We might need to push that into _strptime, though. We could check that it returns all 0s, but then things like 2024-01-01T00:00 will fail to raise an exception.

format)
got = date.strptime(string, format)
self.assertEqual(expected, got)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably test that we fail when passed time components:

Suggested change
def test_strptime_date_with_time_error(self):
formats = [
('12', '%H'),
('03', '%M'),
('12', '%S'),
('12:30:14.531', '%H:M:%S.%f'),
('2004-02-03 04:11:23.145', '%Y-%m-%d %H:%M:%S.%f'),
('2004-02 12', '%Y-%m %H'),
]
for dtstr, format in formats:
with self.subTest(format=format):
with self.assertRaises(ValueError):
self.theclass.strptime(dtstr, format)

with self.assertRaises(ValueError): strptime("-2400", "%z")
with self.assertRaises(ValueError): strptime("-000", "%z")
with self.assertRaises(ValueError): strptime("z", "%z")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, we should test to see that there are no date components in the string.

Suggested change
def test_strptime_date_with_time_error(self):
formats = [
('2004', '%Y'),
('02', '%m'),
('13', '%d'),
('2004-02-13', '%Y-%m-%d'),
('2004-02-03 04:11:23.145', '%Y-%m-%d %H:%M:%S.%f'),
('2004-02 12', '%Y-%m %H'),
]
for dtstr, format in formats:
with self.subTest(format=format):
with self.assertRaises(ValueError):
self.theclass.strptime(dtstr, format)

@bedevere-app
Copy link

bedevere-app bot commented Sep 24, 2024

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

nineteendo and others added 2 commits September 25, 2024 07:46
Co-authored-by: Paul Ganssle <1377457+pganssle@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants