Skip to content

Commit e6878fc

Browse files
Revert "[3.12] gh-120713: Normalize year with century for datetime.strftime (GH-120820) (GH-121145)" (GH-122409)
This reverts commit 027902b.
1 parent 5bd2ea2 commit e6878fc

File tree

7 files changed

+16
-174
lines changed

7 files changed

+16
-174
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
@@ -1687,26 +1687,18 @@ def test_bool(self):
16871687
self.assertTrue(self.theclass.max)
16881688

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

17111703
def test_replace(self):
17121704
cls = self.theclass

Misc/NEWS.d/next/Library/2024-06-21-06-37-46.gh-issue-120713.WBbQx4.rst

-2
This file was deleted.

Modules/_datetimemodule.c

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

1606-
#ifdef Py_NORMALIZE_CENTURY
1607-
/* Buffer of maximum size of formatted year permitted by long. */
1608-
char buf[SIZEOF_LONG*5/2+2];
1609-
#endif
1610-
16111606
assert(object && format && timetuple);
16121607
assert(PyUnicode_Check(format));
16131608
/* Convert the input format to a C string and size */
16141609
pin = PyUnicode_AsUTF8AndSize(format, &flen);
16151610
if (!pin)
16161611
return NULL;
16171612

1618-
PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
1619-
if (strftime == NULL) {
1620-
goto Done;
1621-
}
1622-
16231613
/* Scan the input format, looking for %z/%Z/%f escapes, building
16241614
* a new format. Since computing the replacements for those codes
16251615
* is expensive, don't unless they're actually used.
@@ -1701,47 +1691,8 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
17011691
ptoappend = PyBytes_AS_STRING(freplacement);
17021692
ntoappend = PyBytes_GET_SIZE(freplacement);
17031693
}
1704-
#ifdef Py_NORMALIZE_CENTURY
1705-
else if (ch == 'Y' || ch == 'G') {
1706-
/* 0-pad year with century as necessary */
1707-
PyObject *item = PyTuple_GET_ITEM(timetuple, 0);
1708-
long year_long = PyLong_AsLong(item);
1709-
1710-
if (year_long == -1 && PyErr_Occurred()) {
1711-
goto Done;
1712-
}
1713-
/* Note that datetime(1000, 1, 1).strftime('%G') == '1000' so year
1714-
1000 for %G can go on the fast path. */
1715-
if (year_long >= 1000) {
1716-
goto PassThrough;
1717-
}
1718-
if (ch == 'G') {
1719-
PyObject *year_str = PyObject_CallFunction(strftime, "sO",
1720-
"%G", timetuple);
1721-
if (year_str == NULL) {
1722-
goto Done;
1723-
}
1724-
PyObject *year = PyNumber_Long(year_str);
1725-
Py_DECREF(year_str);
1726-
if (year == NULL) {
1727-
goto Done;
1728-
}
1729-
year_long = PyLong_AsLong(year);
1730-
Py_DECREF(year);
1731-
if (year_long == -1 && PyErr_Occurred()) {
1732-
goto Done;
1733-
}
1734-
}
1735-
1736-
ntoappend = PyOS_snprintf(buf, sizeof(buf), "%04ld", year_long);
1737-
ptoappend = buf;
1738-
}
1739-
#endif
17401694
else {
17411695
/* percent followed by something else */
1742-
#ifdef Py_NORMALIZE_CENTURY
1743-
PassThrough:
1744-
#endif
17451696
ptoappend = pin - 2;
17461697
ntoappend = 2;
17471698
}
@@ -1773,21 +1724,24 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
17731724
goto Done;
17741725
{
17751726
PyObject *format;
1727+
PyObject *strftime = _PyImport_GetModuleAttrString("time", "strftime");
17761728

1729+
if (strftime == NULL)
1730+
goto Done;
17771731
format = PyUnicode_FromString(PyBytes_AS_STRING(newfmt));
17781732
if (format != NULL) {
17791733
result = PyObject_CallFunctionObjArgs(strftime,
17801734
format, timetuple, NULL);
17811735
Py_DECREF(format);
17821736
}
1737+
Py_DECREF(strftime);
17831738
}
17841739
Done:
17851740
Py_XDECREF(freplacement);
17861741
Py_XDECREF(zreplacement);
17871742
Py_XDECREF(colonzreplacement);
17881743
Py_XDECREF(Zreplacement);
17891744
Py_XDECREF(newfmt);
1790-
Py_XDECREF(strftime);
17911745
return result;
17921746
}
17931747

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
@@ -6415,34 +6415,6 @@ then
64156415
[Define if you have struct stat.st_mtimensec])
64166416
fi
64176417

6418-
AC_CACHE_CHECK([whether year with century should be normalized for strftime], [ac_cv_normalize_century], [
6419-
AC_RUN_IFELSE([AC_LANG_SOURCE([[
6420-
#include <time.h>
6421-
#include <string.h>
6422-
6423-
int main(void)
6424-
{
6425-
char year[5];
6426-
struct tm date = {
6427-
.tm_year = -1801,
6428-
.tm_mon = 0,
6429-
.tm_mday = 1
6430-
};
6431-
if (strftime(year, sizeof(year), "%Y", &date) && !strcmp(year, "0099")) {
6432-
return 1;
6433-
}
6434-
return 0;
6435-
}
6436-
]])],
6437-
[ac_cv_normalize_century=yes],
6438-
[ac_cv_normalize_century=no],
6439-
[ac_cv_normalize_century=yes])])
6440-
if test "$ac_cv_normalize_century" = yes
6441-
then
6442-
AC_DEFINE([Py_NORMALIZE_CENTURY], [1],
6443-
[Define if year with century should be normalized for strftime.])
6444-
fi
6445-
64466418
dnl check for ncurses/ncursesw and panel/panelw
64476419
dnl NOTE: old curses is not detected.
64486420
dnl have_curses=[no, ncursesw, ncurses]

pyconfig.h.in

-3
Original file line numberDiff line numberDiff line change
@@ -1618,9 +1618,6 @@
16181618
SipHash13: 3, externally defined: 0 */
16191619
#undef Py_HASH_ALGORITHM
16201620

1621-
/* Define if year with century should be normalized for strftime. */
1622-
#undef Py_NORMALIZE_CENTURY
1623-
16241621
/* Define if you want to enable internal statistics gathering. */
16251622
#undef Py_STATS
16261623

0 commit comments

Comments
 (0)