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-120713: normalize year with century for datetime.strftime #120820

Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9022177
gh-120713: normalize year with century for datetime.strftime
blhsing Jun 21, 2024
a1592e3
📜🤖 Added by blurb_it.
blurb-it[bot] Jun 21, 2024
48affbd
gh-120713: fixed pyconfig.h.in
blhsing Jun 21, 2024
2ff6c0b
Update 2024-06-21-06-37-46.gh-issue-120713.WBbQx4.rst
blhsing Jun 21, 2024
ab39dd9
Update datetimetester.py
blhsing Jun 21, 2024
b905a4e
Update datetimetester.py
blhsing Jun 21, 2024
b43858e
Update _pydatetime.py
blhsing Jun 21, 2024
6ca887e
gh-120713: format "%Y" with "%4d" using sprintf instead of replacing …
blhsing Jun 24, 2024
a9623c4
gh-120713: test that a 0-padded year with century is guaranteed
blhsing Jun 24, 2024
2fc1c6a
gh-120713: revised detection logics in Python
blhsing Jun 24, 2024
2c3252f
gh-120713: fixed test
blhsing Jun 24, 2024
9fd3d2d
gh-120713: use "%04ld" instead of "%04d" to avoid unnecessary int con…
blhsing Jun 24, 2024
e64780b
gh-120713: fixed scope for formatted year buffer
blhsing Jun 24, 2024
32026be
gh-120713: added #ifdef guard over formatted year buffer declaration
blhsing Jun 24, 2024
fed7657
gh-120713: fixed typo
blhsing Jun 24, 2024
7560a60
gh-120713: account for "%G"; increase buffer for formatted year to ma…
blhsing Jun 25, 2024
f5508ee
gh-120713: be pessimistic when cross-compiling
blhsing Jun 25, 2024
abc3d21
gh-120713: call time.strftime instead to obtain the proper ISO-8601 year
blhsing Jun 25, 2024
ff7de92
gh-120713: align comment
blhsing Jun 25, 2024
9c9e645
gh-120713: additional comment and formatting
blhsing Jun 25, 2024
0f8886d
gh-120713: fixed comment
blhsing Jun 25, 2024
2ff8cc5
gh-120713: streamlined C code with better error handling
blhsing Jun 26, 2024
637c341
gh-120713: release reference sooner
blhsing Jun 26, 2024
07327c5
gh-120713: release reference to year if created for %G
blhsing Jun 26, 2024
818d1f4
Update 2024-06-21-06-37-46.gh-issue-120713.WBbQx4.rst
blhsing Jun 26, 2024
7038c1d
gh-120713: made year as python string a variable separate from result…
blhsing Jun 27, 2024
2fb0262
gh-120713: skip 0-padding logics if year > 1000
blhsing Jun 27, 2024
96484f9
gh-120713: made python implementation skip 0-padding logics if year >…
blhsing Jun 27, 2024
6c2c169
gh-120713: made C check return exit code instead of calling exit
blhsing Jun 28, 2024
42244f4
removed inclusion of stdlib.h now that we don't call exit
blhsing Jun 28, 2024
af147f7
gh-120713: updated configure
blhsing Jun 28, 2024
40ae42b
gh-120713: updated configure
blhsing Jun 28, 2024
fc765d0
gh-120713: removed unnecessary comment
blhsing Jun 28, 2024
3cd21f0
made the method a link in the blurb
blhsing Jun 28, 2024
32cc992
Update configure.ac
blhsing Jun 28, 2024
c506568
gh-120713: renamed macro NORMALIZE_CENTURY; moved declarations of new…
blhsing Jun 28, 2024
5f8bf96
gh-120713: initialize strftime on declaration
blhsing Jun 28, 2024
07df016
gh-120713: added versionchanged info in the documentation
blhsing Jun 28, 2024
0588e38
gh-120713: switched to PyOS_snprintf; moved declarations closer to us…
blhsing Jun 28, 2024
e8bbd88
gh-120713: reverted addition of the versionchanged directive
blhsing Jun 28, 2024
2ca5af6
gh-120713: moved declarations closer to usage
blhsing Jun 28, 2024
451ebf1
gh-120713: initialize variables on declarations
blhsing Jun 28, 2024
25e4fa5
Update Lib/test/datetimetester.py
blhsing Jun 28, 2024
0656e21
Update Misc/NEWS.d/next/Library/2024-06-21-06-37-46.gh-issue-120713.W…
blhsing Jun 28, 2024
049a05b
Update Modules/_datetimemodule.c
blhsing Jun 28, 2024
a82bca4
gh-120713: moved initialization of strftime above the first goto to a…
blhsing Jun 28, 2024
492ecf9
Update Lib/test/datetimetester.py
blhsing Jun 28, 2024
7d8a9f1
gh-120713: comment on why year 1000 can go on the fast path for %G
blhsing Jun 28, 2024
455fa96
Update Modules/_datetimemodule.c
blhsing Jun 29, 2024
f299c20
Update Modules/_datetimemodule.c
blhsing Jun 29, 2024
fcc7c7f
Update Lib/_pydatetime.py
blhsing Jun 29, 2024
3069d62
Update Modules/_datetimemodule.c
blhsing Jun 29, 2024
e603132
gh-120713: use sizeof instead of a variable to determine the size of …
blhsing Jun 29, 2024
c54739b
gh-120713: use macro instead of sizeof
blhsing Jun 29, 2024
881dc1d
gh-120713: moved buffer declaration to the same scope as the ptoappen…
blhsing Jun 29, 2024
0cd5e27
gh-120713: fixed typo
blhsing Jun 29, 2024
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
17 changes: 17 additions & 0 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,17 @@ def _format_offset(off, sep=':'):
s += '.%06d' % ss.microseconds
return s

