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
}

Read our documentation on viewing source code .

Loading