Skip to content

Commit 8709490

Browse files
authored
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.
1 parent 8abc3f4 commit 8709490

File tree

3 files changed

+45
-39
lines changed

3 files changed

+45
-39
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :func:`time.mktime` error handling on AIX for year before 1970.

Modules/timemodule.c

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -990,60 +990,68 @@ not present, current time as returned by localtime() is used.");
990990

991991
#ifdef HAVE_MKTIME
992992
static PyObject *
993-
time_mktime(PyObject *self, PyObject *tup)
993+
time_mktime(PyObject *self, PyObject *tm_tuple)
994994
{
995-
struct tm buf;
995+
struct tm tm;
996996
time_t tt;
997-
#ifdef _AIX
998-
time_t clk;
999-
int year = buf.tm_year;
1000-
int delta_days = 0;
1001-
#endif
1002997

1003-
if (!gettmarg(tup, &buf,
998+
if (!gettmarg(tm_tuple, &tm,
1004999
"iiiiiiiii;mktime(): illegal time tuple argument"))
10051000
{
10061001
return NULL;
10071002
}
1008-
#ifndef _AIX
1009-
buf.tm_wday = -1; /* sentinel; original value ignored */
1010-
tt = mktime(&buf);
1011-
#else
1012-
/* year < 1902 or year > 2037 */
1013-
if ((buf.tm_year < 2) || (buf.tm_year > 137)) {
1014-
/* Issue #19748: On AIX, mktime() doesn't report overflow error for
1015-
* timestamp < -2^31 or timestamp > 2**31-1. */
1003+
1004+
#ifdef _AIX
1005+
/* bpo-19748: AIX mktime() valid range is 00:00:00 UTC, January 1, 1970
1006+
to 03:14:07 UTC, January 19, 2038. Thanks to the workaround below,
1007+
it is possible to support years in range [1902; 2037] */
1008+
if (tm.tm_year < 2 || tm.tm_year > 137) {
1009+
/* bpo-19748: On AIX, mktime() does not report overflow error
1010+
for timestamp < -2^31 or timestamp > 2**31-1. */
10161011
PyErr_SetString(PyExc_OverflowError,
10171012
"mktime argument out of range");
10181013
return NULL;
10191014
}
1020-
year = buf.tm_year;
1021-
/* year < 1970 - adjust buf.tm_year into legal range */
1022-
while (buf.tm_year < 70) {
1023-
buf.tm_year += 4;
1015+
1016+
/* bpo-34373: AIX mktime() has an integer overflow for years in range
1017+
[1902; 1969]. Workaround the issue by using a year greater or equal than
1018+
1970 (tm_year >= 70): mktime() behaves correctly in that case
1019+
(ex: properly report errors). tm_year and tm_wday are adjusted after
1020+
mktime() call. */
1021+
int orig_tm_year = tm.tm_year;
1022+
int delta_days = 0;
1023+
while (tm.tm_year < 70) {
1024+
/* Use 4 years to account properly leap years */
1025+
tm.tm_year += 4;
10241026
delta_days -= (366 + (365 * 3));
10251027
}
1028+
#endif
10261029

1027-
buf.tm_wday = -1;
1028-
clk = mktime(&buf);
1029-
buf.tm_year = year;
1030-
1031-
if ((buf.tm_wday != -1) && delta_days)
1032-
buf.tm_wday = (buf.tm_wday + delta_days) % 7;
1030+
tm.tm_wday = -1; /* sentinel; original value ignored */
1031+
tt = mktime(&tm);
10331032

1034-
tt = clk + (delta_days * (24 * 3600));
1035-
#endif
10361033
/* Return value of -1 does not necessarily mean an error, but tm_wday
10371034
* cannot remain set to -1 if mktime succeeded. */
10381035
if (tt == (time_t)(-1)
10391036
/* Return value of -1 does not necessarily mean an error, but
10401037
* tm_wday cannot remain set to -1 if mktime succeeded. */
1041-
&& buf.tm_wday == -1)
1038+
&& tm.tm_wday == -1)
10421039
{
10431040
PyErr_SetString(PyExc_OverflowError,
10441041
"mktime argument out of range");
10451042
return NULL;
10461043
}
1044+
1045+
#ifdef _AIX
1046+
if (delta_days != 0) {
1047+
tm.tm_year = orig_tm_year;
1048+
if (tm.tm_wday != -1) {
1049+
tm.tm_wday = (tm.tm_wday + delta_days) % 7;
1050+
}
1051+
tt += delta_days * (24 * 3600);
1052+
}
1053+
#endif
1054+
10471055
return PyFloat_FromDouble((double)tt);
10481056
}
10491057

Python/pytime.c

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,26 +1062,23 @@ _PyTime_localtime(time_t t, struct tm *tm)
10621062
}
10631063
return 0;
10641064
#else /* !MS_WINDOWS */
1065+
10651066
#ifdef _AIX
1066-
/* AIX does not return NULL on an error
1067-
so test ranges - asif!
1068-
(1902-01-01, -2145916800.0)
1069-
(2038-01-01, 2145916800.0) */
1070-
if (abs(t) > (time_t) 2145916800) {
1071-
#ifdef EINVAL
1067+
/* bpo-34373: AIX does not return NULL if t is too small or too large */
1068+
if (t < -2145916800 /* 1902-01-01 */
1069+
|| t > 2145916800 /* 2038-01-01 */) {
10721070
errno = EINVAL;
1073-
#endif
10741071
PyErr_SetString(PyExc_OverflowError,
1075-
"ctime argument out of range");
1072+
"localtime argument out of range");
10761073
return -1;
10771074
}
10781075
#endif
1076+
1077+
errno = 0;
10791078
if (localtime_r(&t, tm) == NULL) {
1080-
#ifdef EINVAL
10811079
if (errno == 0) {
10821080
errno = EINVAL;
10831081
}
1084-
#endif
10851082
PyErr_SetFromErrno(PyExc_OSError);
10861083
return -1;
10871084
}

0 commit comments

Comments
 (0)