bpo-34373: Fix time.mktime() on AIX (GH-12726)

Fix time.mktime() error handling on AIX for year before 1970.

Other changes:

* mktime(): rename variable 'buf' to 'tm'.
* _PyTime_localtime():

  * Use "localtime" rather than "ctime" in the error message
    (specific to AIX).
  * Always initialize errno to 0 just in case if localtime_r()
    doesn't set errno on error.
  * On AIX, avoid abs() which is limited to int type.
  * EINVAL constant is now always available.
This commit is contained in:
Victor Stinner 2019-04-09 19:12:26 +02:00 committed by GitHub
parent 8abc3f4f91
commit 8709490f48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 40 deletions

View file

@ -0,0 +1 @@
Fix :func:`time.mktime` error handling on AIX for year before 1970.

View file

@ -990,60 +990,68 @@ not present, current time as returned by localtime() is used.");
#ifdef HAVE_MKTIME
static PyObject *
time_mktime(PyObject *self, PyObject *tup)
time_mktime(PyObject *self, PyObject *tm_tuple)
{
struct tm buf;
struct tm tm;
time_t tt;
#ifdef _AIX
time_t clk;
int year = buf.tm_year;
int delta_days = 0;
#endif
if (!gettmarg(tup, &buf,
if (!gettmarg(tm_tuple, &tm,
"iiiiiiiii;mktime(): illegal time tuple argument"))
{
return NULL;
}
#ifndef _AIX
buf.tm_wday = -1; /* sentinel; original value ignored */
tt = mktime(&buf);
#else
/* year < 1902 or year > 2037 */
if ((buf.tm_year < 2) || (buf.tm_year > 137)) {
/* Issue #19748: On AIX, mktime() doesn't report overflow error for
* timestamp < -2^31 or timestamp > 2**31-1. */
#ifdef _AIX
/* bpo-19748: AIX mktime() valid range is 00:00:00 UTC, January 1, 1970
to 03:14:07 UTC, January 19, 2038. Thanks to the workaround below,
it is possible to support years in range [1902; 2037] */
if (tm.tm_year < 2 || tm.tm_year > 137) {
/* bpo-19748: On AIX, mktime() does not report overflow error
for timestamp < -2^31 or timestamp > 2**31-1. */
PyErr_SetString(PyExc_OverflowError,
"mktime argument out of range");
return NULL;
}
year = buf.tm_year;
/* year < 1970 - adjust buf.tm_year into legal range */
while (buf.tm_year < 70) {
buf.tm_year += 4;
/* bpo-34373: AIX mktime() has an integer overflow for years in range
[1902; 1969]. Workaround the issue by using a year greater or equal than
1970 (tm_year >= 70): mktime() behaves correctly in that case
(ex: properly report errors). tm_year and tm_wday are adjusted after
mktime() call. */
int orig_tm_year = tm.tm_year;
int delta_days = 0;
while (tm.tm_year < 70) {
/* Use 4 years to account properly leap years */
tm.tm_year += 4;
delta_days -= (366 + (365 * 3));
}
buf.tm_wday = -1;
clk = mktime(&buf);
buf.tm_year = year;
if ((buf.tm_wday != -1) && delta_days)
buf.tm_wday = (buf.tm_wday + delta_days) % 7;
tt = clk + (delta_days * (24 * 3600));
#endif
tm.tm_wday = -1; /* sentinel; original value ignored */
tt = mktime(&tm);
/* Return value of -1 does not necessarily mean an error, but tm_wday
* cannot remain set to -1 if mktime succeeded. */
if (tt == (time_t)(-1)
/* Return value of -1 does not necessarily mean an error, but
* tm_wday cannot remain set to -1 if mktime succeeded. */
&& buf.tm_wday == -1)
&& tm.tm_wday == -1)
{
PyErr_SetString(PyExc_OverflowError,
"mktime argument out of range");
return NULL;
}
#ifdef _AIX
if (delta_days != 0) {
tm.tm_year = orig_tm_year;
if (tm.tm_wday != -1) {
tm.tm_wday = (tm.tm_wday + delta_days) % 7;
}
tt += delta_days * (24 * 3600);
}
#endif
return PyFloat_FromDouble((double)tt);
}

View file

@ -1062,26 +1062,23 @@ _PyTime_localtime(time_t t, struct tm *tm)
}
return 0;
#else /* !MS_WINDOWS */
#ifdef _AIX
/* AIX does not return NULL on an error
so test ranges - asif!
(1902-01-01, -2145916800.0)
(2038-01-01, 2145916800.0) */
if (abs(t) > (time_t) 2145916800) {
#ifdef EINVAL
/* bpo-34373: AIX does not return NULL if t is too small or too large */
if (t < -2145916800 /* 1902-01-01 */
|| t > 2145916800 /* 2038-01-01 */) {
errno = EINVAL;
#endif
PyErr_SetString(PyExc_OverflowError,
"ctime argument out of range");
"localtime argument out of range");
return -1;
}
#endif
errno = 0;
if (localtime_r(&t, tm) == NULL) {
#ifdef EINVAL
if (errno == 0) {
errno = EINVAL;
}
#endif
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}