Skip to content

gh-127527: Improve error handling in time_strftime1 #127528

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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
14 changes: 9 additions & 5 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2638,11 +2638,15 @@ For :class:`date` objects, the format codes for hours, minutes, seconds, and
microseconds should not be used, as :class:`date` objects have no such
values. If they're used anyway, 0 is substituted for them.

For the same reason, handling of format strings containing Unicode code points
that can't be represented in the charset of the current locale is also
platform-dependent. On some platforms such code points are preserved intact in
the output, while on others ``strftime`` may raise :exc:`UnicodeError` or return
an empty string instead.
If a format directive is unrecognized, the behavior is platform-dependent. glibc
will return the directive unmodified, Windows will raise a :exc:`ValueError`,
and macOS, musl, and BSD will return an empty string.

Handling of format strings containing Unicode code points that can't be
represented in the charset of the current locale is also platform-dependent. On
some platforms such code points are preserved intact in the output, while on
others ``strftime`` may raise :exc:`UnicodeError` or return an empty string
instead.
Comment on lines +2645 to +2649
Copy link
Member

Choose a reason for hiding this comment

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

Is this still true? According to test_strftime_special, these should be always preserved.


Notes:

Expand Down
4 changes: 4 additions & 0 deletions Doc/library/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,10 @@ Functions
and thus does not necessarily support all directives available that are not
documented as supported.

If a format directive is unrecognized, the behavior is platform-dependent.
glibc will return the directive unmodified, Windows will raise a
:exc:`ValueError`, and macOS, musl, and BSD will return an empty string.


.. class:: struct_time

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Made minor improvements to the error handling in ``time.strftime`` and
documented the behavior when an unknown directive is seen
38 changes: 28 additions & 10 deletions Modules/timemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -837,20 +837,38 @@ time_strftime1(time_char **outbuf, size_t *bufsize,
return NULL;
}
#endif
if (buflen == 0 && *bufsize < 256 * fmtlen) {
*bufsize += *bufsize;
continue;
if ((*outbuf)[buflen] == '\0') {
#ifdef HAVE_WCSFTIME
return PyUnicode_FromWideChar(*outbuf, buflen);
#else
return PyUnicode_DecodeLocaleAndSize(*outbuf, buflen, "surrogateescape");
#endif
}
if (buflen != 0) {
// I believe this is unreachable in glibc, musl, and BSD.
PyErr_SetString(PyExc_SystemError, "Unexpected behavior from strftime");
return NULL;
}
/* If the buffer is 256 times as long as the format,
it's probably not failing for lack of room!
More likely, the format yields an empty result,
e.g. an empty format, or %Z when the timezone
is unknown. */
if (*bufsize >= 256 * fmtlen) {
// If the buffer is 256 times as long as the format, it's probably
// not failing for lack of room! More likely, `format_time` doesn't
// like the format string.
// I believe that this is unreachable in glibc, but both musl and
// BSD can end up here.
#ifdef HAVE_WCSFTIME
return PyUnicode_FromWideChar(*outbuf, buflen);
// For backwards compatibility, return empty string instead of
// raising a ValueError.
return PyUnicode_FromStringAndSize(NULL, 0);
#else
return PyUnicode_DecodeLocaleAndSize(*outbuf, buflen, "surrogateescape");
// Previously we raised ValueError("embedded null byte") here, so
// this is backwards compatible as long as we are concerned only
// about error type.
PyErr_SetString(PyExc_ValueError, "Invalid format string");
return NULL;
#endif

}
*bufsize += *bufsize;
}
}

Expand Down
Loading