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

[3.13] Revert "[3.13] gh-120713: Normalize year with century for datetime.strftime (GH-120820) (GH-121144)" #122408

Merged
Merged
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
19 changes: 0 additions & 19 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,17 +204,6 @@ 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 @@ -272,14 +261,6 @@ 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 object.year < 1000 and _need_normalize_century():
# Note that datetime(1000, 1, 1).strftime('%G') == '1000' so
# year 1000 for %G can go on the fast path.
if ch == 'G':
year = int(_time.strftime("%G", timetuple))
else:
year = object.year
push('{:04}'.format(year))
else:
push('%')
push(ch)
Expand Down
32 changes: 12 additions & 20 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1697,26 +1697,18 @@ def test_bool(self):
self.assertTrue(self.theclass.max)

def test_strftime_y2k(self):
# 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'.
dataset = (
(1, 0),
(49, -1),
(70, 0),
(99, 0),
(100, -1),
(999, 0),
(1000, 0),
(1970, 0),
)
for year, offset in dataset:
for specifier in 'YG':
with self.subTest(year=year, specifier=specifier):
d = self.theclass(year, 1, 1)
if specifier == 'G':
year += offset
self.assertEqual(d.strftime(f"%{specifier}"), f"{year:04d}")
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)

def test_replace(self):
cls = self.theclass
Expand Down
54 changes: 4 additions & 50 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1848,23 +1848,13 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
const char *ptoappend; /* ptr to string to append to output buffer */
Py_ssize_t ntoappend; /* # of bytes to append to output buffer */

#ifdef Py_NORMALIZE_CENTURY
/* Buffer of maximum size of formatted year permitted by long. */
char buf[SIZEOF_LONG*5/2+2];
#endif

assert(object && format && timetuple);
assert(PyUnicode_Check(format));
/* Convert the input format to a C string and size */
pin = PyUnicode_AsUTF8AndSize(format, &flen);
if (!pin)
return NULL;

PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
if (strftime == NULL) {
goto Done;
}

/* Scan the input format, looking for %z/%Z/%f escapes, building
* a new format. Since computing the replacements for those codes
* is expensive, don't unless they're actually used.
Expand Down Expand Up @@ -1946,47 +1936,8 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
ptoappend = PyBytes_AS_STRING(freplacement);
ntoappend = PyBytes_GET_SIZE(freplacement);
}
#ifdef Py_NORMALIZE_CENTURY
else if (ch == 'Y' || ch == 'G') {
/* 0-pad year with century as necessary */
PyObject *item = PyTuple_GET_ITEM(timetuple, 0);
long year_long = PyLong_AsLong(item);

if (year_long == -1 && PyErr_Occurred()) {
goto Done;
}
/* Note that datetime(1000, 1, 1).strftime('%G') == '1000' so year
1000 for %G can go on the fast path. */
if (year_long >= 1000) {
goto PassThrough;
}
if (ch == 'G') {
PyObject *year_str = PyObject_CallFunction(strftime, "sO",
"%G", timetuple);
if (year_str == NULL) {
goto Done;
}
PyObject *year = PyNumber_Long(year_str);
Py_DECREF(year_str);
if (year == NULL) {
goto Done;
}
year_long = PyLong_AsLong(year);
Py_DECREF(year);
if (year_long == -1 && PyErr_Occurred()) {
goto Done;
}
}

ntoappend = PyOS_snprintf(buf, sizeof(buf), "%04ld", year_long);
ptoappend = buf;
}
#endif
else {
/* percent followed by something else */
#ifdef Py_NORMALIZE_CENTURY
PassThrough:
#endif
ptoappend = pin - 2;
ntoappend = 2;
}
Expand Down Expand Up @@ -2018,21 +1969,24 @@ 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,
format, timetuple, NULL);
Py_DECREF(format);
}
Py_DECREF(strftime);
}
Done:
Py_XDECREF(freplacement);
Py_XDECREF(zreplacement);
Py_XDECREF(colonzreplacement);
Py_XDECREF(Zreplacement);
Py_XDECREF(newfmt);
Py_XDECREF(strftime);
return result;
}

Expand Down
52 changes: 0 additions & 52 deletions configure

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

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

AC_CACHE_CHECK([whether year with century should be normalized for strftime], [ac_cv_normalize_century], [
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <time.h>
#include <string.h>

int main(void)
{
char year[5];
struct tm date = {
.tm_year = -1801,
.tm_mon = 0,
.tm_mday = 1
};
if (strftime(year, sizeof(year), "%Y", &date) && !strcmp(year, "0099")) {
return 1;
}
return 0;
}
]])],
[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([Py_NORMALIZE_CENTURY], [1],
[Define if year with century should be normalized for strftime.])
fi

dnl check for ncursesw/ncurses and panelw/panel
dnl NOTE: old curses is not detected.
dnl have_curses=[no, yes]
Expand Down
3 changes: 0 additions & 3 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -1686,9 +1686,6 @@
SipHash13: 3, externally defined: 0 */
#undef Py_HASH_ALGORITHM

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

/* Define if rl_startup_hook takes arguments */
#undef Py_RL_STARTUP_HOOK_TAKES_ARGS

Expand Down
Loading