Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 77bcfcb

Browse files
committed
Merge pull request #2861 from certik/backport2858
Backport #2858
2 parents 7f3aeac + bae7330 commit 77bcfcb

File tree

1 file changed

+61
-11
lines changed

1 file changed

+61
-11
lines changed

numpy/core/src/multiarray/datetime_strings.c

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424
#include "_datetime.h"
2525
#include "datetime_strings.h"
2626

27-
/* Platform-specific time_t typedef */
27+
/*
28+
* Platform-specific time_t typedef. Some platforms use 32 bit, some use 64 bit
29+
* and we just use the default with the exception of mingw, where we must use
30+
* 64 bit because MSVCRT version 9 does not have the (32 bit) localtime()
31+
* symbol, so we need to use the 64 bit version [1].
32+
*
33+
* [1] http://thread.gmane.org/gmane.comp.gnu.mingw.user/27011
34+
*/
2835
#if defined(NPY_MINGW_USE_CUSTOM_MSVCR)
2936
typedef __time64_t NPY_TIME_T;
3037
#else
@@ -34,8 +41,35 @@
3441
/*
3542
* Wraps `localtime` functionality for multiple platforms. This
3643
* converts a time value to a time structure in the local timezone.
44+
* If size(NPY_TIME_T) == 4, then years must be between 1970 and 2038. If
45+
* size(NPY_TIME_T) == 8, then years must be later than 1970. If the years are
46+
* not in this range, then get_localtime() will fail on some platforms.
3747
*
3848
* Returns 0 on success, -1 on failure.
49+
*
50+
* Notes:
51+
* 1) If NPY_TIME_T is 32 bit (i.e. sizeof(NPY_TIME_T) == 4), then the
52+
* maximum year it can represent is 2038 (see [1] for more details). Trying
53+
* to use a higher date like 2041 in the 32 bit "ts" variable below will
54+
* typically result in "ts" being a negative number (corresponding roughly
55+
* to a year ~ 1905). If NPY_TIME_T is 64 bit, then there is no such
56+
* problem in practice.
57+
* 2) If the "ts" argument to localtime() is negative, it represents
58+
* years < 1970 both for 32 and 64 bits (for 32 bits the earliest year it can
59+
* represent is 1901, while 64 bits can represent much earlier years).
60+
* 3) On Linux, localtime() works for negative "ts". On Windows and in Wine,
61+
* localtime() as well as the localtime_s() and _localtime64_s() functions
62+
* will fail for any negative "ts" and return a nonzero exit number
63+
* (localtime_s, _localtime64_s) or NULL (localtime). This behavior is the
64+
* same for both 32 and 64 bits.
65+
*
66+
* From this it follows that get_localtime() is only guaranteed to work
67+
* correctly on all platforms for years between 1970 and 2038 for 32bit
68+
* NPY_TIME_T and years higher than 1970 for 64bit NPY_TIME_T. For
69+
* multiplatform code, get_localtime() should never be used outside of this
70+
* range.
71+
*
72+
* [1] http://en.wikipedia.org/wiki/Year_2038_problem
3973
*/
4074
static int
4175
get_localtime(NPY_TIME_T *ts, struct tm *tms)
@@ -154,7 +188,9 @@ get_mktime(struct tm *tms)
154188

155189
/*
156190
* Converts a datetimestruct in UTC to a datetimestruct in local time,
157-
* also returning the timezone offset applied.
191+
* also returning the timezone offset applied. This function works for any year
192+
* > 1970 on all platforms and both 32 and 64 bits. If the year < 1970, then it
193+
* will fail on some platforms.
158194
*
159195
* Returns 0 on success, -1 on failure.
160196
*/
@@ -169,17 +205,23 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
169205
/* Make a copy of the input 'dts' to modify */
170206
*out_dts_local = *dts_utc;
171207

172-
/* HACK: Use a year < 2038 for later years for small time_t */
208+
/*
209+
* For 32 bit NPY_TIME_T, the get_localtime() function does not work for
210+
* years later than 2038, see the comments above get_localtime(). So if the
211+
* year >= 2038, we instead call get_localtime() for the year 2036 or 2037
212+
* (depending on the leap year) which must work and at the end we add the
213+
* 'year_correction' back.
214+
*/
173215
if (sizeof(NPY_TIME_T) == 4 && out_dts_local->year >= 2038) {
174216
if (is_leapyear(out_dts_local->year)) {
175217
/* 2036 is a leap year */
176218
year_correction = out_dts_local->year - 2036;
177-
out_dts_local->year -= year_correction;
219+
out_dts_local->year -= year_correction; /* = 2036 */
178220
}
179221
else {
180222
/* 2037 is not a leap year */
181223
year_correction = out_dts_local->year - 2037;
182-
out_dts_local->year -= year_correction;
224+
out_dts_local->year -= year_correction; /* = 2037 */
183225
}
184226
}
185227

@@ -195,6 +237,7 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
195237

196238
/* localtime converts a 'time_t' into a local 'struct tm' */
197239
if (get_localtime(&rawtime, &tm_) < 0) {
240+
/* This should only fail if year < 1970 on some platforms. */
198241
return -1;
199242
}
200243

@@ -213,7 +256,7 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
213256

214257
*out_timezone_offset = localrawtime - rawtime;
215258

216-
/* Reapply the year 2038 year correction HACK */
259+
/* Reapply the year 2038 year correction */
217260
out_dts_local->year += year_correction;
218261

219262
return 0;
@@ -233,17 +276,23 @@ convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc,
233276
/* Make a copy of the input 'dts' to modify */
234277
*out_dts_utc = *dts_local;
235278

236-
/* HACK: Use a year < 2038 for later years for small time_t */
279+
/*
280+
* For 32 bit NPY_TIME_T, the get_mktime()/get_gmtime() functions do not
281+
* work for years later than 2038. So if the year >= 2038, we instead call
282+
* get_mktime()/get_gmtime() for the year 2036 or 2037 (depending on the
283+
* leap year) which must work and at the end we add the 'year_correction'
284+
* back.
285+
*/
237286
if (sizeof(NPY_TIME_T) == 4 && out_dts_utc->year >= 2038) {
238287
if (is_leapyear(out_dts_utc->year)) {
239288
/* 2036 is a leap year */
240289
year_correction = out_dts_utc->year - 2036;
241-
out_dts_utc->year -= year_correction;
290+
out_dts_utc->year -= year_correction; /* = 2036 */
242291
}
243292
else {
244293
/* 2037 is not a leap year */
245294
year_correction = out_dts_utc->year - 2037;
246-
out_dts_utc->year -= year_correction;
295+
out_dts_utc->year -= year_correction; /* = 2037 */
247296
}
248297
}
249298

@@ -286,7 +335,7 @@ convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc,
286335
out_dts_utc->year = tm_.tm_year + 1900;
287336
}
288337

289-
/* Reapply the year 2038 year correction HACK */
338+
/* Reapply the year 2038 year correction */
290339
out_dts_utc->year += year_correction;
291340

292341
return 0;
@@ -1053,7 +1102,8 @@ make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen,
10531102
/*
10541103
* Only do local time within a reasonable year range. The years
10551104
* earlier than 1970 are not made local, because the Windows API
1056-
* raises an error when they are attempted. For consistency, this
1105+
* raises an error when they are attempted (see the comments above the
1106+
* get_localtime() function). For consistency, this
10571107
* restriction is applied to all platforms.
10581108
*
10591109
* Note that this only affects how the datetime becomes a string.

0 commit comments

Comments
 (0)