Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :func:`time.mktime` error handling on AIX for year before 1970.
66 changes: 37 additions & 29 deletions Modules/timemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
#endif

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;
tm.tm_wday = -1; /* sentinel; original value ignored */
tt = mktime(&tm);

tt = clk + (delta_days * (24 * 3600));
#endif
/* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of this chunk is unclear.
Probably need to comment how exactly AIX is faulty like in the others.

Copy link
Contributor

@aixtools aixtools Apr 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue AIX mktime() cannot handle on it's own - is that mktime() does not accept a tm structure when the value for tm_year is < 70.

The new version has an _AIX specific block before the call to mktime() (to add years to tm_year until it is >= 70 and remember this 'delta' in number of days per 4 years including leap-day.

Once adjusted (only when tm_year is < 70) the code is "normal". If mktime() fails, a failure is returned.

If mktime() was successful - and _AIX and delta_days != 0 then the structure adjusted by mktime() needs to be adjusted further (back in time):

  • the original year is restored
  • the tm_wday is adjusted according to delta_days
  • the number of seconds (tt) is adjusted according to delta_days.

Hope this helps.

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);
}

Expand Down
17 changes: 7 additions & 10 deletions Python/pytime.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down