_normalize_century = None
def _need_normalize_century():
global _normalize_century
if _normalize_century is None:
try:
_normalize_century = (
_time.strftime("%Y", (99, 1, 1, 0, 0, 0, 0, 1, 0)) != "0099")
except ValueError:
_normalize_century = True
return _normalize_century

# Correctly substitute for %z and %Z escapes in strftime formats.
def _wrap_strftime(object, format, timetuple):
# Don't call utcoffset() or tzname() unless actually needed.
Expand Down Expand Up @@ -261,6 +272,12 @@ def _wrap_strftime(object, format, timetuple):
# strftime is going to have at this: escape %
Zreplace = s.replace('%', '%%')
newformat.append(Zreplace)
elif ch in 'YG' and _need_normalize_century():
blhsing marked this conversation as resolved.
Show resolved Hide resolved
if ch == 'G':
year = int(_time.strftime("%G", timetuple))
else:
year = object.year
push('{:04}'.format(year))
else:
push('%')
push(ch)
Expand Down
23 changes: 11 additions & 12 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1697,18 +1697,17 @@ def test_bool(self):
self.assertTrue(self.theclass.max)

def test_strftime_y2k(self):
for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
d = self.theclass(y, 1, 1)
# Issue 13305: For years < 1000, the value is not always
# padded to 4 digits across platforms. The C standard
# assumes year >= 1900, so it does not specify the number
# of digits.
if d.strftime("%Y") != '%04d' % y:
# Year 42 returns '42', not padded
self.assertEqual(d.strftime("%Y"), '%d' % y)
# '0042' is obtained anyway
if support.has_strftime_extensions:
self.assertEqual(d.strftime("%4Y"), '%04d' % y)
# Test that years less than 1000 are 0-padded; note that the beginning
# of an ISO 8601 year may fall in an ISO week of the year before, and
# therefore needs an offset of -1 when formatting with '%G'.
for y, o in ((1, 0), (49, -1), (70, 0), (99, 0), (100, -1), (999, 0),
(1000, 0), (1970, 0)):
blhsing marked this conversation as resolved.
Show resolved Hide resolved
for s in 'YG':
with self.subTest(year=y, specifier=s):
d = self.theclass(y, 1, 1)
if s == 'G':
y += o
self.assertEqual(d.strftime("%" + s), '%04d' % y)
blhsing marked this conversation as resolved.
Show resolved Hide resolved

def test_replace(self):
cls = self.theclass
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`datetime.datetime.strftime` now 0-pads a year <= 999 for the format specifier ``%Y`` on Linux.
Patch by Ben Hsing
29 changes: 26 additions & 3 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "datetime.h"


