1
|
|
/*
|
2
|
|
* This file implements string parsing and creation for NumPy datetime.
|
3
|
|
*
|
4
|
|
* Written by Mark Wiebe (mwwiebe@gmail.com)
|
5
|
|
* Copyright (c) 2011 by Enthought, Inc.
|
6
|
|
*
|
7
|
|
* See LICENSE.txt for the license.
|
8
|
|
*/
|
9
|
|
|
10
|
|
#define PY_SSIZE_T_CLEAN
|
11
|
|
#include <Python.h>
|
12
|
|
|
13
|
|
#include <time.h>
|
14
|
|
|
15
|
|
#define NPY_NO_DEPRECATED_API NPY_API_VERSION
|
16
|
|
#define _MULTIARRAYMODULE
|
17
|
|
#include <numpy/arrayobject.h>
|
18
|
|
|
19
|
|
#include "npy_config.h"
|
20
|
|
#include "npy_pycompat.h"
|
21
|
|
|
22
|
|
#include "numpy/arrayscalars.h"
|
23
|
|
#include "convert_datatype.h"
|
24
|
|
#include "_datetime.h"
|
25
|
|
#include "datetime_strings.h"
|
26
|
|
|
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
|
|
*/
|
35
|
|
#if defined(NPY_MINGW_USE_CUSTOM_MSVCR)
|
36
|
|
typedef __time64_t NPY_TIME_T;
|
37
|
|
#else
|
38
|
|
typedef time_t NPY_TIME_T;
|
39
|
|
#endif
|
40
|
|
|
41
|
|
/*
|
42
|
|
* Wraps `localtime` functionality for multiple platforms. This
|
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.
|
47
|
|
*
|
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] https://en.wikipedia.org/wiki/Year_2038_problem
|
73
|
|
*/
|
74
|
|
static int
|
75
|
1
|
get_localtime(NPY_TIME_T *ts, struct tm *tms)
|
76
|
|
{
|
77
|
1
|
char *func_name = "<unknown>";
|
78
|
|
#if defined(_WIN32)
|
79
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
|
80
|
|
if (localtime_s(tms, ts) != 0) {
|
81
|
|
func_name = "localtime_s";
|
82
|
|
goto fail;
|
83
|
|
}
|
84
|
|
#elif defined(NPY_MINGW_USE_CUSTOM_MSVCR)
|
85
|
|
if (_localtime64_s(tms, ts) != 0) {
|
86
|
|
func_name = "_localtime64_s";
|
87
|
|
goto fail;
|
88
|
|
}
|
89
|
|
#else
|
90
|
|
struct tm *tms_tmp;
|
91
|
|
tms_tmp = localtime(ts);
|
92
|
|
if (tms_tmp == NULL) {
|
93
|
|
func_name = "localtime";
|
94
|
|
goto fail;
|
95
|
|
}
|
96
|
|
memcpy(tms, tms_tmp, sizeof(struct tm));
|
97
|
|
#endif
|
98
|
|
#else
|
99
|
1
|
if (localtime_r(ts, tms) == NULL) {
|
100
|
0
|
func_name = "localtime_r";
|
101
|
|
goto fail;
|
102
|
|
}
|
103
|
|
#endif
|
104
|
|
|
105
|
|
return 0;
|
106
|
|
|
107
|
0
|
fail:
|
108
|
0
|
PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert "
|
109
|
|
"to a local time", func_name);
|
110
|
0
|
return -1;
|
111
|
|
}
|
112
|
|
|
113
|
|
/*
|
114
|
|
* Converts a datetimestruct in UTC to a datetimestruct in local time,
|
115
|
|
* also returning the timezone offset applied. This function works for any year
|
116
|
|
* > 1970 on all platforms and both 32 and 64 bits. If the year < 1970, then it
|
117
|
|
* will fail on some platforms.
|
118
|
|
*
|
119
|
|
* Returns 0 on success, -1 on failure.
|
120
|
|
*/
|
121
|
|
static int
|
122
|
1
|
convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
|
123
|
|
const npy_datetimestruct *dts_utc, int *out_timezone_offset)
|
124
|
|
{
|
125
|
1
|
NPY_TIME_T rawtime = 0, localrawtime;
|
126
|
|
struct tm tm_;
|
127
|
1
|
npy_int64 year_correction = 0;
|
128
|
|
|
129
|
|
/* Make a copy of the input 'dts' to modify */
|
130
|
1
|
*out_dts_local = *dts_utc;
|
131
|
|
|
132
|
|
/*
|
133
|
|
* For 32 bit NPY_TIME_T, the get_localtime() function does not work for
|
134
|
|
* years later than 2038, see the comments above get_localtime(). So if the
|
135
|
|
* year >= 2038, we instead call get_localtime() for the year 2036 or 2037
|
136
|
|
* (depending on the leap year) which must work and at the end we add the
|
137
|
|
* 'year_correction' back.
|
138
|
|
*/
|
139
|
|
if (sizeof(NPY_TIME_T) == 4 && out_dts_local->year >= 2038) {
|
140
|
|
if (is_leapyear(out_dts_local->year)) {
|
141
|
|
/* 2036 is a leap year */
|
142
|
|
year_correction = out_dts_local->year - 2036;
|
143
|
|
out_dts_local->year -= year_correction; /* = 2036 */
|
144
|
|
}
|
145
|
|
else {
|
146
|
|
/* 2037 is not a leap year */
|
147
|
|
year_correction = out_dts_local->year - 2037;
|
148
|
|
out_dts_local->year -= year_correction; /* = 2037 */
|
149
|
|
}
|
150
|
|
}
|
151
|
|
|
152
|
|
/*
|
153
|
|
* Convert everything in 'dts' to a time_t, to minutes precision.
|
154
|
|
* This is POSIX time, which skips leap-seconds, but because
|
155
|
|
* we drop the seconds value from the npy_datetimestruct, everything
|
156
|
|
* is ok for this operation.
|
157
|
|
*/
|
158
|
1
|
rawtime = (NPY_TIME_T)get_datetimestruct_days(out_dts_local) * 24 * 60 * 60;
|
159
|
1
|
rawtime += dts_utc->hour * 60 * 60;
|
160
|
1
|
rawtime += dts_utc->min * 60;
|
161
|
|
|
162
|
|
/* localtime converts a 'time_t' into a local 'struct tm' */
|
163
|
1
|
if (get_localtime(&rawtime, &tm_) < 0) {
|
164
|
|
/* This should only fail if year < 1970 on some platforms. */
|
165
|
|
return -1;
|
166
|
|
}
|
167
|
|
|
168
|
|
/* Copy back all the values except seconds */
|
169
|
1
|
out_dts_local->min = tm_.tm_min;
|
170
|
1
|
out_dts_local->hour = tm_.tm_hour;
|
171
|
1
|
out_dts_local->day = tm_.tm_mday;
|
172
|
1
|
out_dts_local->month = tm_.tm_mon + 1;
|
173
|
1
|
out_dts_local->year = tm_.tm_year + 1900;
|
174
|
|
|
175
|
|
/* Extract the timezone offset that was applied */
|
176
|
1
|
rawtime /= 60;
|
177
|
1
|
localrawtime = (NPY_TIME_T)get_datetimestruct_days(out_dts_local) * 24 * 60;
|
178
|
1
|
localrawtime += out_dts_local->hour * 60;
|
179
|
1
|
localrawtime += out_dts_local->min;
|
180
|
|
|
181
|
1
|
*out_timezone_offset = localrawtime - rawtime;
|
182
|
|
|
183
|
|
/* Reapply the year 2038 year correction */
|
184
|
1
|
out_dts_local->year += year_correction;
|
185
|
|
|
186
|
1
|
return 0;
|
187
|
|
}
|
188
|
|
|
189
|
|
/*
|
190
|
|
* Parses (almost) standard ISO 8601 date strings. The differences are:
|
191
|
|
*
|
192
|
|
* + The date "20100312" is parsed as the year 20100312, not as
|
193
|
|
* equivalent to "2010-03-12". The '-' in the dates are not optional.
|
194
|
|
* + Only seconds may have a decimal point, with up to 18 digits after it
|
195
|
|
* (maximum attoseconds precision).
|
196
|
|
* + Either a 'T' as in ISO 8601 or a ' ' may be used to separate
|
197
|
|
* the date and the time. Both are treated equivalently.
|
198
|
|
* + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats.
|
199
|
|
* + Doesn't handle leap seconds (seconds value has 60 in these cases).
|
200
|
|
* + Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow
|
201
|
|
* + Accepts special values "NaT" (not a time), "Today", (current
|
202
|
|
* day according to local time) and "Now" (current time in UTC).
|
203
|
|
*
|
204
|
|
* 'str' must be a NULL-terminated string, and 'len' must be its length.
|
205
|
|
* 'unit' should contain -1 if the unit is unknown, or the unit
|
206
|
|
* which will be used if it is.
|
207
|
|
* 'casting' controls how the detected unit from the string is allowed
|
208
|
|
* to be cast to the 'unit' parameter.
|
209
|
|
*
|
210
|
|
* 'out' gets filled with the parsed date-time.
|
211
|
|
* 'out_bestunit' gives a suggested unit based on the amount of
|
212
|
|
* resolution provided in the string, or -1 for NaT.
|
213
|
|
* 'out_special' gets set to 1 if the parsed time was 'today',
|
214
|
|
* 'now', or ''/'NaT'. For 'today', the unit recommended is
|
215
|
|
* 'D', for 'now', the unit recommended is 's', and for 'NaT'
|
216
|
|
* the unit recommended is 'Y'.
|
217
|
|
*
|
218
|
|
* Returns 0 on success, -1 on failure.
|
219
|
|
*/
|
220
|
|
NPY_NO_EXPORT int
|
221
|
1
|
parse_iso_8601_datetime(char const *str, Py_ssize_t len,
|
222
|
|
NPY_DATETIMEUNIT unit,
|
223
|
|
NPY_CASTING casting,
|
224
|
|
npy_datetimestruct *out,
|
225
|
|
NPY_DATETIMEUNIT *out_bestunit,
|
226
|
|
npy_bool *out_special)
|
227
|
|
{
|
228
|
1
|
int year_leap = 0;
|
229
|
|
int i, numdigits;
|
230
|
|
char const *substr;
|
231
|
|
Py_ssize_t sublen;
|
232
|
|
NPY_DATETIMEUNIT bestunit;
|
233
|
|
|
234
|
|
/* Initialize the output to all zeros */
|
235
|
1
|
memset(out, 0, sizeof(npy_datetimestruct));
|
236
|
1
|
out->month = 1;
|
237
|
1
|
out->day = 1;
|
238
|
|
|
239
|
|
/*
|
240
|
|
* Convert the empty string and case-variants of "NaT" to not-a-time.
|
241
|
|
* Tried to use PyOS_stricmp, but that function appears to be broken,
|
242
|
|
* not even matching the strcmp function signature as it should.
|
243
|
|
*/
|
244
|
1
|
if (len <= 0 || (len == 3 &&
|
245
|
1
|
tolower(str[0]) == 'n' &&
|
246
|
1
|
tolower(str[1]) == 'a' &&
|
247
|
1
|
tolower(str[2]) == 't')) {
|
248
|
1
|
out->year = NPY_DATETIME_NAT;
|
249
|
|
|
250
|
|
/*
|
251
|
|
* Indicate that this was a special value, and
|
252
|
|
* recommend generic units.
|
253
|
|
*/
|
254
|
1
|
if (out_bestunit != NULL) {
|
255
|
1
|
*out_bestunit = NPY_FR_GENERIC;
|
256
|
|
}
|
257
|
1
|
if (out_special != NULL) {
|
258
|
0
|
*out_special = 1;
|
259
|
|
}
|
260
|
|
|
261
|
|
return 0;
|
262
|
|
}
|
263
|
|
|
264
|
1
|
if (unit == NPY_FR_GENERIC) {
|
265
|
0
|
PyErr_SetString(PyExc_ValueError,
|
266
|
|
"Cannot create a NumPy datetime other than NaT "
|
267
|
|
"with generic units");
|
268
|
0
|
return -1;
|
269
|
|
}
|
270
|
|
|
271
|
|
/*
|
272
|
|
* The string "today" means take today's date in local time, and
|
273
|
|
* convert it to a date representation. This date representation, if
|
274
|
|
* forced into a time unit, will be at midnight UTC.
|
275
|
|
* This is perhaps a little weird, but done so that the
|
276
|
|
* 'datetime64[D]' type produces the date you expect, rather than
|
277
|
|
* switching to an adjacent day depending on the current time and your
|
278
|
|
* timezone.
|
279
|
|
*/
|
280
|
1
|
if (len == 5 && tolower(str[0]) == 't' &&
|
281
|
1
|
tolower(str[1]) == 'o' &&
|
282
|
1
|
tolower(str[2]) == 'd' &&
|
283
|
1
|
tolower(str[3]) == 'a' &&
|
284
|
1
|
tolower(str[4]) == 'y') {
|
285
|
1
|
NPY_TIME_T rawtime = 0;
|
286
|
|
struct tm tm_;
|
287
|
|
|
288
|
1
|
time(&rawtime);
|
289
|
1
|
if (get_localtime(&rawtime, &tm_) < 0) {
|
290
|
|
return -1;
|
291
|
|
}
|
292
|
1
|
out->year = tm_.tm_year + 1900;
|
293
|
1
|
out->month = tm_.tm_mon + 1;
|
294
|
1
|
out->day = tm_.tm_mday;
|
295
|
|
|
296
|
1
|
bestunit = NPY_FR_D;
|
297
|
|
|
298
|
|
/*
|
299
|
|
* Indicate that this was a special value, and
|
300
|
|
* is a date (unit 'D').
|
301
|
|
*/
|
302
|
1
|
if (out_bestunit != NULL) {
|
303
|
1
|
*out_bestunit = bestunit;
|
304
|
|
}
|
305
|
1
|
if (out_special != NULL) {
|
306
|
0
|
*out_special = 1;
|
307
|
|
}
|
308
|
|
|
309
|
|
/* Check the casting rule */
|
310
|
1
|
if (unit != NPY_FR_ERROR &&
|
311
|
1
|
!can_cast_datetime64_units(bestunit, unit, casting)) {
|
312
|
0
|
PyErr_Format(PyExc_TypeError, "Cannot parse \"%s\" as unit "
|
313
|
|
"'%s' using casting rule %s",
|
314
|
|
str, _datetime_strings[unit],
|
315
|
|
npy_casting_to_string(casting));
|
316
|
0
|
return -1;
|
317
|
|
}
|
318
|
|
|
319
|
|
return 0;
|
320
|
|
}
|
321
|
|
|
322
|
|
/* The string "now" resolves to the current UTC time */
|
323
|
1
|
if (len == 3 && tolower(str[0]) == 'n' &&
|
324
|
1
|
tolower(str[1]) == 'o' &&
|
325
|
1
|
tolower(str[2]) == 'w') {
|
326
|
1
|
NPY_TIME_T rawtime = 0;
|
327
|
|
PyArray_DatetimeMetaData meta;
|
328
|
|
|
329
|
1
|
time(&rawtime);
|
330
|
|
|
331
|
|
/* Set up a dummy metadata for the conversion */
|
332
|
1
|
meta.base = NPY_FR_s;
|
333
|
1
|
meta.num = 1;
|
334
|
|
|
335
|
1
|
bestunit = NPY_FR_s;
|
336
|
|
|
337
|
|
/*
|
338
|
|
* Indicate that this was a special value, and
|
339
|
|
* use 's' because the time() function has resolution
|
340
|
|
* seconds.
|
341
|
|
*/
|
342
|
1
|
if (out_bestunit != NULL) {
|
343
|
1
|
*out_bestunit = bestunit;
|
344
|
|
}
|
345
|
1
|
if (out_special != NULL) {
|
346
|
0
|
*out_special = 1;
|
347
|
|
}
|
348
|
|
|
349
|
|
/* Check the casting rule */
|
350
|
1
|
if (unit != NPY_FR_ERROR &&
|
351
|
1
|
!can_cast_datetime64_units(bestunit, unit, casting)) {
|
352
|
0
|
PyErr_Format(PyExc_TypeError, "Cannot parse \"%s\" as unit "
|
353
|
|
"'%s' using casting rule %s",
|
354
|
|
str, _datetime_strings[unit],
|
355
|
|
npy_casting_to_string(casting));
|
356
|
0
|
return -1;
|
357
|
|
}
|
358
|
|
|
359
|
1
|
return convert_datetime_to_datetimestruct(&meta, rawtime, out);
|
360
|
|
}
|
361
|
|
|
362
|
|
/* Anything else isn't a special value */
|
363
|
1
|
if (out_special != NULL) {
|
364
|
0
|
*out_special = 0;
|
365
|
|
}
|
366
|
|
|
367
|
1
|
substr = str;
|
368
|
1
|
sublen = len;
|
369
|
|
|
370
|
|
/* Skip leading whitespace */
|
371
|
1
|
while (sublen > 0 && isspace(*substr)) {
|
372
|
0
|
++substr;
|
373
|
0
|
--sublen;
|
374
|
|
}
|
375
|
|
|
376
|
|
/* Leading '-' sign for negative year */
|
377
|
1
|
if (*substr == '-' || *substr == '+') {
|
378
|
1
|
++substr;
|
379
|
1
|
--sublen;
|
380
|
|
}
|
381
|
|
|
382
|
1
|
if (sublen == 0) {
|
383
|
|
goto parse_error;
|
384
|
|
}
|
385
|
|
|
386
|
|
/* PARSE THE YEAR (digits until the '-' character) */
|
387
|
1
|
out->year = 0;
|
388
|
1
|
while (sublen > 0 && isdigit(*substr)) {
|
389
|
1
|
out->year = 10 * out->year + (*substr - '0');
|
390
|
1
|
++substr;
|
391
|
1
|
--sublen;
|
392
|
|
}
|
393
|
|
|
394
|
|
/* Negate the year if necessary */
|
395
|
1
|
if (str[0] == '-') {
|
396
|
1
|
out->year = -out->year;
|
397
|
|
}
|
398
|
|
/* Check whether it's a leap-year */
|
399
|
1
|
year_leap = is_leapyear(out->year);
|
400
|
|
|
401
|
|
/* Next character must be a '-' or the end of the string */
|
402
|
1
|
if (sublen == 0) {
|
403
|
|
bestunit = NPY_FR_Y;
|
404
|
|
goto finish;
|
405
|
|
}
|
406
|
1
|
else if (*substr == '-') {
|
407
|
1
|
++substr;
|
408
|
1
|
--sublen;
|
409
|
|
}
|
410
|
|
else {
|
411
|
|
goto parse_error;
|
412
|
|
}
|
413
|
|
|
414
|
|
/* Can't have a trailing '-' */
|
415
|
1
|
if (sublen == 0) {
|
416
|
|
goto parse_error;
|
417
|
|
}
|
418
|
|
|
419
|
|
/* PARSE THE MONTH (2 digits) */
|
420
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
421
|
1
|
out->month = 10 * (substr[0] - '0') + (substr[1] - '0');
|
422
|
|
|
423
|
1
|
if (out->month < 1 || out->month > 12) {
|
424
|
1
|
PyErr_Format(PyExc_ValueError,
|
425
|
|
"Month out of range in datetime string \"%s\"", str);
|
426
|
1
|
goto error;
|
427
|
|
}
|
428
|
1
|
substr += 2;
|
429
|
1
|
sublen -= 2;
|
430
|
|
}
|
431
|
|
else {
|
432
|
|
goto parse_error;
|
433
|
|
}
|
434
|
|
|
435
|
|
/* Next character must be a '-' or the end of the string */
|
436
|
1
|
if (sublen == 0) {
|
437
|
|
bestunit = NPY_FR_M;
|
438
|
|
goto finish;
|
439
|
|
}
|
440
|
1
|
else if (*substr == '-') {
|
441
|
1
|
++substr;
|
442
|
1
|
--sublen;
|
443
|
|
}
|
444
|
|
else {
|
445
|
|
goto parse_error;
|
446
|
|
}
|
447
|
|
|
448
|
|
/* Can't have a trailing '-' */
|
449
|
1
|
if (sublen == 0) {
|
450
|
|
goto parse_error;
|
451
|
|
}
|
452
|
|
|
453
|
|
/* PARSE THE DAY (2 digits) */
|
454
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
455
|
1
|
out->day = 10 * (substr[0] - '0') + (substr[1] - '0');
|
456
|
|
|
457
|
1
|
if (out->day < 1 ||
|
458
|
1
|
out->day > _days_per_month_table[year_leap][out->month-1]) {
|
459
|
1
|
PyErr_Format(PyExc_ValueError,
|
460
|
|
"Day out of range in datetime string \"%s\"", str);
|
461
|
1
|
goto error;
|
462
|
|
}
|
463
|
1
|
substr += 2;
|
464
|
1
|
sublen -= 2;
|
465
|
|
}
|
466
|
|
else {
|
467
|
|
goto parse_error;
|
468
|
|
}
|
469
|
|
|
470
|
|
/* Next character must be a 'T', ' ', or end of string */
|
471
|
1
|
if (sublen == 0) {
|
472
|
|
bestunit = NPY_FR_D;
|
473
|
|
goto finish;
|
474
|
|
}
|
475
|
1
|
else if (*substr != 'T' && *substr != ' ') {
|
476
|
|
goto parse_error;
|
477
|
|
}
|
478
|
|
else {
|
479
|
1
|
++substr;
|
480
|
1
|
--sublen;
|
481
|
|
}
|
482
|
|
|
483
|
|
/* PARSE THE HOURS (2 digits) */
|
484
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
485
|
1
|
out->hour = 10 * (substr[0] - '0') + (substr[1] - '0');
|
486
|
|
|
487
|
1
|
if (out->hour >= 24) {
|
488
|
1
|
PyErr_Format(PyExc_ValueError,
|
489
|
|
"Hours out of range in datetime string \"%s\"", str);
|
490
|
1
|
goto error;
|
491
|
|
}
|
492
|
1
|
substr += 2;
|
493
|
1
|
sublen -= 2;
|
494
|
|
}
|
495
|
|
else {
|
496
|
|
goto parse_error;
|
497
|
|
}
|
498
|
|
|
499
|
|
/* Next character must be a ':' or the end of the string */
|
500
|
1
|
if (sublen > 0 && *substr == ':') {
|
501
|
1
|
++substr;
|
502
|
1
|
--sublen;
|
503
|
|
}
|
504
|
|
else {
|
505
|
|
bestunit = NPY_FR_h;
|
506
|
|
goto parse_timezone;
|
507
|
|
}
|
508
|
|
|
509
|
|
/* Can't have a trailing ':' */
|
510
|
1
|
if (sublen == 0) {
|
511
|
|
goto parse_error;
|
512
|
|
}
|
513
|
|
|
514
|
|
/* PARSE THE MINUTES (2 digits) */
|
515
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
516
|
1
|
out->min = 10 * (substr[0] - '0') + (substr[1] - '0');
|
517
|
|
|
518
|
1
|
if (out->min >= 60) {
|
519
|
1
|
PyErr_Format(PyExc_ValueError,
|
520
|
|
"Minutes out of range in datetime string \"%s\"", str);
|
521
|
1
|
goto error;
|
522
|
|
}
|
523
|
1
|
substr += 2;
|
524
|
1
|
sublen -= 2;
|
525
|
|
}
|
526
|
|
else {
|
527
|
|
goto parse_error;
|
528
|
|
}
|
529
|
|
|
530
|
|
/* Next character must be a ':' or the end of the string */
|
531
|
1
|
if (sublen > 0 && *substr == ':') {
|
532
|
1
|
++substr;
|
533
|
1
|
--sublen;
|
534
|
|
}
|
535
|
|
else {
|
536
|
|
bestunit = NPY_FR_m;
|
537
|
|
goto parse_timezone;
|
538
|
|
}
|
539
|
|
|
540
|
|
/* Can't have a trailing ':' */
|
541
|
1
|
if (sublen == 0) {
|
542
|
|
goto parse_error;
|
543
|
|
}
|
544
|
|
|
545
|
|
/* PARSE THE SECONDS (2 digits) */
|
546
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
547
|
1
|
out->sec = 10 * (substr[0] - '0') + (substr[1] - '0');
|
548
|
|
|
549
|
1
|
if (out->sec >= 60) {
|
550
|
1
|
PyErr_Format(PyExc_ValueError,
|
551
|
|
"Seconds out of range in datetime string \"%s\"", str);
|
552
|
1
|
goto error;
|
553
|
|
}
|
554
|
1
|
substr += 2;
|
555
|
1
|
sublen -= 2;
|
556
|
|
}
|
557
|
|
else {
|
558
|
|
goto parse_error;
|
559
|
|
}
|
560
|
|
|
561
|
|
/* Next character may be a '.' indicating fractional seconds */
|
562
|
1
|
if (sublen > 0 && *substr == '.') {
|
563
|
1
|
++substr;
|
564
|
1
|
--sublen;
|
565
|
|
}
|
566
|
|
else {
|
567
|
|
bestunit = NPY_FR_s;
|
568
|
|
goto parse_timezone;
|
569
|
|
}
|
570
|
|
|
571
|
|
/* PARSE THE MICROSECONDS (0 to 6 digits) */
|
572
|
1
|
numdigits = 0;
|
573
|
1
|
for (i = 0; i < 6; ++i) {
|
574
|
1
|
out->us *= 10;
|
575
|
1
|
if (sublen > 0 && isdigit(*substr)) {
|
576
|
1
|
out->us += (*substr - '0');
|
577
|
1
|
++substr;
|
578
|
1
|
--sublen;
|
579
|
1
|
++numdigits;
|
580
|
|
}
|
581
|
|
}
|
582
|
|
|
583
|
1
|
if (sublen == 0 || !isdigit(*substr)) {
|
584
|
1
|
if (numdigits > 3) {
|
585
|
|
bestunit = NPY_FR_us;
|
586
|
|
}
|
587
|
|
else {
|
588
|
1
|
bestunit = NPY_FR_ms;
|
589
|
|
}
|
590
|
|
goto parse_timezone;
|
591
|
|
}
|
592
|
|
|
593
|
|
/* PARSE THE PICOSECONDS (0 to 6 digits) */
|
594
|
|
numdigits = 0;
|
595
|
1
|
for (i = 0; i < 6; ++i) {
|
596
|
1
|
out->ps *= 10;
|
597
|
1
|
if (sublen > 0 && isdigit(*substr)) {
|
598
|
1
|
out->ps += (*substr - '0');
|
599
|
1
|
++substr;
|
600
|
1
|
--sublen;
|
601
|
1
|
++numdigits;
|
602
|
|
}
|
603
|
|
}
|
604
|
|
|
605
|
1
|
if (sublen == 0 || !isdigit(*substr)) {
|
606
|
1
|
if (numdigits > 3) {
|
607
|
|
bestunit = NPY_FR_ps;
|
608
|
|
}
|
609
|
|
else {
|
610
|
1
|
bestunit = NPY_FR_ns;
|
611
|
|
}
|
612
|
|
goto parse_timezone;
|
613
|
|
}
|
614
|
|
|
615
|
|
/* PARSE THE ATTOSECONDS (0 to 6 digits) */
|
616
|
|
numdigits = 0;
|
617
|
1
|
for (i = 0; i < 6; ++i) {
|
618
|
1
|
out->as *= 10;
|
619
|
1
|
if (sublen > 0 && isdigit(*substr)) {
|
620
|
1
|
out->as += (*substr - '0');
|
621
|
1
|
++substr;
|
622
|
1
|
--sublen;
|
623
|
1
|
++numdigits;
|
624
|
|
}
|
625
|
|
}
|
626
|
|
|
627
|
1
|
if (numdigits > 3) {
|
628
|
|
bestunit = NPY_FR_as;
|
629
|
|
}
|
630
|
|
else {
|
631
|
1
|
bestunit = NPY_FR_fs;
|
632
|
|
}
|
633
|
|
|
634
|
1
|
parse_timezone:
|
635
|
1
|
if (sublen == 0) {
|
636
|
|
goto finish;
|
637
|
|
}
|
638
|
|
else {
|
639
|
|
/* 2016-01-14, 1.11 */
|
640
|
1
|
PyErr_Clear();
|
641
|
1
|
if (DEPRECATE(
|
642
|
|
"parsing timezone aware datetimes is deprecated; "
|
643
|
|
"this will raise an error in the future") < 0) {
|
644
|
|
return -1;
|
645
|
|
}
|
646
|
|
}
|
647
|
|
|
648
|
|
/* UTC specifier */
|
649
|
1
|
if (*substr == 'Z') {
|
650
|
1
|
if (sublen == 1) {
|
651
|
|
goto finish;
|
652
|
|
}
|
653
|
|
else {
|
654
|
0
|
++substr;
|
655
|
0
|
--sublen;
|
656
|
|
}
|
657
|
|
}
|
658
|
|
/* Time zone offset */
|
659
|
1
|
else if (*substr == '-' || *substr == '+') {
|
660
|
1
|
int offset_neg = 0, offset_hour = 0, offset_minute = 0;
|
661
|
|
|
662
|
1
|
if (*substr == '-') {
|
663
|
1
|
offset_neg = 1;
|
664
|
|
}
|
665
|
1
|
++substr;
|
666
|
1
|
--sublen;
|
667
|
|
|
668
|
|
/* The hours offset */
|
669
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
670
|
1
|
offset_hour = 10 * (substr[0] - '0') + (substr[1] - '0');
|
671
|
1
|
substr += 2;
|
672
|
1
|
sublen -= 2;
|
673
|
1
|
if (offset_hour >= 24) {
|
674
|
1
|
PyErr_Format(PyExc_ValueError,
|
675
|
|
"Timezone hours offset out of range "
|
676
|
|
"in datetime string \"%s\"", str);
|
677
|
1
|
goto error;
|
678
|
|
}
|
679
|
|
}
|
680
|
|
else {
|
681
|
|
goto parse_error;
|
682
|
|
}
|
683
|
|
|
684
|
|
/* The minutes offset is optional */
|
685
|
1
|
if (sublen > 0) {
|
686
|
|
/* Optional ':' */
|
687
|
1
|
if (*substr == ':') {
|
688
|
1
|
++substr;
|
689
|
1
|
--sublen;
|
690
|
|
}
|
691
|
|
|
692
|
|
/* The minutes offset (at the end of the string) */
|
693
|
1
|
if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
|
694
|
1
|
offset_minute = 10 * (substr[0] - '0') + (substr[1] - '0');
|
695
|
1
|
substr += 2;
|
696
|
1
|
sublen -= 2;
|
697
|
1
|
if (offset_minute >= 60) {
|
698
|
1
|
PyErr_Format(PyExc_ValueError,
|
699
|
|
"Timezone minutes offset out of range "
|
700
|
|
"in datetime string \"%s\"", str);
|
701
|
1
|
goto error;
|
702
|
|
}
|
703
|
|
}
|
704
|
|
else {
|
705
|
|
goto parse_error;
|
706
|
|
}
|
707
|
|
}
|
708
|
|
|
709
|
|
/* Apply the time zone offset */
|
710
|
1
|
if (offset_neg) {
|
711
|
1
|
offset_hour = -offset_hour;
|
712
|
1
|
offset_minute = -offset_minute;
|
713
|
|
}
|
714
|
1
|
add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute);
|
715
|
|
}
|
716
|
|
|
717
|
|
/* Skip trailing whitespace */
|
718
|
1
|
while (sublen > 0 && isspace(*substr)) {
|
719
|
0
|
++substr;
|
720
|
0
|
--sublen;
|
721
|
|
}
|
722
|
|
|
723
|
1
|
if (sublen != 0) {
|
724
|
|
goto parse_error;
|
725
|
|
}
|
726
|
|
|
727
|
1
|
finish:
|
728
|
1
|
if (out_bestunit != NULL) {
|
729
|
1
|
*out_bestunit = bestunit;
|
730
|
|
}
|
731
|
|
|
732
|
|
/* Check the casting rule */
|
733
|
1
|
if (unit != NPY_FR_ERROR &&
|
734
|
1
|
!can_cast_datetime64_units(bestunit, unit, casting)) {
|
735
|
0
|
PyErr_Format(PyExc_TypeError, "Cannot parse \"%s\" as unit "
|
736
|
|
"'%s' using casting rule %s",
|
737
|
|
str, _datetime_strings[unit],
|
738
|
|
npy_casting_to_string(casting));
|
739
|
0
|
return -1;
|
740
|
|
}
|
741
|
|
|
742
|
|
return 0;
|
743
|
|
|
744
|
1
|
parse_error:
|
745
|
1
|
PyErr_Format(PyExc_ValueError,
|
746
|
|
"Error parsing datetime string \"%s\" at position %zd",
|
747
|
|
str, substr - str);
|
748
|
1
|
return -1;
|
749
|
|
|
750
|
|
error:
|
751
|
|
return -1;
|
752
|
|
}
|
753
|
|
|
754
|
|
/*
|
755
|
|
* Provides a string length to use for converting datetime
|
756
|
|
* objects with the given local and unit settings.
|
757
|
|
*/
|
758
|
|
NPY_NO_EXPORT int
|
759
|
1
|
get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base)
|
760
|
|
{
|
761
|
1
|
int len = 0;
|
762
|
|
|
763
|
1
|
switch (base) {
|
764
|
|
case NPY_FR_ERROR:
|
765
|
|
/* If no unit is provided, return the maximum length */
|
766
|
|
return NPY_DATETIME_MAX_ISO8601_STRLEN;
|
767
|
0
|
case NPY_FR_GENERIC:
|
768
|
|
/* Generic units can only be used to represent NaT */
|
769
|
0
|
return 4;
|
770
|
1
|
case NPY_FR_as:
|
771
|
1
|
len += 3; /* "###" */
|
772
|
1
|
case NPY_FR_fs:
|
773
|
1
|
len += 3; /* "###" */
|
774
|
1
|
case NPY_FR_ps:
|
775
|
1
|
len += 3; /* "###" */
|
776
|
1
|
case NPY_FR_ns:
|
777
|
1
|
len += 3; /* "###" */
|
778
|
1
|
case NPY_FR_us:
|
779
|
1
|
len += 3; /* "###" */
|
780
|
1
|
case NPY_FR_ms:
|
781
|
1
|
len += 4; /* ".###" */
|
782
|
1
|
case NPY_FR_s:
|
783
|
1
|
len += 3; /* ":##" */
|
784
|
1
|
case NPY_FR_m:
|
785
|
1
|
len += 3; /* ":##" */
|
786
|
1
|
case NPY_FR_h:
|
787
|
1
|
len += 3; /* "T##" */
|
788
|
1
|
case NPY_FR_D:
|
789
|
|
case NPY_FR_W:
|
790
|
1
|
len += 3; /* "-##" */
|
791
|
1
|
case NPY_FR_M:
|
792
|
1
|
len += 3; /* "-##" */
|
793
|
1
|
case NPY_FR_Y:
|
794
|
1
|
len += 21; /* 64-bit year */
|
795
|
1
|
break;
|
796
|
|
}
|
797
|
|
|
798
|
1
|
if (base >= NPY_FR_h) {
|
799
|
1
|
if (local) {
|
800
|
1
|
len += 5; /* "+####" or "-####" */
|
801
|
|
}
|
802
|
|
else {
|
803
|
1
|
len += 1; /* "Z" */
|
804
|
|
}
|
805
|
|
}
|
806
|
|
|
807
|
1
|
len += 1; /* NULL terminator */
|
808
|
|
|
809
|
1
|
return len;
|
810
|
|
}
|
811
|
|
|
812
|
|
/*
|
813
|
|
* Finds the largest unit whose value is nonzero, and for which
|
814
|
|
* the remainder for the rest of the units is zero.
|
815
|
|
*/
|
816
|
|
static NPY_DATETIMEUNIT
|
817
|
1
|
lossless_unit_from_datetimestruct(npy_datetimestruct *dts)
|
818
|
|
{
|
819
|
1
|
if (dts->as % 1000 != 0) {
|
820
|
|
return NPY_FR_as;
|
821
|
|
}
|
822
|
1
|
else if (dts->as != 0) {
|
823
|
|
return NPY_FR_fs;
|
824
|
|
}
|
825
|
1
|
else if (dts->ps % 1000 != 0) {
|
826
|
|
return NPY_FR_ps;
|
827
|
|
}
|
828
|
1
|
else if (dts->ps != 0) {
|
829
|
|
return NPY_FR_ns;
|
830
|
|
}
|
831
|
1
|
else if (dts->us % 1000 != 0) {
|
832
|
|
return NPY_FR_us;
|
833
|
|
}
|
834
|
1
|
else if (dts->us != 0) {
|
835
|
|
return NPY_FR_ms;
|
836
|
|
}
|
837
|
1
|
else if (dts->sec != 0) {
|
838
|
|
return NPY_FR_s;
|
839
|
|
}
|
840
|
1
|
else if (dts->min != 0) {
|
841
|
|
return NPY_FR_m;
|
842
|
|
}
|
843
|
1
|
else if (dts->hour != 0) {
|
844
|
|
return NPY_FR_h;
|
845
|
|
}
|
846
|
1
|
else if (dts->day != 1) {
|
847
|
|
return NPY_FR_D;
|
848
|
|
}
|
849
|
1
|
else if (dts->month != 1) {
|
850
|
|
return NPY_FR_M;
|
851
|
|
}
|
852
|
|
else {
|
853
|
1
|
return NPY_FR_Y;
|
854
|
|
}
|
855
|
|
}
|
856
|
|
|
857
|
|
/*
|
858
|
|
* Converts an npy_datetimestruct to an (almost) ISO 8601
|
859
|
|
* NULL-terminated string. If the string fits in the space exactly,
|
860
|
|
* it leaves out the NULL terminator and returns success.
|
861
|
|
*
|
862
|
|
* The differences from ISO 8601 are the 'NaT' string, and
|
863
|
|
* the number of year digits is >= 4 instead of strictly 4.
|
864
|
|
*
|
865
|
|
* If 'local' is non-zero, it produces a string in local time with
|
866
|
|
* a +-#### timezone offset. If 'local' is zero and 'utc' is non-zero,
|
867
|
|
* produce a string ending with 'Z' to denote UTC. By default, no time
|
868
|
|
* zone information is attached.
|
869
|
|
*
|
870
|
|
* 'base' restricts the output to that unit. Set 'base' to
|
871
|
|
* -1 to auto-detect a base after which all the values are zero.
|
872
|
|
*
|
873
|
|
* 'tzoffset' is used if 'local' is enabled, and 'tzoffset' is
|
874
|
|
* set to a value other than -1. This is a manual override for
|
875
|
|
* the local time zone to use, as an offset in minutes.
|
876
|
|
*
|
877
|
|
* 'casting' controls whether data loss is allowed by truncating
|
878
|
|
* the data to a coarser unit. This interacts with 'local', slightly,
|
879
|
|
* in order to form a date unit string as a local time, the casting
|
880
|
|
* must be unsafe.
|
881
|
|
*
|
882
|
|
* Returns 0 on success, -1 on failure (for example if the output
|
883
|
|
* string was too short).
|
884
|
|
*/
|
885
|
|
NPY_NO_EXPORT int
|
886
|
1
|
make_iso_8601_datetime(npy_datetimestruct *dts, char *outstr, npy_intp outlen,
|
887
|
|
int local, int utc, NPY_DATETIMEUNIT base, int tzoffset,
|
888
|
|
NPY_CASTING casting)
|
889
|
|
{
|
890
|
|
npy_datetimestruct dts_local;
|
891
|
1
|
int timezone_offset = 0;
|
892
|
|
|
893
|
1
|
char *substr = outstr;
|
894
|
1
|
npy_intp sublen = outlen;
|
895
|
|
npy_intp tmplen;
|
896
|
|
|
897
|
|
/* Handle NaT, and treat a datetime with generic units as NaT */
|
898
|
1
|
if (dts->year == NPY_DATETIME_NAT || base == NPY_FR_GENERIC) {
|
899
|
1
|
if (outlen < 3) {
|
900
|
|
goto string_too_short;
|
901
|
|
}
|
902
|
1
|
outstr[0] = 'N';
|
903
|
1
|
outstr[1] = 'a';
|
904
|
1
|
outstr[2] = 'T';
|
905
|
1
|
if (outlen > 3) {
|
906
|
1
|
outstr[3] = '\0';
|
907
|
|
}
|
908
|
|
|
909
|
|
return 0;
|
910
|
|
}
|
911
|
|
|
912
|
|
/*
|
913
|
|
* Only do local time within a reasonable year range. The years
|
914
|
|
* earlier than 1970 are not made local, because the Windows API
|
915
|
|
* raises an error when they are attempted (see the comments above the
|
916
|
|
* get_localtime() function). For consistency, this
|
917
|
|
* restriction is applied to all platforms.
|
918
|
|
*
|
919
|
|
* Note that this only affects how the datetime becomes a string.
|
920
|
|
* The result is still completely unambiguous, it only means
|
921
|
|
* that datetimes outside this range will not include a time zone
|
922
|
|
* when they are printed.
|
923
|
|
*/
|
924
|
1
|
if ((dts->year < 1970 || dts->year >= 10000) && tzoffset == -1) {
|
925
|
1
|
local = 0;
|
926
|
|
}
|
927
|
|
|
928
|
|
/* Automatically detect a good unit */
|
929
|
1
|
if (base == NPY_FR_ERROR) {
|
930
|
1
|
base = lossless_unit_from_datetimestruct(dts);
|
931
|
|
/*
|
932
|
|
* If there's a timezone, use at least minutes precision,
|
933
|
|
* and never split up hours and minutes by default
|
934
|
|
*/
|
935
|
1
|
if ((base < NPY_FR_m && local) || base == NPY_FR_h) {
|
936
|
|
base = NPY_FR_m;
|
937
|
|
}
|
938
|
|
/* Don't split up dates by default */
|
939
|
1
|
else if (base < NPY_FR_D) {
|
940
|
1
|
base = NPY_FR_D;
|
941
|
|
}
|
942
|
|
}
|
943
|
|
/*
|
944
|
|
* Print weeks with the same precision as days.
|
945
|
|
*
|
946
|
|
* TODO: Could print weeks with YYYY-Www format if the week
|
947
|
|
* epoch is a Monday.
|
948
|
|
*/
|
949
|
1
|
else if (base == NPY_FR_W) {
|
950
|
1
|
base = NPY_FR_D;
|
951
|
|
}
|
952
|
|
|
953
|
|
/* Use the C API to convert from UTC to local time */
|
954
|
1
|
if (local && tzoffset == -1) {
|
955
|
1
|
if (convert_datetimestruct_utc_to_local(&dts_local, dts,
|
956
|
|
&timezone_offset) < 0) {
|
957
|
|
return -1;
|
958
|
|
}
|
959
|
|
|
960
|
|
/* Set dts to point to our local time instead of the UTC time */
|
961
|
|
dts = &dts_local;
|
962
|
|
}
|
963
|
|
/* Use the manually provided tzoffset */
|
964
|
1
|
else if (local) {
|
965
|
|
/* Make a copy of the npy_datetimestruct we can modify */
|
966
|
1
|
dts_local = *dts;
|
967
|
1
|
dts = &dts_local;
|
968
|
|
|
969
|
|
/* Set and apply the required timezone offset */
|
970
|
1
|
timezone_offset = tzoffset;
|
971
|
1
|
add_minutes_to_datetimestruct(dts, timezone_offset);
|
972
|
|
}
|
973
|
|
|
974
|
|
/*
|
975
|
|
* Now the datetimestruct data is in the final form for
|
976
|
|
* the string representation, so ensure that the data
|
977
|
|
* is being cast according to the casting rule.
|
978
|
|
*/
|
979
|
1
|
if (casting != NPY_UNSAFE_CASTING) {
|
980
|
|
/* Producing a date as a local time is always 'unsafe' */
|
981
|
1
|
if (base <= NPY_FR_D && local) {
|
982
|
1
|
PyErr_SetString(PyExc_TypeError, "Cannot create a local "
|
983
|
|
"timezone-based date string from a NumPy "
|
984
|
|
"datetime without forcing 'unsafe' casting");
|
985
|
1
|
return -1;
|
986
|
|
}
|
987
|
|
/* Only 'unsafe' and 'same_kind' allow data loss */
|
988
|
|
else {
|
989
|
|
NPY_DATETIMEUNIT unitprec;
|
990
|
|
|
991
|
1
|
unitprec = lossless_unit_from_datetimestruct(dts);
|
992
|
1
|
if (casting != NPY_SAME_KIND_CASTING && unitprec > base) {
|
993
|
0
|
PyErr_Format(PyExc_TypeError, "Cannot create a "
|
994
|
|
"string with unit precision '%s' "
|
995
|
|
"from the NumPy datetime, which has data at "
|
996
|
|
"unit precision '%s', "
|
997
|
|
"requires 'unsafe' or 'same_kind' casting",
|
998
|
|
_datetime_strings[base],
|
999
|
|
_datetime_strings[unitprec]);
|
1000
|
0
|
return -1;
|
1001
|
|
}
|
1002
|
|
}
|
1003
|
|
}
|
1004
|
|
|
1005
|
|
/* YEAR */
|
1006
|
|
/*
|
1007
|
|
* Can't use PyOS_snprintf, because it always produces a '\0'
|
1008
|
|
* character at the end, and NumPy string types are permitted
|
1009
|
|
* to have data all the way to the end of the buffer.
|
1010
|
|
*/
|
1011
|
|
#ifdef _WIN32
|
1012
|
|
tmplen = _snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year);
|
1013
|
|
#else
|
1014
|
1
|
tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year);
|
1015
|
|
#endif
|
1016
|
|
/* If it ran out of space or there isn't space for the NULL terminator */
|
1017
|
1
|
if (tmplen < 0 || tmplen > sublen) {
|
1018
|
|
goto string_too_short;
|
1019
|
|
}
|
1020
|
1
|
substr += tmplen;
|
1021
|
1
|
sublen -= tmplen;
|
1022
|
|
|
1023
|
|
/* Stop if the unit is years */
|
1024
|
1
|
if (base == NPY_FR_Y) {
|
1025
|
1
|
if (sublen > 0) {
|
1026
|
1
|
*substr = '\0';
|
1027
|
|
}
|
1028
|
|
return 0;
|
1029
|
|
}
|
1030
|
|
|
1031
|
|
/* MONTH */
|
1032
|
1
|
if (sublen < 1 ) {
|
1033
|
|
goto string_too_short;
|
1034
|
|
}
|
1035
|
1
|
substr[0] = '-';
|
1036
|
1
|
if (sublen < 2 ) {
|
1037
|
|
goto string_too_short;
|
1038
|
|
}
|
1039
|
1
|
substr[1] = (char)((dts->month / 10) + '0');
|
1040
|
1
|
if (sublen < 3 ) {
|
1041
|
|
goto string_too_short;
|
1042
|
|
}
|
1043
|
1
|
substr[2] = (char)((dts->month % 10) + '0');
|
1044
|
1
|
substr += 3;
|
1045
|
1
|
sublen -= 3;
|
1046
|
|
|
1047
|
|
/* Stop if the unit is months */
|
1048
|
1
|
if (base == NPY_FR_M) {
|
1049
|
1
|
if (sublen > 0) {
|
1050
|
1
|
*substr = '\0';
|
1051
|
|
}
|
1052
|
|
return 0;
|
1053
|
|
}
|
1054
|
|
|
1055
|
|
/* DAY */
|
1056
|
1
|
if (sublen < 1 ) {
|
1057
|
|
goto string_too_short;
|
1058
|
|
}
|
1059
|
1
|
substr[0] = '-';
|
1060
|
1
|
if (sublen < 2 ) {
|
1061
|
|
goto string_too_short;
|
1062
|
|
}
|
1063
|
1
|
substr[1] = (char)((dts->day / 10) + '0');
|
1064
|
1
|
if (sublen < 3 ) {
|
1065
|
|
goto string_too_short;
|
1066
|
|
}
|
1067
|
1
|
substr[2] = (char)((dts->day % 10) + '0');
|
1068
|
1
|
substr += 3;
|
1069
|
1
|
sublen -= 3;
|
1070
|
|
|
1071
|
|
/* Stop if the unit is days */
|
1072
|
1
|
if (base == NPY_FR_D) {
|
1073
|
1
|
if (sublen > 0) {
|
1074
|
1
|
*substr = '\0';
|
1075
|
|
}
|
1076
|
|
return 0;
|
1077
|
|
}
|
1078
|
|
|
1079
|
|
/* HOUR */
|
1080
|
1
|
if (sublen < 1 ) {
|
1081
|
|
goto string_too_short;
|
1082
|
|
}
|
1083
|
1
|
substr[0] = 'T';
|
1084
|
1
|
if (sublen < 2 ) {
|
1085
|
|
goto string_too_short;
|
1086
|
|
}
|
1087
|
1
|
substr[1] = (char)((dts->hour / 10) + '0');
|
1088
|
1
|
if (sublen < 3 ) {
|
1089
|
|
goto string_too_short;
|
1090
|
|
}
|
1091
|
1
|
substr[2] = (char)((dts->hour % 10) + '0');
|
1092
|
1
|
substr += 3;
|
1093
|
1
|
sublen -= 3;
|
1094
|
|
|
1095
|
|
/* Stop if the unit is hours */
|
1096
|
1
|
if (base == NPY_FR_h) {
|
1097
|
|
goto add_time_zone;
|
1098
|
|
}
|
1099
|
|
|
1100
|
|
/* MINUTE */
|
1101
|
1
|
if (sublen < 1 ) {
|
1102
|
|
goto string_too_short;
|
1103
|
|
}
|
1104
|
1
|
substr[0] = ':';
|
1105
|
1
|
if (sublen < 2 ) {
|
1106
|
|
goto string_too_short;
|
1107
|
|
}
|
1108
|
1
|
substr[1] = (char)((dts->min / 10) + '0');
|
1109
|
1
|
if (sublen < 3 ) {
|
1110
|
|
goto string_too_short;
|
1111
|
|
}
|
1112
|
1
|
substr[2] = (char)((dts->min % 10) + '0');
|
1113
|
1
|
substr += 3;
|
1114
|
1
|
sublen -= 3;
|
1115
|
|
|
1116
|
|
/* Stop if the unit is minutes */
|
1117
|
1
|
if (base == NPY_FR_m) {
|
1118
|
|
goto add_time_zone;
|
1119
|
|
}
|
1120
|
|
|
1121
|
|
/* SECOND */
|
1122
|
1
|
if (sublen < 1 ) {
|
1123
|
|
goto string_too_short;
|
1124
|
|
}
|
1125
|
1
|
substr[0] = ':';
|
1126
|
1
|
if (sublen < 2 ) {
|
1127
|
|
goto string_too_short;
|
1128
|
|
}
|
1129
|
1
|
substr[1] = (char)((dts->sec / 10) + '0');
|
1130
|
1
|
if (sublen < 3 ) {
|
1131
|
|
goto string_too_short;
|
1132
|
|
}
|
1133
|
1
|
substr[2] = (char)((dts->sec % 10) + '0');
|
1134
|
1
|
substr += 3;
|
1135
|
1
|
sublen -= 3;
|
1136
|
|
|
1137
|
|
/* Stop if the unit is seconds */
|
1138
|
1
|
if (base == NPY_FR_s) {
|
1139
|
|
goto add_time_zone;
|
1140
|
|
}
|
1141
|
|
|
1142
|
|
/* MILLISECOND */
|
1143
|
1
|
if (sublen < 1 ) {
|
1144
|
|
goto string_too_short;
|
1145
|
|
}
|
1146
|
1
|
substr[0] = '.';
|
1147
|
1
|
if (sublen < 2 ) {
|
1148
|
|
goto string_too_short;
|
1149
|
|
}
|
1150
|
1
|
substr[1] = (char)((dts->us / 100000) % 10 + '0');
|
1151
|
1
|
if (sublen < 3 ) {
|
1152
|
|
goto string_too_short;
|
1153
|
|
}
|
1154
|
1
|
substr[2] = (char)((dts->us / 10000) % 10 + '0');
|
1155
|
1
|
if (sublen < 4 ) {
|
1156
|
|
goto string_too_short;
|
1157
|
|
}
|
1158
|
1
|
substr[3] = (char)((dts->us / 1000) % 10 + '0');
|
1159
|
1
|
substr += 4;
|
1160
|
1
|
sublen -= 4;
|
1161
|
|
|
1162
|
|
/* Stop if the unit is milliseconds */
|
1163
|
1
|
if (base == NPY_FR_ms) {
|
1164
|
|
goto add_time_zone;
|
1165
|
|
}
|
1166
|
|
|
1167
|
|
/* MICROSECOND */
|
1168
|
1
|
if (sublen < 1 ) {
|
1169
|
|
goto string_too_short;
|
1170
|
|
}
|
1171
|
1
|
substr[0] = (char)((dts->us / 100) % 10 + '0');
|
1172
|
1
|
if (sublen < 2 ) {
|
1173
|
|
goto string_too_short;
|
1174
|
|
}
|
1175
|
1
|
substr[1] = (char)((dts->us / 10) % 10 + '0');
|
1176
|
1
|
if (sublen < 3 ) {
|
1177
|
|
goto string_too_short;
|
1178
|
|
}
|
1179
|
1
|
substr[2] = (char)(dts->us % 10 + '0');
|
1180
|
1
|
substr += 3;
|
1181
|
1
|
sublen -= 3;
|
1182
|
|
|
1183
|
|
/* Stop if the unit is microseconds */
|
1184
|
1
|
if (base == NPY_FR_us) {
|
1185
|
|
goto add_time_zone;
|
1186
|
|
}
|
1187
|
|
|
1188
|
|
/* NANOSECOND */
|
1189
|
1
|
if (sublen < 1 ) {
|
1190
|
|
goto string_too_short;
|
1191
|
|
}
|
1192
|
1
|
substr[0] = (char)((dts->ps / 100000) % 10 + '0');
|
1193
|
1
|
if (sublen < 2 ) {
|
1194
|
|
goto string_too_short;
|
1195
|
|
}
|
1196
|
1
|
substr[1] = (char)((dts->ps / 10000) % 10 + '0');
|
1197
|
1
|
if (sublen < 3 ) {
|
1198
|
|
goto string_too_short;
|
1199
|
|
}
|
1200
|
1
|
substr[2] = (char)((dts->ps / 1000) % 10 + '0');
|
1201
|
1
|
substr += 3;
|
1202
|
1
|
sublen -= 3;
|
1203
|
|
|
1204
|
|
/* Stop if the unit is nanoseconds */
|
1205
|
1
|
if (base == NPY_FR_ns) {
|
1206
|
|
goto add_time_zone;
|
1207
|
|
}
|
1208
|
|
|
1209
|
|
/* PICOSECOND */
|
1210
|
1
|
if (sublen < 1 ) {
|
1211
|
|
goto string_too_short;
|
1212
|
|
}
|
1213
|
1
|
substr[0] = (char)((dts->ps / 100) % 10 + '0');
|
1214
|
1
|
if (sublen < 2 ) {
|
1215
|
|
goto string_too_short;
|
1216
|
|
}
|
1217
|
1
|
substr[1] = (char)((dts->ps / 10) % 10 + '0');
|
1218
|
1
|
if (sublen < 3 ) {
|
1219
|
|
goto string_too_short;
|
1220
|
|
}
|
1221
|
1
|
substr[2] = (char)(dts->ps % 10 + '0');
|
1222
|
1
|
substr += 3;
|
1223
|
1
|
sublen -= 3;
|
1224
|
|
|
1225
|
|
/* Stop if the unit is picoseconds */
|
1226
|
1
|
if (base == NPY_FR_ps) {
|
1227
|
|
goto add_time_zone;
|
1228
|
|
}
|
1229
|
|
|
1230
|
|
/* FEMTOSECOND */
|
1231
|
1
|
if (sublen < 1 ) {
|
1232
|
|
goto string_too_short;
|
1233
|
|
}
|
1234
|
1
|
substr[0] = (char)((dts->as / 100000) % 10 + '0');
|
1235
|
1
|
if (sublen < 2 ) {
|
1236
|
|
goto string_too_short;
|
1237
|
|
}
|
1238
|
1
|
substr[1] = (char)((dts->as / 10000) % 10 + '0');
|
1239
|
1
|
if (sublen < 3 ) {
|
1240
|
|
goto string_too_short;
|
1241
|
|
}
|
1242
|
1
|
substr[2] = (char)((dts->as / 1000) % 10 + '0');
|
1243
|
1
|
substr += 3;
|
1244
|
1
|
sublen -= 3;
|
1245
|
|
|
1246
|
|
/* Stop if the unit is femtoseconds */
|
1247
|
1
|
if (base == NPY_FR_fs) {
|
1248
|
|
goto add_time_zone;
|
1249
|
|
}
|
1250
|
|
|
1251
|
|
/* ATTOSECOND */
|
1252
|
1
|
if (sublen < 1 ) {
|
1253
|
|
goto string_too_short;
|
1254
|
|
}
|
1255
|
1
|
substr[0] = (char)((dts->as / 100) % 10 + '0');
|
1256
|
1
|
if (sublen < 2 ) {
|
1257
|
|
goto string_too_short;
|
1258
|
|
}
|
1259
|
1
|
substr[1] = (char)((dts->as / 10) % 10 + '0');
|
1260
|
1
|
if (sublen < 3 ) {
|
1261
|
|
goto string_too_short;
|
1262
|
|
}
|
1263
|
1
|
substr[2] = (char)(dts->as % 10 + '0');
|
1264
|
1
|
substr += 3;
|
1265
|
1
|
sublen -= 3;
|
1266
|
|
|
1267
|
1
|
add_time_zone:
|
1268
|
1
|
if (local) {
|
1269
|
|
/* Add the +/- sign */
|
1270
|
1
|
if (sublen < 1) {
|
1271
|
|
goto string_too_short;
|
1272
|
|
}
|
1273
|
1
|
if (timezone_offset < 0) {
|
1274
|
1
|
substr[0] = '-';
|
1275
|
1
|
timezone_offset = -timezone_offset;
|
1276
|
|
}
|
1277
|
|
else {
|
1278
|
1
|
substr[0] = '+';
|
1279
|
|
}
|
1280
|
1
|
substr += 1;
|
1281
|
1
|
sublen -= 1;
|
1282
|
|
|
1283
|
|
/* Add the timezone offset */
|
1284
|
1
|
if (sublen < 1 ) {
|
1285
|
|
goto string_too_short;
|
1286
|
|
}
|
1287
|
1
|
substr[0] = (char)((timezone_offset / (10*60)) % 10 + '0');
|
1288
|
1
|
if (sublen < 2 ) {
|
1289
|
|
goto string_too_short;
|
1290
|
|
}
|
1291
|
1
|
substr[1] = (char)((timezone_offset / 60) % 10 + '0');
|
1292
|
1
|
if (sublen < 3 ) {
|
1293
|
|
goto string_too_short;
|
1294
|
|
}
|
1295
|
1
|
substr[2] = (char)(((timezone_offset % 60) / 10) % 10 + '0');
|
1296
|
1
|
if (sublen < 4 ) {
|
1297
|
|
goto string_too_short;
|
1298
|
|
}
|
1299
|
1
|
substr[3] = (char)((timezone_offset % 60) % 10 + '0');
|
1300
|
1
|
substr += 4;
|
1301
|
1
|
sublen -= 4;
|
1302
|
|
}
|
1303
|
|
/* UTC "Zulu" time */
|
1304
|
1
|
else if (utc) {
|
1305
|
1
|
if (sublen < 1) {
|
1306
|
|
goto string_too_short;
|
1307
|
|
}
|
1308
|
1
|
substr[0] = 'Z';
|
1309
|
1
|
substr += 1;
|
1310
|
1
|
sublen -= 1;
|
1311
|
|
}
|
1312
|
|
|
1313
|
|
/* Add a NULL terminator, and return */
|
1314
|
1
|
if (sublen > 0) {
|
1315
|
1
|
substr[0] = '\0';
|
1316
|
|
}
|
1317
|
|
|
1318
|
|
return 0;
|
1319
|
|
|
1320
|
1
|
string_too_short:
|
1321
|
1
|
PyErr_Format(PyExc_RuntimeError,
|
1322
|
|
"The string provided for NumPy ISO datetime formatting "
|
1323
|
|
"was too short, with length %"NPY_INTP_FMT,
|
1324
|
|
outlen);
|
1325
|
1
|
return -1;
|
1326
|
|
}
|
1327
|
|
|
1328
|
|
|
1329
|
|
/*
|
1330
|
|
* This is the Python-exposed datetime_as_string function.
|
1331
|
|
*/
|
1332
|
|
NPY_NO_EXPORT PyObject *
|
1333
|
1
|
array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args,
|
1334
|
|
PyObject *kwds)
|
1335
|
|
{
|
1336
|
1
|
PyObject *arr_in = NULL, *unit_in = NULL, *timezone_obj = NULL;
|
1337
|
|
NPY_DATETIMEUNIT unit;
|
1338
|
1
|
NPY_CASTING casting = NPY_SAME_KIND_CASTING;
|
1339
|
|
|
1340
|
1
|
int local = 0;
|
1341
|
1
|
int utc = 0;
|
1342
|
|
PyArray_DatetimeMetaData *meta;
|
1343
|
|
int strsize;
|
1344
|
|
|
1345
|
1
|
PyArrayObject *ret = NULL;
|
1346
|
|
|
1347
|
1
|
NpyIter *iter = NULL;
|
1348
|
1
|
PyArrayObject *op[2] = {NULL, NULL};
|
1349
|
1
|
PyArray_Descr *op_dtypes[2] = {NULL, NULL};
|
1350
|
|
npy_uint32 flags, op_flags[2];
|
1351
|
|
|
1352
|
|
static char *kwlist[] = {"arr", "unit", "timezone", "casting", NULL};
|
1353
|
|
|
1354
|
1
|
if(!PyArg_ParseTupleAndKeywords(args, kwds,
|
1355
|
|
"O|OOO&:datetime_as_string", kwlist,
|
1356
|
|
&arr_in,
|
1357
|
|
&unit_in,
|
1358
|
|
&timezone_obj,
|
1359
|
|
&PyArray_CastingConverter, &casting)) {
|
1360
|
|
return NULL;
|
1361
|
|
}
|
1362
|
|
|
1363
|
|
/* Claim a reference to timezone for later */
|
1364
|
1
|
Py_XINCREF(timezone_obj);
|
1365
|
|
|
1366
|
1
|
op[0] = (PyArrayObject *)PyArray_FROM_O(arr_in);
|
1367
|
1
|
if (op[0] == NULL) {
|
1368
|
|
goto fail;
|
1369
|
|
}
|
1370
|
1
|
if (PyArray_DESCR(op[0])->type_num != NPY_DATETIME) {
|
1371
|
0
|
PyErr_SetString(PyExc_TypeError,
|
1372
|
|
"input must have type NumPy datetime");
|
1373
|
0
|
goto fail;
|
1374
|
|
}
|
1375
|
|
|
1376
|
|
/* Get the datetime metadata */
|
1377
|
1
|
meta = get_datetime_metadata_from_dtype(PyArray_DESCR(op[0]));
|
1378
|
1
|
if (meta == NULL) {
|
1379
|
|
goto fail;
|
1380
|
|
}
|
1381
|
|
|
1382
|
|
/* Use the metadata's unit for printing by default */
|
1383
|
1
|
unit = meta->base;
|
1384
|
|
|
1385
|
|
/* Parse the input unit if provided */
|
1386
|
1
|
if (unit_in != NULL && unit_in != Py_None) {
|
1387
|
|
PyObject *strobj;
|
1388
|
1
|
char *str = NULL;
|
1389
|
1
|
Py_ssize_t len = 0;
|
1390
|
|
|
1391
|
1
|
if (PyUnicode_Check(unit_in)) {
|
1392
|
1
|
strobj = PyUnicode_AsASCIIString(unit_in);
|
1393
|
1
|
if (strobj == NULL) {
|
1394
|
|
goto fail;
|
1395
|
|
}
|
1396
|
|
}
|
1397
|
|
else {
|
1398
|
0
|
strobj = unit_in;
|
1399
|
0
|
Py_INCREF(strobj);
|
1400
|
|
}
|
1401
|
|
|
1402
|
1
|
if (PyBytes_AsStringAndSize(strobj, &str, &len) < 0) {
|
1403
|
0
|
Py_DECREF(strobj);
|
1404
|
|
goto fail;
|
1405
|
|
}
|
1406
|
|
|
1407
|
|
/*
|
1408
|
|
* unit == NPY_FR_ERROR means to autodetect the unit
|
1409
|
|
* from the datetime data
|
1410
|
|
* */
|
1411
|
1
|
if (strcmp(str, "auto") == 0) {
|
1412
|
|
unit = NPY_FR_ERROR;
|
1413
|
|
}
|
1414
|
|
else {
|
1415
|
1
|
unit = parse_datetime_unit_from_string(str, len, NULL);
|
1416
|
1
|
if (unit == NPY_FR_ERROR) {
|
1417
|
0
|
Py_DECREF(strobj);
|
1418
|
|
goto fail;
|
1419
|
|
}
|
1420
|
|
}
|
1421
|
1
|
Py_DECREF(strobj);
|
1422
|
|
|
1423
|
1
|
if (unit != NPY_FR_ERROR &&
|
1424
|
1
|
!can_cast_datetime64_units(meta->base, unit, casting)) {
|
1425
|
0
|
PyErr_Format(PyExc_TypeError, "Cannot create a datetime "
|
1426
|
|
"string as units '%s' from a NumPy datetime "
|
1427
|
|
"with units '%s' according to the rule %s",
|
1428
|
|
_datetime_strings[unit],
|
1429
|
0
|
_datetime_strings[meta->base],
|
1430
|
|
npy_casting_to_string(casting));
|
1431
|
0
|
goto fail;
|
1432
|
|
}
|
1433
|
|
}
|
1434
|
|
|
1435
|
|
/* Get the input time zone */
|
1436
|
1
|
if (timezone_obj != NULL) {
|
1437
|
|
/* Convert to ASCII if it's unicode */
|
1438
|
1
|
if (PyUnicode_Check(timezone_obj)) {
|
1439
|
|
/* accept unicode input */
|
1440
|
|
PyObject *obj_str;
|
1441
|
1
|
obj_str = PyUnicode_AsASCIIString(timezone_obj);
|
1442
|
1
|
if (obj_str == NULL) {
|
1443
|
|
goto fail;
|
1444
|
|
}
|
1445
|
1
|
Py_DECREF(timezone_obj);
|
1446
|
1
|
timezone_obj = obj_str;
|
1447
|
|
}
|
1448
|
|
|
1449
|
|
/* Check for the supported string inputs */
|
1450
|
1
|
if (PyBytes_Check(timezone_obj)) {
|
1451
|
|
char *str;
|
1452
|
|
Py_ssize_t len;
|
1453
|
|
|
1454
|
1
|
if (PyBytes_AsStringAndSize(timezone_obj, &str, &len) < 0) {
|
1455
|
|
goto fail;
|
1456
|
|
}
|
1457
|
|
|
1458
|
1
|
if (strcmp(str, "local") == 0) {
|
1459
|
1
|
local = 1;
|
1460
|
1
|
utc = 0;
|
1461
|
1
|
Py_DECREF(timezone_obj);
|
1462
|
1
|
timezone_obj = NULL;
|
1463
|
|
}
|
1464
|
1
|
else if (strcmp(str, "UTC") == 0) {
|
1465
|
1
|
local = 0;
|
1466
|
1
|
utc = 1;
|
1467
|
1
|
Py_DECREF(timezone_obj);
|
1468
|
1
|
timezone_obj = NULL;
|
1469
|
|
}
|
1470
|
1
|
else if (strcmp(str, "naive") == 0) {
|
1471
|
1
|
local = 0;
|
1472
|
1
|
utc = 0;
|
1473
|
1
|
Py_DECREF(timezone_obj);
|
1474
|
1
|
timezone_obj = NULL;
|
1475
|
|
}
|
1476
|
|
else {
|
1477
|
0
|
PyErr_Format(PyExc_ValueError, "Unsupported timezone "
|
1478
|
|
"input string \"%s\"", str);
|
1479
|
0
|
goto fail;
|
1480
|
|
}
|
1481
|
|
}
|
1482
|
|
/* Otherwise assume it's a Python TZInfo, or acts like one */
|
1483
|
|
else {
|
1484
|
|
local = 1;
|
1485
|
|
}
|
1486
|
|
}
|
1487
|
|
|
1488
|
|
/* Get a string size long enough for any datetimes we're given */
|
1489
|
1
|
strsize = get_datetime_iso_8601_strlen(local, unit);
|
1490
|
|
/*
|
1491
|
|
* For Python3, allocate the output array as a UNICODE array, so
|
1492
|
|
* that it will behave as strings properly
|
1493
|
|
*/
|
1494
|
1
|
op_dtypes[1] = PyArray_DescrNewFromType(NPY_UNICODE);
|
1495
|
1
|
if (op_dtypes[1] == NULL) {
|
1496
|
|
goto fail;
|
1497
|
|
}
|
1498
|
1
|
op_dtypes[1]->elsize = strsize * 4;
|
1499
|
|
/* This steals the UNICODE dtype reference in op_dtypes[1] */
|
1500
|
1
|
op[1] = (PyArrayObject *)PyArray_NewLikeArray(op[0],
|
1501
|
|
NPY_KEEPORDER, op_dtypes[1], 1);
|
1502
|
1
|
if (op[1] == NULL) {
|
1503
|
0
|
op_dtypes[1] = NULL;
|
1504
|
0
|
goto fail;
|
1505
|
|
}
|
1506
|
|
/* Create the iteration string data type (always ASCII string) */
|
1507
|
1
|
op_dtypes[1] = PyArray_DescrNewFromType(NPY_STRING);
|
1508
|
1
|
if (op_dtypes[1] == NULL) {
|
1509
|
|
goto fail;
|
1510
|
|
}
|
1511
|
1
|
op_dtypes[1]->elsize = strsize;
|
1512
|
|
|
1513
|
1
|
flags = NPY_ITER_ZEROSIZE_OK|
|
1514
|
|
NPY_ITER_BUFFERED;
|
1515
|
1
|
op_flags[0] = NPY_ITER_READONLY|
|
1516
|
|
NPY_ITER_ALIGNED;
|
1517
|
1
|
op_flags[1] = NPY_ITER_WRITEONLY|
|
1518
|
|
NPY_ITER_ALLOCATE;
|
1519
|
|
|
1520
|
1
|
iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_UNSAFE_CASTING,
|
1521
|
|
op_flags, op_dtypes);
|
1522
|
1
|
if (iter == NULL) {
|
1523
|
|
goto fail;
|
1524
|
|
}
|
1525
|
|
|
1526
|
1
|
if (NpyIter_GetIterSize(iter) != 0) {
|
1527
|
|
NpyIter_IterNextFunc *iternext;
|
1528
|
|
char **dataptr;
|
1529
|
|
npy_datetime dt;
|
1530
|
|
npy_datetimestruct dts;
|
1531
|
|
|
1532
|
1
|
iternext = NpyIter_GetIterNext(iter, NULL);
|
1533
|
1
|
if (iternext == NULL) {
|
1534
|
|
goto fail;
|
1535
|
|
}
|
1536
|
1
|
dataptr = NpyIter_GetDataPtrArray(iter);
|
1537
|
|
|
1538
|
|
do {
|
1539
|
1
|
int tzoffset = -1;
|
1540
|
|
|
1541
|
|
/* Get the datetime */
|
1542
|
1
|
dt = *(npy_datetime *)dataptr[0];
|
1543
|
|
|
1544
|
|
/* Convert it to a struct */
|
1545
|
1
|
if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) {
|
1546
|
|
goto fail;
|
1547
|
|
}
|
1548
|
|
|
1549
|
|
/* Get the tzoffset from the timezone if provided */
|
1550
|
1
|
if (local && timezone_obj != NULL) {
|
1551
|
1
|
tzoffset = get_tzoffset_from_pytzinfo(timezone_obj, &dts);
|
1552
|
1
|
if (tzoffset == -1) {
|
1553
|
|
goto fail;
|
1554
|
|
}
|
1555
|
|
}
|
1556
|
|
|
1557
|
|
/* Zero the destination string completely */
|
1558
|
1
|
memset(dataptr[1], 0, strsize);
|
1559
|
|
/* Convert that into a string */
|
1560
|
1
|
if (make_iso_8601_datetime(&dts, (char *)dataptr[1], strsize,
|
1561
|
|
local, utc, unit, tzoffset, casting) < 0) {
|
1562
|
|
goto fail;
|
1563
|
|
}
|
1564
|
1
|
} while(iternext(iter));
|
1565
|
|
}
|
1566
|
|
|
1567
|
1
|
ret = NpyIter_GetOperandArray(iter)[1];
|
1568
|
1
|
Py_INCREF(ret);
|
1569
|
|
|
1570
|
1
|
Py_XDECREF(timezone_obj);
|
1571
|
1
|
Py_XDECREF(op[0]);
|
1572
|
1
|
Py_XDECREF(op[1]);
|
1573
|
1
|
Py_XDECREF(op_dtypes[0]);
|
1574
|
1
|
Py_XDECREF(op_dtypes[1]);
|
1575
|
|
if (iter != NULL) {
|
1576
|
1
|
NpyIter_Deallocate(iter);
|
1577
|
|
}
|
1578
|
|
|
1579
|
1
|
return PyArray_Return(ret);
|
1580
|
|
|
1581
|
0
|
fail:
|
1582
|
1
|
Py_XDECREF(timezone_obj);
|
1583
|
1
|
Py_XDECREF(op[0]);
|
1584
|
1
|
Py_XDECREF(op[1]);
|
1585
|
1
|
Py_XDECREF(op_dtypes[0]);
|
1586
|
1
|
Py_XDECREF(op_dtypes[1]);
|
1587
|
1
|
if (iter != NULL) {
|
1588
|
1
|
NpyIter_Deallocate(iter);
|
1589
|
|
}
|
1590
|
|
|
1591
|
|
return NULL;
|
1592
|
|
}
|