24
24
#include "_datetime.h"
25
25
#include "datetime_strings.h"
26
26
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
+ */
28
35
#if defined(NPY_MINGW_USE_CUSTOM_MSVCR )
29
36
typedef __time64_t NPY_TIME_T ;
30
37
#else
34
41
/*
35
42
* Wraps `localtime` functionality for multiple platforms. This
36
43
* 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.
37
47
*
38
48
* 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
39
73
*/
40
74
static int
41
75
get_localtime (NPY_TIME_T * ts , struct tm * tms )
@@ -154,7 +188,9 @@ get_mktime(struct tm *tms)
154
188
155
189
/*
156
190
* 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.
158
194
*
159
195
* Returns 0 on success, -1 on failure.
160
196
*/
@@ -169,17 +205,23 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
169
205
/* Make a copy of the input 'dts' to modify */
170
206
* out_dts_local = * dts_utc ;
171
207
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
+ */
173
215
if (sizeof (NPY_TIME_T ) == 4 && out_dts_local -> year >= 2038 ) {
174
216
if (is_leapyear (out_dts_local -> year )) {
175
217
/* 2036 is a leap year */
176
218
year_correction = out_dts_local -> year - 2036 ;
177
- out_dts_local -> year -= year_correction ;
219
+ out_dts_local -> year -= year_correction ; /* = 2036 */
178
220
}
179
221
else {
180
222
/* 2037 is not a leap year */
181
223
year_correction = out_dts_local -> year - 2037 ;
182
- out_dts_local -> year -= year_correction ;
224
+ out_dts_local -> year -= year_correction ; /* = 2037 */
183
225
}
184
226
}
185
227
@@ -195,6 +237,7 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
195
237
196
238
/* localtime converts a 'time_t' into a local 'struct tm' */
197
239
if (get_localtime (& rawtime , & tm_ ) < 0 ) {
240
+ /* This should only fail if year < 1970 on some platforms. */
198
241
return -1 ;
199
242
}
200
243
@@ -213,7 +256,7 @@ convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
213
256
214
257
* out_timezone_offset = localrawtime - rawtime ;
215
258
216
- /* Reapply the year 2038 year correction HACK */
259
+ /* Reapply the year 2038 year correction */
217
260
out_dts_local -> year += year_correction ;
218
261
219
262
return 0 ;
@@ -233,17 +276,23 @@ convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc,
233
276
/* Make a copy of the input 'dts' to modify */
234
277
* out_dts_utc = * dts_local ;
235
278
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
+ */
237
286
if (sizeof (NPY_TIME_T ) == 4 && out_dts_utc -> year >= 2038 ) {
238
287
if (is_leapyear (out_dts_utc -> year )) {
239
288
/* 2036 is a leap year */
240
289
year_correction = out_dts_utc -> year - 2036 ;
241
- out_dts_utc -> year -= year_correction ;
290
+ out_dts_utc -> year -= year_correction ; /* = 2036 */
242
291
}
243
292
else {
244
293
/* 2037 is not a leap year */
245
294
year_correction = out_dts_utc -> year - 2037 ;
246
- out_dts_utc -> year -= year_correction ;
295
+ out_dts_utc -> year -= year_correction ; /* = 2037 */
247
296
}
248
297
}
249
298
@@ -286,7 +335,7 @@ convert_datetimestruct_local_to_utc(npy_datetimestruct *out_dts_utc,
286
335
out_dts_utc -> year = tm_ .tm_year + 1900 ;
287
336
}
288
337
289
- /* Reapply the year 2038 year correction HACK */
338
+ /* Reapply the year 2038 year correction */
290
339
out_dts_utc -> year += year_correction ;
291
340
292
341
return 0 ;
@@ -1053,7 +1102,8 @@ make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, int outlen,
1053
1102
/*
1054
1103
* Only do local time within a reasonable year range. The years
1055
1104
* 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
1057
1107
* restriction is applied to all platforms.
1058
1108
*
1059
1109
* Note that this only affects how the datetime becomes a string.
0 commit comments