diff options
author | Mark Wiebe <mwwiebe@gmail.com> | 2011-09-30 14:55:58 -0700 |
---|---|---|
committer | Charles Harris <charlesr.harris@gmail.com> | 2011-10-01 09:43:10 -0600 |
commit | b1e5fa05ad4803856ec3272a900707eaf4a7f92e (patch) | |
tree | b739b4a819bb3e120a9ed105abe7484255b90266 /numpy | |
parent | 6a3ca96df7179e745c76dc09a67bf37af3d818ce (diff) | |
download | numpy-b1e5fa05ad4803856ec3272a900707eaf4a7f92e.tar.gz |
BUG: Attempt to fix the Y2038 problem parsing dates >= 2038
Also incorporated part of Han Genuit's pull request #23, to try
and reduce the difficulty dealing with the merge conflict that
will result from these concurrent changes
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/src/multiarray/datetime_strings.c | 237 |
1 files changed, 167 insertions, 70 deletions
diff --git a/numpy/core/src/multiarray/datetime_strings.c b/numpy/core/src/multiarray/datetime_strings.c index 692270f4b..a3964c000 100644 --- a/numpy/core/src/multiarray/datetime_strings.c +++ b/numpy/core/src/multiarray/datetime_strings.c @@ -24,6 +24,166 @@ #include "_datetime.h" #include "datetime_strings.h" +/* Platform-specific time_t typedef */ +typedef time_t NPY_TIME_T; + +/* + * Wraps `localtime` functionality for multiple platforms. This + * converts a time value to a time structure in the local timezone. + * + * Returns 0 on success, -1 on failure. + */ +static int +get_localtime(NPY_TIME_T *ts, struct tm *tms) +{ + char *func_name = "<unknown>"; +#if defined(_WIN32) + #if defined(_MSC_VER) && (_MSC_VER >= 1400) + if (localtime_s(tms, ts) != 0) { + func_name = "localtime_s"; + goto fail; + } + #elif defined(__GNUC__) && defined(NPY_MINGW_USE_CUSTOM_MSVCR) + if (_localtime64_s(tms, ts) != 0) { + func_name = "_localtime64_s"; + goto fail; + } + #else + struct tm *tms_tmp; + tms_tmp = localtime(ts); + if (tms_tmp == NULL) { + func_name = "localtime"; + goto fail; + } + memcpy(tms, tms_tmp, sizeof(struct tm)); + #endif +#else + if (localtime_r(ts, tms) == NULL) { + func_name = "localtime_r"; + goto fail; + } +#endif + + return 0; + +fail: + PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert " + "to a local time", func_name); + return -1; +} + +/* + * Wraps `gmtime` functionality for multiple platforms. This + * converts a time value to a time structure in UTC. + * + * Returns 0 on success, -1 on failure. + */ +static int +get_gmtime(NPY_TIME_T *ts, struct tm *tms) +{ + char *func_name = "<unknown>"; +#if defined(_WIN32) + #if defined(_MSC_VER) && (_MSC_VER >= 1400) + if (gmtime_s(tms, ts) != 0) { + func_name = "gmtime_s"; + goto fail; + } + #elif defined(__GNUC__) && defined(NPY_MINGW_USE_CUSTOM_MSVCR) + if (_gmtime64_s(tms, ts) != 0) { + func_name = "_gmtime64_s"; + goto fail; + } + #else + struct tm *tms_tmp; + tms_tmp = gmtime(ts); + if (tms_tmp == NULL) { + func_name = "gmtime"; + goto fail; + } + memcpy(tms, tms_tmp, sizeof(struct tm)); + #endif +#else + if (gmtime_r(ts, tms) == NULL) { + func_name = "gmtime_r"; + goto fail; + } +#endif + + return 0; + +fail: + PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert " + "to a UTC time", func_name); + return -1; +} + +/* + * Converts a datetimestruct in UTC to a datetimestruct in local time, + * also returning the timezone offset applied. + * + * Returns 0 on success, -1 on failure. + */ +static int +convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local, + const npy_datetimestruct *dts_utc, int *out_timezone_offset) +{ + NPY_TIME_T rawtime = 0, localrawtime; + struct tm tm_; + npy_int64 year_correction = 0; + + /* Make a copy of the input 'dts' to modify */ + *out_dts_local = *dts_utc; + + /* HACK: Use a year < 2038 for later years for small time_t */ + if (sizeof(NPY_TIME_T) == 4 && out_dts_local->year >= 2038) { + if (is_leapyear(out_dts_local->year)) { + /* 2036 is a leap year */ + year_correction = out_dts_local->year - 2036; + out_dts_local->year -= year_correction; + } + else { + /* 2037 is not a leap year */ + year_correction = out_dts_local->year - 2037; + out_dts_local->year -= year_correction; + } + } + + /* + * Convert everything in 'dts' to a time_t, to minutes precision. + * This is POSIX time, which skips leap-seconds, but because + * we drop the seconds value from the npy_datetimestruct, everything + * is ok for this operation. + */ + rawtime = (time_t)get_datetimestruct_days(out_dts_local) * 24 * 60 * 60; + rawtime += dts_utc->hour * 60 * 60; + rawtime += dts_utc->min * 60; + + /* localtime converts a 'time_t' into a local 'struct tm' */ + if (get_localtime(&rawtime, &tm_) < 0) { + return -1; + } + + /* Copy back all the values except seconds */ + out_dts_local->min = tm_.tm_min; + out_dts_local->hour = tm_.tm_hour; + out_dts_local->day = tm_.tm_mday; + out_dts_local->month = tm_.tm_mon + 1; + out_dts_local->year = tm_.tm_year + 1900; + + /* Extract the timezone offset that was applied */ + rawtime /= 60; + localrawtime = (time_t)get_datetimestruct_days(out_dts_local) * 24 * 60; + localrawtime += out_dts_local->hour * 60; + localrawtime += out_dts_local->min; + + *out_timezone_offset = localrawtime - rawtime; + + /* Reapply the year 2038 year correction HACK */ + out_dts_local->year += year_correction; + + return 0; +} + /* * Parses (almost) standard ISO 8601 date strings. The differences are: * @@ -127,24 +287,13 @@ parse_iso_8601_datetime(char *str, int len, tolower(str[2]) == 'd' && tolower(str[3]) == 'a' && tolower(str[4]) == 'y') { - time_t rawtime = 0; + NPY_TIME_T rawtime = 0; struct tm tm_; time(&rawtime); -#if defined(_WIN32) - if (localtime_s(&tm_, &rawtime) != 0) { - PyErr_SetString(PyExc_OSError, "Failed to obtain local time " - "from localtime_s"); + if (get_localtime(&rawtime, &tm_) < 0) { return -1; } -#else - /* Other platforms may require something else */ - if (localtime_r(&rawtime, &tm_) == NULL) { - PyErr_SetString(PyExc_OSError, "Failed obtain local time " - "from localtime_r"); - return -1; - } -#endif out->year = tm_.tm_year + 1900; out->month = tm_.tm_mon + 1; out->day = tm_.tm_mday; @@ -182,7 +331,7 @@ parse_iso_8601_datetime(char *str, int len, if (len == 3 && tolower(str[0]) == 'n' && tolower(str[1]) == 'o' && tolower(str[2]) == 'w') { - time_t rawtime = 0; + NPY_TIME_T rawtime = 0; PyArray_DatetimeMetaData meta; time(&rawtime); @@ -512,7 +661,7 @@ parse_timezone: * Only do this timezone adjustment for recent and future years. */ if (out->year > 1900 && out->year < 10000) { - time_t rawtime = 0; + NPY_TIME_T rawtime = 0; struct tm tm_; tm_.tm_sec = out->sec; @@ -532,20 +681,9 @@ parse_timezone: } /* gmtime converts a 'time_t' into a UTC 'struct tm' */ -#if defined(_WIN32) - if (gmtime_s(&tm_, &rawtime) != 0) { - PyErr_SetString(PyExc_OSError, "Failed to use gmtime_s to " - "get a UTC time"); - goto error; - } -#else - /* Other platforms may require something else */ - if (gmtime_r(&rawtime, &tm_) == NULL) { - PyErr_SetString(PyExc_OSError, "Failed to use gmtime_r to " - "get a UTC time"); + if (get_gmtime(&rawtime, &tm_) < 0) { goto error; } -#endif out->sec = tm_.tm_sec; out->min = tm_.tm_min; out->hour = tm_.tm_hour; @@ -870,51 +1008,10 @@ make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen, /* Use the C API to convert from UTC to local time */ if (local && tzoffset == -1) { - time_t rawtime = 0, localrawtime; - struct tm tm_; - - /* - * Convert everything in 'dts' to a time_t, to minutes precision. - * This is POSIX time, which skips leap-seconds, but because - * we drop the seconds value from the npy_datetimestruct, everything - * is ok for this operation. - */ - rawtime = (time_t)get_datetimestruct_days(dts) * 24 * 60 * 60; - rawtime += dts->hour * 60 * 60; - rawtime += dts->min * 60; - - /* localtime converts a 'time_t' into a local 'struct tm' */ -#if defined(_WIN32) - if (localtime_s(&tm_, &rawtime) != 0) { - PyErr_SetString(PyExc_OSError, "Failed to use localtime_s to " - "get a local time"); - return -1; - } -#else - /* Other platforms may require something else */ - if (localtime_r(&rawtime, &tm_) == NULL) { - PyErr_SetString(PyExc_OSError, "Failed to use localtime_r to " - "get a local time"); + if (convert_datetimestruct_utc_to_local(&dts_local, dts, + &timezone_offset) < 0) { return -1; } -#endif - /* Make a copy of the npy_datetimestruct we can modify */ - dts_local = *dts; - - /* Copy back all the values except seconds */ - dts_local.min = tm_.tm_min; - dts_local.hour = tm_.tm_hour; - dts_local.day = tm_.tm_mday; - dts_local.month = tm_.tm_mon + 1; - dts_local.year = tm_.tm_year + 1900; - - /* Extract the timezone offset that was applied */ - rawtime /= 60; - localrawtime = (time_t)get_datetimestruct_days(&dts_local) * 24 * 60; - localrawtime += dts_local.hour * 60; - localrawtime += dts_local.min; - - timezone_offset = localrawtime - rawtime; /* Set dts to point to our local time instead of the UTC time */ dts = &dts_local; |