#include <stdlib.h>
#include <time.h>

#ifdef MS_WINDOWS
Expand Down Expand Up @@ -1837,6 +1838,10 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
PyObject *colonzreplacement = NULL; /* py string, replacement for %:z */
PyObject *Zreplacement = NULL; /* py string, replacement for %Z */
PyObject *freplacement = NULL; /* py string, replacement for %f */
#ifdef NORMALIZE_CENTURY
long year; /* year of timetuple as long */
char year_formatted[12]; /* formatted year with century for %Y */
#endif

const char *pin; /* pointer to next char in input format */
Py_ssize_t flen; /* length of input format */
Expand Down Expand Up @@ -1873,6 +1878,11 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
pnew = PyBytes_AsString(newfmt);
usednew = 0;

PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
blhsing marked this conversation as resolved.
Show resolved Hide resolved

if (strftime == NULL)
goto Done;
blhsing marked this conversation as resolved.
Show resolved Hide resolved

while ((ch = *pin++) != '\0') {
if (ch != '%') {
ptoappend = pin - 1;
Expand Down Expand Up @@ -1939,6 +1949,22 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
ptoappend = PyBytes_AS_STRING(freplacement);
ntoappend = PyBytes_GET_SIZE(freplacement);
}
#ifdef NORMALIZE_CENTURY
else if (ch == 'Y' || ch == 'G') {
/* 0-pad year with century as necessary */
if (ch == 'G') {
format = PyUnicode_FromString("%G");
blhsing marked this conversation as resolved.
Show resolved Hide resolved
result = PyObject_CallFunctionObjArgs(strftime, format,
timetuple, NULL);
year = atoi(PyUnicode_AsUTF8(result));
blhsing marked this conversation as resolved.
Show resolved Hide resolved
Py_DECREF(format);
Py_DECREF(result);
} else
year = PyLong_AsLong(PyTuple_GET_ITEM(timetuple, 0));
blhsing marked this conversation as resolved.
Show resolved Hide resolved
ntoappend = sprintf(year_formatted, "%04ld", year);
ptoappend = year_formatted;
}
#endif
else {
/* percent followed by something else */
ptoappend = pin - 2;
Expand Down Expand Up @@ -1972,10 +1998,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
goto Done;
{
PyObject *format;
PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");

if (strftime == NULL)
goto Done;
format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt));
if (format != NULL) {
result = PyObject_CallFunctionObjArgs(strftime,
Expand Down
51 changes: 51 additions & 0 deletions configure

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

27 changes: 27 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -6572,6 +6572,33 @@ then
[Define if you have struct stat.st_mtimensec])
fi

# Check if year with century should be normalized for strftime
blhsing marked this conversation as resolved.
Show resolved Hide resolved
AC_CACHE_CHECK([whether year with century should be normalized for strftime], [ac_cv_normalize_century], [
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <stdlib.h>
blhsing marked this conversation as resolved.
Show resolved Hide resolved
#include <time.h>
#include <string.h>

int main(void)
{
char year[5];
struct tm date = {
.tm_year = -1801,
.tm_mon = 0,
.tm_mday = 1
};
exit(strftime(year, sizeof year, "%Y", &date) && !strcmp(year, "0099"));
blhsing marked this conversation as resolved.
Show resolved Hide resolved
}
]])],
[ac_cv_normalize_century=yes],
[ac_cv_normalize_century=no],
[ac_cv_normalize_century=yes])])
if test "$ac_cv_normalize_century" = yes
then
AC_DEFINE([NORMALIZE_CENTURY], [1],
blhsing marked this conversation as resolved.
Show resolved Hide resolved
[Define if year with century should be normalized for strftime.])
fi

dnl check for ncurses/ncursesw and panel/panelw
dnl NOTE: old curses is not detected.
dnl have_curses=[no, ncursesw, ncurses]
Expand Down
3 changes: 3 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,9 @@
/* Define if mvwdelch in curses.h is an expression. */
#undef MVWDELCH_IS_EXPRESSION

/* Define if year with century should be normalized for strftime. */
#undef NORMALIZE_CENTURY

/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

Expand Down
Loading