Skip to content

Commit

Permalink
Revert "[3.13] pythongh-120713: Normalize year with century for datet…
Browse files Browse the repository at this point in the history
…ime.strftime (pythonGH-120820) (pythonGH-121144)" (pythonGH-122408)

This reverts commit 009618f.
  • Loading branch information
serhiy-storchaka authored Jul 29, 2024
1 parent 10cf7d6 commit 9f6f879
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 172 deletions.
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

0 comments on commit 9f6f879

Please sign in to comment.