Skip to content

Commit 9f6f879

Browse files
Revert "[3.13] gh-120713: Normalize year with century for datetime.strftime (GH-120820) (GH-121144)" (GH-122408)
This reverts commit 009618f.
1 parent 10cf7d6 commit 9f6f879

File tree

6 files changed

+16
-172
lines changed

6 files changed

+16
-172
lines changed

Lib/_pydatetime.py

-19
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,6 @@ def _format_offset(off, sep=':'):
204204
s += '.%06d' % ss.microseconds
205205
return s
206206

207-
_normalize_century = None
208-
def _need_normalize_century():
209-
global _normalize_century
210-
if _normalize_century is None:
211-
try:
212-
_normalize_century = (
213-
_time.strftime("%Y", (99, 1, 1, 0, 0, 0, 0, 1, 0)) != "0099")
214-
except ValueError:
215-
_normalize_century = True
216-
return _normalize_century
217-
218207
# Correctly substitute for %z and %Z escapes in strftime formats.
219208
def _wrap_strftime(object, format, timetuple):
220209
# Don't call utcoffset() or tzname() unless actually needed.
@@ -272,14 +261,6 @@ def _wrap_strftime(object, format, timetuple):
272261
# strftime is going to have at this: escape %
273262
Zreplace = s.replace('%', '%%')
274263
newformat.append(Zreplace)
275-
elif ch in 'YG' and object.year < 1000 and _need_normalize_century():
276-
# Note that datetime(1000, 1, 1).strftime('%G') == '1000' so
277-
# year 1000 for %G can go on the fast path.
278-
if ch == 'G':
279-
year = int(_time.strftime("%G", timetuple))
280-
else:
281-
year = object.year
282-
push('{:04}'.format(year))
283264
else:
284265
push('%')
285266
push(ch)

Lib/test/datetimetester.py

+12-20
Original file line numberDiff line numberDiff line change
@@ -1697,26 +1697,18 @@ def test_bool(self):
16971697
self.assertTrue(self.theclass.max)
16981698

16991699
def test_strftime_y2k(self):
1700-
# Test that years less than 1000 are 0-padded; note that the beginning
1701-
# of an ISO 8601 year may fall in an ISO week of the year before, and
1702-
# therefore needs an offset of -1 when formatting with '%G'.
1703-
dataset = (
1704-
(1, 0),
1705-
(49, -1),
1706-
(70, 0),
1707-
(99, 0),
1708-
(100, -1),
1709-
(999, 0),
1710-
(1000, 0),
1711-
(1970, 0),
1712-
)
1713-
for year, offset in dataset:
1714-
for specifier in 'YG':
1715-
with self.subTest(year=year, specifier=specifier):
1716-
d = self.theclass(year, 1, 1)
1717-
if specifier == 'G':
1718-
year += offset
1719-
self.assertEqual(d.strftime(f"%{specifier}"), f"{year:04d}")
1700+
for y in (1, 49, 70, 99, 100, 999, 1000, 1970):
1701+
d = self.theclass(y, 1, 1)
1702+
# Issue 13305: For years < 1000, the value is not always
1703+
# padded to 4 digits across platforms. The C standard
1704+
# assumes year >= 1900, so it does not specify the number
1705+
# of digits.
1706+
if d.strftime("%Y") != '%04d' % y:
1707+
# Year 42 returns '42', not padded
1708+
self.assertEqual(d.strftime("%Y"), '%d' % y)
1709+
# '0042' is obtained anyway
1710+
if support.has_strftime_extensions:
1711+
self.assertEqual(d.strftime("%4Y"), '%04d' % y)
17201712

17211713
def test_replace(self):
17221714
cls = self.theclass

Modules/_datetimemodule.c

+4-50
Original file line numberDiff line numberDiff line change
@@ -1848,23 +1848,13 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
18481848
const char *ptoappend; /* ptr to string to append to output buffer */
18491849
Py_ssize_t ntoappend; /* # of bytes to append to output buffer */
18501850

1851-
#ifdef Py_NORMALIZE_CENTURY
1852-
/* Buffer of maximum size of formatted year permitted by long. */
1853-
char buf[SIZEOF_LONG*5/2+2];
1854-
#endif
1855-
18561851
assert(object && format && timetuple);
18571852
assert(PyUnicode_Check(format));
18581853
/* Convert the input format to a C string and size */
18591854
pin = PyUnicode_AsUTF8AndSize(format, &flen);
18601855
if (!pin)
18611856
return NULL;
18621857

