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.
# 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)
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'.
+ 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}")
def test_replace(self):
cls = self.theclass
--- /dev/null
+:meth:`datetime.datetime.strftime` now 0-pads years with less than four digits for the format specifiers ``%Y`` and ``%G`` on Linux.
+Patch by Ben Hsing
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 */
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.
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;
}
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(colonzreplacement);
Py_XDECREF(Zreplacement);
Py_XDECREF(newfmt);
+ Py_XDECREF(strftime);
return result;
}
fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether year with century should be normalized for strftime" >&5
+printf %s "checking whether year with century should be normalized for strftime... " >&6; }
+if test ${ac_cv_normalize_century+y}
+then :
+ printf %s "(cached) " >&6
+else $as_nop
+
+if test "$cross_compiling" = yes
+then :
+ ac_cv_normalize_century=yes
+else $as_nop
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#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;
+}
+
+_ACEOF
+if ac_fn_c_try_run "$LINENO"
+then :
+ ac_cv_normalize_century=yes
+else $as_nop
+ ac_cv_normalize_century=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_normalize_century" >&5
+printf "%s\n" "$ac_cv_normalize_century" >&6; }
+if test "$ac_cv_normalize_century" = yes
+then
+
+printf "%s\n" "#define Py_NORMALIZE_CENTURY 1" >>confdefs.h
+
+fi
+
have_curses=no
have_panel=no
[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 ncurses/ncursesw and panel/panelw
dnl NOTE: old curses is not detected.
dnl have_curses=[no, ncursesw, ncurses]
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 you want to enable internal statistics gathering. */
#undef Py_STATS