1863-
PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
1864-
if (strftime == NULL) {
1865-
goto Done;
1866-
}
1867-
18681858
/* Scan the input format, looking for %z/%Z/%f escapes, building
18691859
* a new format. Since computing the replacements for those codes
18701860
* is expensive, don't unless they're actually used.
@@ -1946,47 +1936,8 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
19461936
ptoappend = PyBytes_AS_STRING(freplacement);
19471937
ntoappend = PyBytes_GET_SIZE(freplacement);
19481938
}
1949-
#ifdef Py_NORMALIZE_CENTURY
1950-
else if (ch == 'Y' || ch == 'G') {
1951-
/* 0-pad year with century as necessary */
1952-
PyObject *item = PyTuple_GET_ITEM(timetuple, 0);
1953-
long year_long = PyLong_AsLong(item);
1954-
1955-
if (year_long == -1 && PyErr_Occurred()) {
1956-
goto Done;
1957-
}
1958-
/* Note that datetime(1000, 1, 1).strftime('%G') == '1000' so year
1959-
1000 for %G can go on the fast path. */
1960-
if (year_long >= 1000) {
1961-
goto PassThrough;
1962-
}
1963-
if (ch == 'G') {
1964-
PyObject *year_str = PyObject_CallFunction(strftime, "sO",
1965-
"%G", timetuple);
1966-
if (year_str == NULL) {
1967-
goto Done;
1968-
}
1969-
PyObject *year = PyNumber_Long(year_str);
1970-
Py_DECREF(year_str);
1971-
if (year == NULL) {
1972-
goto Done;
1973-
}
1974-
year_long = PyLong_AsLong(year);
1975-
Py_DECREF(year);
1976-
if (year_long == -1 && PyErr_Occurred()) {
1977-
goto Done;
1978-
}
1979-
}
1980-
1981-
ntoappend = PyOS_snprintf(buf, sizeof(buf), "%04ld", year_long);
1982-
ptoappend = buf;
1983-
}
1984-
#endif
19851939
else {
19861940
/* percent followed by something else */
1987-
#ifdef Py_NORMALIZE_CENTURY
1988-
PassThrough:
1989-
#endif
19901941
ptoappend = pin - 2;
19911942
ntoappend = 2;
19921943
}
@@ -2018,21 +1969,24 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
20181969
goto Done;
20191970
{
20201971
PyObject *format;
1972+
PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
20211973

1974+
if (strftime == NULL)
1975+
goto Done;
20221976
format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt));
20231977
if (format != NULL) {
20241978
result = PyObject_CallFunctionObjArgs(strftime,
20251979
format, timetuple, NULL);
20261980
Py_DECREF(format);
20271981
}
1982+
Py_DECREF(strftime);
20281983
}
20291984
Done:
20301985
Py_XDECREF(freplacement);
20311986
Py_XDECREF(zreplacement);
20321987
Py_XDECREF(colonzreplacement);
20331988
Py_XDECREF(Zreplacement);
20341989
Py_XDECREF(newfmt);
2035-
Py_XDECREF(strftime);
20361990
return result;
20371991
}
20381992

configure

-52
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

-28
Original file line numberDiff line numberDiff line change
@@ -6612,34 +6612,6 @@ then
66126612
[Define if you have struct stat.st_mtimensec])
66136613
fi
66146614

6615-
AC_CACHE_CHECK([whether year with century should be normalized for strftime], [ac_cv_normalize_century], [
6616-
AC_RUN_IFELSE([AC_LANG_SOURCE([[
6617-
#include <time.h>
6618-
#include <string.h>
6619-
6620-
int main(void)
6621-
{
6622-
char year[5];
6623-
struct tm date = {
6624-
.tm_year = -1801,
6625-
.tm_mon = 0,
6626-
.tm_mday = 1
6627-
};
6628-
if (strftime(year, sizeof(year), "%Y", &date) && !strcmp(year, "0099")) {
6629-
return 1;
6630-
}
6631-
return 0;
6632-
}
6633-
]])],
6634-
[ac_cv_normalize_century=yes],
6635-
[ac_cv_normalize_century=no],
6636-
[ac_cv_normalize_century=yes])])
6637-
if test "$ac_cv_normalize_century" = yes
6638-
then
6639-
AC_DEFINE([Py_NORMALIZE_CENTURY], [1],
6640-
[Define if year with century should be normalized for strftime.])
6641-
fi
6642-
66436615
dnl check for ncursesw/ncurses and panelw/panel
66446616
dnl NOTE: old curses is not detected.
66456617
dnl have_curses=[no, yes]

pyconfig.h.in

-3
Original file line numberDiff line numberDiff line change
@@ -1686,9 +1686,6 @@
16861686
SipHash13: 3, externally defined: 0 */
16871687
#undef Py_HASH_ALGORITHM
16881688

1689-
/* Define if year with century should be normalized for strftime. */
1690-
#undef Py_NORMALIZE_CENTURY
1691-
16921689
/* Define if rl_startup_hook takes arguments */
16931690
#undef Py_RL_STARTUP_HOOK_TAKES_ARGS
16941691

0 commit comments

Comments
 (0)