"Fossies" - the Fresh Open Source Software Archive

Member "numpy-1.16.4/numpy/core/src/multiarray/datetime_strings.c" (22 Feb 2019, 45400 Bytes) of package /linux/misc/numpy-1.16.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "datetime_strings.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 1.15.4_vs_1.16.0.

    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 "methods.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 get_localtime(NPY_TIME_T *ts, struct tm *tms)
   76 {
   77     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     if (localtime_r(ts, tms) == NULL) {
  100         func_name = "localtime_r";
  101         goto fail;
  102     }
  103 #endif
  104 
  105     return 0;
  106 
  107 fail:
  108     PyErr_Format(PyExc_OSError, "Failed to use '%s' to convert "
  109                                 "to a local time", func_name);
  110     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 convert_datetimestruct_utc_to_local(npy_datetimestruct *out_dts_local,
  123                 const npy_datetimestruct *dts_utc, int *out_timezone_offset)
  124 {
  125     NPY_TIME_T rawtime = 0, localrawtime;
  126     struct tm tm_;
  127     npy_int64 year_correction = 0;
  128 
  129     /* Make a copy of the input 'dts' to modify */
  130     *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     rawtime = (NPY_TIME_T)get_datetimestruct_days(out_dts_local) * 24 * 60 * 60;
  159     rawtime += dts_utc->hour * 60 * 60;
  160     rawtime += dts_utc->min * 60;
  161 
  162     /* localtime converts a 'time_t' into a local 'struct tm' */
  163     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     out_dts_local->min = tm_.tm_min;
  170     out_dts_local->hour = tm_.tm_hour;
  171     out_dts_local->day = tm_.tm_mday;
  172     out_dts_local->month = tm_.tm_mon + 1;
  173     out_dts_local->year = tm_.tm_year + 1900;
  174 
  175     /* Extract the timezone offset that was applied */
  176     rawtime /= 60;
  177     localrawtime = (NPY_TIME_T)get_datetimestruct_days(out_dts_local) * 24 * 60;
  178     localrawtime += out_dts_local->hour * 60;
  179     localrawtime += out_dts_local->min;
  180 
  181     *out_timezone_offset = localrawtime - rawtime;
  182 
  183     /* Reapply the year 2038 year correction */
  184     out_dts_local->year += year_correction;
  185 
  186     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 parse_iso_8601_datetime(char *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     int year_leap = 0;
  229     int i, numdigits;
  230     char *substr;
  231     Py_ssize_t sublen;
  232     NPY_DATETIMEUNIT bestunit;
  233 
  234     /* Initialize the output to all zeros */
  235     memset(out, 0, sizeof(npy_datetimestruct));
  236     out->month = 1;
  237     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     if (len <= 0 || (len == 3 &&
  245                         tolower(str[0]) == 'n' &&
  246                         tolower(str[1]) == 'a' &&
  247                         tolower(str[2]) == 't')) {
  248         out->year = NPY_DATETIME_NAT;
  249 
  250         /*
  251          * Indicate that this was a special value, and
  252          * recommend generic units.
  253          */
  254         if (out_bestunit != NULL) {
  255             *out_bestunit = NPY_FR_GENERIC;
  256         }
  257         if (out_special != NULL) {
  258             *out_special = 1;
  259         }
  260 
  261         return 0;
  262     }
  263 
  264     if (unit == NPY_FR_GENERIC) {
  265         PyErr_SetString(PyExc_ValueError,
  266                     "Cannot create a NumPy datetime other than NaT "
  267                     "with generic units");
  268         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     if (len == 5 && tolower(str[0]) == 't' &&
  281                     tolower(str[1]) == 'o' &&
  282                     tolower(str[2]) == 'd' &&
  283                     tolower(str[3]) == 'a' &&
  284                     tolower(str[4]) == 'y') {
  285         NPY_TIME_T rawtime = 0;
  286         struct tm tm_;
  287 
  288         time(&rawtime);
  289         if (get_localtime(&rawtime, &tm_) < 0) {
  290             return -1;
  291         }
  292         out->year = tm_.tm_year + 1900;
  293         out->month = tm_.tm_mon + 1;
  294         out->day = tm_.tm_mday;
  295 
  296         bestunit = NPY_FR_D;
  297 
  298         /*
  299          * Indicate that this was a special value, and
  300          * is a date (unit 'D').
  301          */
  302         if (out_bestunit != NULL) {
  303             *out_bestunit = bestunit;
  304         }
  305         if (out_special != NULL) {
  306             *out_special = 1;
  307         }
  308 
  309         /* Check the casting rule */
  310         if (unit != NPY_FR_ERROR &&
  311                 !can_cast_datetime64_units(bestunit, unit, casting)) {
  312             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             return -1;
  317         }
  318 
  319         return 0;
  320     }
  321 
  322     /* The string "now" resolves to the current UTC time */
  323     if (len == 3 && tolower(str[0]) == 'n' &&
  324                     tolower(str[1]) == 'o' &&
  325                     tolower(str[2]) == 'w') {
  326         NPY_TIME_T rawtime = 0;
  327         PyArray_DatetimeMetaData meta;
  328 
  329         time(&rawtime);
  330 
  331         /* Set up a dummy metadata for the conversion */
  332         meta.base = NPY_FR_s;
  333         meta.num = 1;
  334 
  335         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         if (out_bestunit != NULL) {
  343             *out_bestunit = bestunit;
  344         }
  345         if (out_special != NULL) {
  346             *out_special = 1;
  347         }
  348 
  349         /* Check the casting rule */
  350         if (unit != NPY_FR_ERROR &&
  351                 !can_cast_datetime64_units(bestunit, unit, casting)) {
  352             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             return -1;
  357         }
  358 
  359         return convert_datetime_to_datetimestruct(&meta, rawtime, out);
  360     }
  361 
  362     /* Anything else isn't a special value */
  363     if (out_special != NULL) {
  364         *out_special = 0;
  365     }
  366 
  367     substr = str;
  368     sublen = len;
  369 
  370     /* Skip leading whitespace */
  371     while (sublen > 0 && isspace(*substr)) {
  372         ++substr;
  373         --sublen;
  374     }
  375 
  376     /* Leading '-' sign for negative year */
  377     if (*substr == '-' || *substr == '+') {
  378         ++substr;
  379         --sublen;
  380     }
  381 
  382     if (sublen == 0) {
  383         goto parse_error;
  384     }
  385 
  386     /* PARSE THE YEAR (digits until the '-' character) */
  387     out->year = 0;
  388     while (sublen > 0 && isdigit(*substr)) {
  389         out->year = 10 * out->year + (*substr - '0');
  390         ++substr;
  391         --sublen;
  392     }
  393 
  394     /* Negate the year if necessary */
  395     if (str[0] == '-') {
  396         out->year = -out->year;
  397     }
  398     /* Check whether it's a leap-year */
  399     year_leap = is_leapyear(out->year);
  400 
  401     /* Next character must be a '-' or the end of the string */
  402     if (sublen == 0) {
  403         bestunit = NPY_FR_Y;
  404         goto finish;
  405     }
  406     else if (*substr == '-') {
  407         ++substr;
  408         --sublen;
  409     }
  410     else {
  411         goto parse_error;
  412     }
  413 
  414     /* Can't have a trailing '-' */
  415     if (sublen == 0) {
  416         goto parse_error;
  417     }
  418 
  419     /* PARSE THE MONTH (2 digits) */
  420     if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  421         out->month = 10 * (substr[0] - '0') + (substr[1] - '0');
  422 
  423         if (out->month < 1 || out->month > 12) {
  424             PyErr_Format(PyExc_ValueError,
  425                         "Month out of range in datetime string \"%s\"", str);
  426             goto error;
  427         }
  428         substr += 2;
  429         sublen -= 2;
  430     }
  431     else {
  432         goto parse_error;
  433     }
  434 
  435     /* Next character must be a '-' or the end of the string */
  436     if (sublen == 0) {
  437         bestunit = NPY_FR_M;
  438         goto finish;
  439     }
  440     else if (*substr == '-') {
  441         ++substr;
  442         --sublen;
  443     }
  444     else {
  445         goto parse_error;
  446     }
  447 
  448     /* Can't have a trailing '-' */
  449     if (sublen == 0) {
  450         goto parse_error;
  451     }
  452 
  453     /* PARSE THE DAY (2 digits) */
  454     if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  455         out->day = 10 * (substr[0] - '0') + (substr[1] - '0');
  456 
  457         if (out->day < 1 ||
  458                     out->day > _days_per_month_table[year_leap][out->month-1]) {
  459             PyErr_Format(PyExc_ValueError,
  460                         "Day out of range in datetime string \"%s\"", str);
  461             goto error;
  462         }
  463         substr += 2;
  464         sublen -= 2;
  465     }
  466     else {
  467         goto parse_error;
  468     }
  469 
  470     /* Next character must be a 'T', ' ', or end of string */
  471     if (sublen == 0) {
  472         bestunit = NPY_FR_D;
  473         goto finish;
  474     }
  475     else if (*substr != 'T' && *substr != ' ') {
  476         goto parse_error;
  477     }
  478     else {
  479         ++substr;
  480         --sublen;
  481     }
  482 
  483     /* PARSE THE HOURS (2 digits) */
  484     if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  485         out->hour = 10 * (substr[0] - '0') + (substr[1] - '0');
  486 
  487         if (out->hour >= 24) {
  488             PyErr_Format(PyExc_ValueError,
  489                         "Hours out of range in datetime string \"%s\"", str);
  490             goto error;
  491         }
  492         substr += 2;
  493         sublen -= 2;
  494     }
  495     else {
  496         goto parse_error;
  497     }
  498 
  499     /* Next character must be a ':' or the end of the string */
  500     if (sublen > 0 && *substr == ':') {
  501         ++substr;
  502         --sublen;
  503     }
  504     else {
  505         bestunit = NPY_FR_h;
  506         goto parse_timezone;
  507     }
  508 
  509     /* Can't have a trailing ':' */
  510     if (sublen == 0) {
  511         goto parse_error;
  512     }
  513 
  514     /* PARSE THE MINUTES (2 digits) */
  515     if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  516         out->min = 10 * (substr[0] - '0') + (substr[1] - '0');
  517 
  518         if (out->min >= 60) {
  519             PyErr_Format(PyExc_ValueError,
  520                         "Minutes out of range in datetime string \"%s\"", str);
  521             goto error;
  522         }
  523         substr += 2;
  524         sublen -= 2;
  525     }
  526     else {
  527         goto parse_error;
  528     }
  529 
  530     /* Next character must be a ':' or the end of the string */
  531     if (sublen > 0 && *substr == ':') {
  532         ++substr;
  533         --sublen;
  534     }
  535     else {
  536         bestunit = NPY_FR_m;
  537         goto parse_timezone;
  538     }
  539 
  540     /* Can't have a trailing ':' */
  541     if (sublen == 0) {
  542         goto parse_error;
  543     }
  544 
  545     /* PARSE THE SECONDS (2 digits) */
  546     if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  547         out->sec = 10 * (substr[0] - '0') + (substr[1] - '0');
  548 
  549         if (out->sec >= 60) {
  550             PyErr_Format(PyExc_ValueError,
  551                         "Seconds out of range in datetime string \"%s\"", str);
  552             goto error;
  553         }
  554         substr += 2;
  555         sublen -= 2;
  556     }
  557     else {
  558         goto parse_error;
  559     }
  560 
  561     /* Next character may be a '.' indicating fractional seconds */
  562     if (sublen > 0 && *substr == '.') {
  563         ++substr;
  564         --sublen;
  565     }
  566     else {
  567         bestunit = NPY_FR_s;
  568         goto parse_timezone;
  569     }
  570 
  571     /* PARSE THE MICROSECONDS (0 to 6 digits) */
  572     numdigits = 0;
  573     for (i = 0; i < 6; ++i) {
  574         out->us *= 10;
  575         if (sublen > 0  && isdigit(*substr)) {
  576             out->us += (*substr - '0');
  577             ++substr;
  578             --sublen;
  579             ++numdigits;
  580         }
  581     }
  582 
  583     if (sublen == 0 || !isdigit(*substr)) {
  584         if (numdigits > 3) {
  585             bestunit = NPY_FR_us;
  586         }
  587         else {
  588             bestunit = NPY_FR_ms;
  589         }
  590         goto parse_timezone;
  591     }
  592 
  593     /* PARSE THE PICOSECONDS (0 to 6 digits) */
  594     numdigits = 0;
  595     for (i = 0; i < 6; ++i) {
  596         out->ps *= 10;
  597         if (sublen > 0 && isdigit(*substr)) {
  598             out->ps += (*substr - '0');
  599             ++substr;
  600             --sublen;
  601             ++numdigits;
  602         }
  603     }
  604 
  605     if (sublen == 0 || !isdigit(*substr)) {
  606         if (numdigits > 3) {
  607             bestunit = NPY_FR_ps;
  608         }
  609         else {
  610             bestunit = NPY_FR_ns;
  611         }
  612         goto parse_timezone;
  613     }
  614 
  615     /* PARSE THE ATTOSECONDS (0 to 6 digits) */
  616     numdigits = 0;
  617     for (i = 0; i < 6; ++i) {
  618         out->as *= 10;
  619         if (sublen > 0 && isdigit(*substr)) {
  620             out->as += (*substr - '0');
  621             ++substr;
  622             --sublen;
  623             ++numdigits;
  624         }
  625     }
  626 
  627     if (numdigits > 3) {
  628         bestunit = NPY_FR_as;
  629     }
  630     else {
  631         bestunit = NPY_FR_fs;
  632     }
  633 
  634 parse_timezone:
  635     if (sublen == 0) {
  636         goto finish;
  637     }
  638     else {
  639         /* 2016-01-14, 1.11 */
  640         PyErr_Clear();
  641         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     if (*substr == 'Z') {
  650         if (sublen == 1) {
  651             goto finish;
  652         }
  653         else {
  654             ++substr;
  655             --sublen;
  656         }
  657     }
  658     /* Time zone offset */
  659     else if (*substr == '-' || *substr == '+') {
  660         int offset_neg = 0, offset_hour = 0, offset_minute = 0;
  661 
  662         if (*substr == '-') {
  663             offset_neg = 1;
  664         }
  665         ++substr;
  666         --sublen;
  667 
  668         /* The hours offset */
  669         if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  670             offset_hour = 10 * (substr[0] - '0') + (substr[1] - '0');
  671             substr += 2;
  672             sublen -= 2;
  673             if (offset_hour >= 24) {
  674                 PyErr_Format(PyExc_ValueError,
  675                             "Timezone hours offset out of range "
  676                             "in datetime string \"%s\"", str);
  677                 goto error;
  678             }
  679         }
  680         else {
  681             goto parse_error;
  682         }
  683 
  684         /* The minutes offset is optional */
  685         if (sublen > 0) {
  686             /* Optional ':' */
  687             if (*substr == ':') {
  688                 ++substr;
  689                 --sublen;
  690             }
  691 
  692             /* The minutes offset (at the end of the string) */
  693             if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) {
  694                 offset_minute = 10 * (substr[0] - '0') + (substr[1] - '0');
  695                 substr += 2;
  696                 sublen -= 2;
  697                 if (offset_minute >= 60) {
  698                     PyErr_Format(PyExc_ValueError,
  699                                 "Timezone minutes offset out of range "
  700                                 "in datetime string \"%s\"", str);
  701                     goto error;
  702                 }
  703             }
  704             else {
  705                 goto parse_error;
  706             }
  707         }
  708 
  709         /* Apply the time zone offset */
  710         if (offset_neg) {
  711             offset_hour = -offset_hour;
  712             offset_minute = -offset_minute;
  713         }
  714         add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute);
  715     }
  716 
  717     /* Skip trailing whitespace */
  718     while (sublen > 0 && isspace(*substr)) {
  719         ++substr;
  720         --sublen;
  721     }
  722 
  723     if (sublen != 0) {
  724         goto parse_error;
  725     }
  726 
  727 finish:
  728     if (out_bestunit != NULL) {
  729         *out_bestunit = bestunit;
  730     }
  731 
  732     /* Check the casting rule */
  733     if (unit != NPY_FR_ERROR &&
  734             !can_cast_datetime64_units(bestunit, unit, casting)) {
  735         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         return -1;
  740     }
  741 
  742     return 0;
  743 
  744 parse_error:
  745     PyErr_Format(PyExc_ValueError,
  746             "Error parsing datetime string \"%s\" at position %d",
  747             str, (int)(substr-str));
  748     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 get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base)
  760 {
  761     int len = 0;
  762 
  763     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         case NPY_FR_GENERIC:
  768             /* Generic units can only be used to represent NaT */
  769             return 4;
  770         case NPY_FR_as:
  771             len += 3;  /* "###" */
  772         case NPY_FR_fs:
  773             len += 3;  /* "###" */
  774         case NPY_FR_ps:
  775             len += 3;  /* "###" */
  776         case NPY_FR_ns:
  777             len += 3;  /* "###" */
  778         case NPY_FR_us:
  779             len += 3;  /* "###" */
  780         case NPY_FR_ms:
  781             len += 4;  /* ".###" */
  782         case NPY_FR_s:
  783             len += 3;  /* ":##" */
  784         case NPY_FR_m:
  785             len += 3;  /* ":##" */
  786         case NPY_FR_h:
  787             len += 3;  /* "T##" */
  788         case NPY_FR_D:
  789         case NPY_FR_W:
  790             len += 3;  /* "-##" */
  791         case NPY_FR_M:
  792             len += 3;  /* "-##" */
  793         case NPY_FR_Y:
  794             len += 21; /* 64-bit year */
  795             break;
  796     }
  797 
  798     if (base >= NPY_FR_h) {
  799         if (local) {
  800             len += 5;  /* "+####" or "-####" */
  801         }
  802         else {
  803             len += 1;  /* "Z" */
  804         }
  805     }
  806 
  807     len += 1; /* NULL terminator */
  808 
  809     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 lossless_unit_from_datetimestruct(npy_datetimestruct *dts)
  818 {
  819     if (dts->as % 1000 != 0) {
  820         return NPY_FR_as;
  821     }
  822     else if (dts->as != 0) {
  823         return NPY_FR_fs;
  824     }
  825     else if (dts->ps % 1000 != 0) {
  826         return NPY_FR_ps;
  827     }
  828     else if (dts->ps != 0) {
  829         return NPY_FR_ns;
  830     }
  831     else if (dts->us % 1000 != 0) {
  832         return NPY_FR_us;
  833     }
  834     else if (dts->us != 0) {
  835         return NPY_FR_ms;
  836     }
  837     else if (dts->sec != 0) {
  838         return NPY_FR_s;
  839     }
  840     else if (dts->min != 0) {
  841         return NPY_FR_m;
  842     }
  843     else if (dts->hour != 0) {
  844         return NPY_FR_h;
  845     }
  846     else if (dts->day != 1) {
  847         return NPY_FR_D;
  848     }
  849     else if (dts->month != 1) {
  850         return NPY_FR_M;
  851     }
  852     else {
  853         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 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     int timezone_offset = 0;
  892 
  893     char *substr = outstr;
  894     npy_intp sublen = outlen;
  895     npy_intp tmplen;
  896 
  897     /* Handle NaT, and treat a datetime with generic units as NaT */
  898     if (dts->year == NPY_DATETIME_NAT || base == NPY_FR_GENERIC) {
  899         if (outlen < 3) {
  900             goto string_too_short;
  901         }
  902         outstr[0] = 'N';
  903         outstr[1] = 'a';
  904         outstr[2] = 'T';
  905         if (outlen > 3) {
  906             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     if ((dts->year < 1970 || dts->year >= 10000) && tzoffset == -1) {
  925         local = 0;
  926     }
  927 
  928     /* Automatically detect a good unit */
  929     if (base == NPY_FR_ERROR) {
  930         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         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         else if (base < NPY_FR_D) {
  940             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     else if (base == NPY_FR_W) {
  950         base = NPY_FR_D;
  951     }
  952 
  953     /* Use the C API to convert from UTC to local time */
  954     if (local && tzoffset == -1) {
  955         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     else if (local) {
  965         /* Make a copy of the npy_datetimestruct we can modify */
  966         dts_local = *dts;
  967         dts = &dts_local;
  968 
  969         /* Set and apply the required timezone offset */
  970         timezone_offset = tzoffset;
  971         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     if (casting != NPY_UNSAFE_CASTING) {
  980         /* Producing a date as a local time is always 'unsafe' */
  981         if (base <= NPY_FR_D && local) {
  982             PyErr_SetString(PyExc_TypeError, "Cannot create a local "
  983                         "timezone-based date string from a NumPy "
  984                         "datetime without forcing 'unsafe' casting");
  985             return -1;
  986         }
  987         /* Only 'unsafe' and 'same_kind' allow data loss */
  988         else {
  989             NPY_DATETIMEUNIT unitprec;
  990 
  991             unitprec = lossless_unit_from_datetimestruct(dts);
  992             if (casting != NPY_SAME_KIND_CASTING && unitprec > base) {
  993                 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                 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     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     if (tmplen < 0 || tmplen > sublen) {
 1018         goto string_too_short;
 1019     }
 1020     substr += tmplen;
 1021     sublen -= tmplen;
 1022 
 1023     /* Stop if the unit is years */
 1024     if (base == NPY_FR_Y) {
 1025         if (sublen > 0) {
 1026             *substr = '\0';
 1027         }
 1028         return 0;
 1029     }
 1030 
 1031     /* MONTH */
 1032     if (sublen < 1 ) {
 1033         goto string_too_short;
 1034     }
 1035     substr[0] = '-';
 1036     if (sublen < 2 ) {
 1037         goto string_too_short;
 1038     }
 1039     substr[1] = (char)((dts->month / 10) + '0');
 1040     if (sublen < 3 ) {
 1041         goto string_too_short;
 1042     }
 1043     substr[2] = (char)((dts->month % 10) + '0');
 1044     substr += 3;
 1045     sublen -= 3;
 1046 
 1047     /* Stop if the unit is months */
 1048     if (base == NPY_FR_M) {
 1049         if (sublen > 0) {
 1050             *substr = '\0';
 1051         }
 1052         return 0;
 1053     }
 1054 
 1055     /* DAY */
 1056     if (sublen < 1 ) {
 1057         goto string_too_short;
 1058     }
 1059     substr[0] = '-';
 1060     if (sublen < 2 ) {
 1061         goto string_too_short;
 1062     }
 1063     substr[1] = (char)((dts->day / 10) + '0');
 1064     if (sublen < 3 ) {
 1065         goto string_too_short;
 1066     }
 1067     substr[2] = (char)((dts->day % 10) + '0');
 1068     substr += 3;
 1069     sublen -= 3;
 1070 
 1071     /* Stop if the unit is days */
 1072     if (base == NPY_FR_D) {
 1073         if (sublen > 0) {
 1074             *substr = '\0';
 1075         }
 1076         return 0;
 1077     }
 1078 
 1079     /* HOUR */
 1080     if (sublen < 1 ) {
 1081         goto string_too_short;
 1082     }
 1083     substr[0] = 'T';
 1084     if (sublen < 2 ) {
 1085         goto string_too_short;
 1086     }
 1087     substr[1] = (char)((dts->hour / 10) + '0');
 1088     if (sublen < 3 ) {
 1089         goto string_too_short;
 1090     }
 1091     substr[2] = (char)((dts->hour % 10) + '0');
 1092     substr += 3;
 1093     sublen -= 3;
 1094 
 1095     /* Stop if the unit is hours */
 1096     if (base == NPY_FR_h) {
 1097         goto add_time_zone;
 1098     }
 1099 
 1100     /* MINUTE */
 1101     if (sublen < 1 ) {
 1102         goto string_too_short;
 1103     }
 1104     substr[0] = ':';
 1105     if (sublen < 2 ) {
 1106         goto string_too_short;
 1107     }
 1108     substr[1] = (char)((dts->min / 10) + '0');
 1109     if (sublen < 3 ) {
 1110         goto string_too_short;
 1111     }
 1112     substr[2] = (char)((dts->min % 10) + '0');
 1113     substr += 3;
 1114     sublen -= 3;
 1115 
 1116     /* Stop if the unit is minutes */
 1117     if (base == NPY_FR_m) {
 1118         goto add_time_zone;
 1119     }
 1120 
 1121     /* SECOND */
 1122     if (sublen < 1 ) {
 1123         goto string_too_short;
 1124     }
 1125     substr[0] = ':';
 1126     if (sublen < 2 ) {
 1127         goto string_too_short;
 1128     }
 1129     substr[1] = (char)((dts->sec / 10) + '0');
 1130     if (sublen < 3 ) {
 1131         goto string_too_short;
 1132     }
 1133     substr[2] = (char)((dts->sec % 10) + '0');
 1134     substr += 3;
 1135     sublen -= 3;
 1136 
 1137     /* Stop if the unit is seconds */
 1138     if (base == NPY_FR_s) {
 1139         goto add_time_zone;
 1140     }
 1141 
 1142     /* MILLISECOND */
 1143     if (sublen < 1 ) {
 1144         goto string_too_short;
 1145     }
 1146     substr[0] = '.';
 1147     if (sublen < 2 ) {
 1148         goto string_too_short;
 1149     }
 1150     substr[1] = (char)((dts->us / 100000) % 10 + '0');
 1151     if (sublen < 3 ) {
 1152         goto string_too_short;
 1153     }
 1154     substr[2] = (char)((dts->us / 10000) % 10 + '0');
 1155     if (sublen < 4 ) {
 1156         goto string_too_short;
 1157     }
 1158     substr[3] = (char)((dts->us / 1000) % 10 + '0');
 1159     substr += 4;
 1160     sublen -= 4;
 1161 
 1162     /* Stop if the unit is milliseconds */
 1163     if (base == NPY_FR_ms) {
 1164         goto add_time_zone;
 1165     }
 1166 
 1167     /* MICROSECOND */
 1168     if (sublen < 1 ) {
 1169         goto string_too_short;
 1170     }
 1171     substr[0] = (char)((dts->us / 100) % 10 + '0');
 1172     if (sublen < 2 ) {
 1173         goto string_too_short;
 1174     }
 1175     substr[1] = (char)((dts->us / 10) % 10 + '0');
 1176     if (sublen < 3 ) {
 1177         goto string_too_short;
 1178     }
 1179     substr[2] = (char)(dts->us % 10 + '0');
 1180     substr += 3;
 1181     sublen -= 3;
 1182 
 1183     /* Stop if the unit is microseconds */
 1184     if (base == NPY_FR_us) {
 1185         goto add_time_zone;
 1186     }
 1187 
 1188     /* NANOSECOND */
 1189     if (sublen < 1 ) {
 1190         goto string_too_short;
 1191     }
 1192     substr[0] = (char)((dts->ps / 100000) % 10 + '0');
 1193     if (sublen < 2 ) {
 1194         goto string_too_short;
 1195     }
 1196     substr[1] = (char)((dts->ps / 10000) % 10 + '0');
 1197     if (sublen < 3 ) {
 1198         goto string_too_short;
 1199     }
 1200     substr[2] = (char)((dts->ps / 1000) % 10 + '0');
 1201     substr += 3;
 1202     sublen -= 3;
 1203 
 1204     /* Stop if the unit is nanoseconds */
 1205     if (base == NPY_FR_ns) {
 1206         goto add_time_zone;
 1207     }
 1208 
 1209     /* PICOSECOND */
 1210     if (sublen < 1 ) {
 1211         goto string_too_short;
 1212     }
 1213     substr[0] = (char)((dts->ps / 100) % 10 + '0');
 1214     if (sublen < 2 ) {
 1215         goto string_too_short;
 1216     }
 1217     substr[1] = (char)((dts->ps / 10) % 10 + '0');
 1218     if (sublen < 3 ) {
 1219         goto string_too_short;
 1220     }
 1221     substr[2] = (char)(dts->ps % 10 + '0');
 1222     substr += 3;
 1223     sublen -= 3;
 1224 
 1225     /* Stop if the unit is picoseconds */
 1226     if (base == NPY_FR_ps) {
 1227         goto add_time_zone;
 1228     }
 1229 
 1230     /* FEMTOSECOND */
 1231     if (sublen < 1 ) {
 1232         goto string_too_short;
 1233     }
 1234     substr[0] = (char)((dts->as / 100000) % 10 + '0');
 1235     if (sublen < 2 ) {
 1236         goto string_too_short;
 1237     }
 1238     substr[1] = (char)((dts->as / 10000) % 10 + '0');
 1239     if (sublen < 3 ) {
 1240         goto string_too_short;
 1241     }
 1242     substr[2] = (char)((dts->as / 1000) % 10 + '0');
 1243     substr += 3;
 1244     sublen -= 3;
 1245 
 1246     /* Stop if the unit is femtoseconds */
 1247     if (base == NPY_FR_fs) {
 1248         goto add_time_zone;
 1249     }
 1250 
 1251     /* ATTOSECOND */
 1252     if (sublen < 1 ) {
 1253         goto string_too_short;
 1254     }
 1255     substr[0] = (char)((dts->as / 100) % 10 + '0');
 1256     if (sublen < 2 ) {
 1257         goto string_too_short;
 1258     }
 1259     substr[1] = (char)((dts->as / 10) % 10 + '0');
 1260     if (sublen < 3 ) {
 1261         goto string_too_short;
 1262     }
 1263     substr[2] = (char)(dts->as % 10 + '0');
 1264     substr += 3;
 1265     sublen -= 3;
 1266 
 1267 add_time_zone:
 1268     if (local) {
 1269         /* Add the +/- sign */
 1270         if (sublen < 1) {
 1271             goto string_too_short;
 1272         }
 1273         if (timezone_offset < 0) {
 1274             substr[0] = '-';
 1275             timezone_offset = -timezone_offset;
 1276         }
 1277         else {
 1278             substr[0] = '+';
 1279         }
 1280         substr += 1;
 1281         sublen -= 1;
 1282 
 1283         /* Add the timezone offset */
 1284         if (sublen < 1 ) {
 1285             goto string_too_short;
 1286         }
 1287         substr[0] = (char)((timezone_offset / (10*60)) % 10 + '0');
 1288         if (sublen < 2 ) {
 1289             goto string_too_short;
 1290         }
 1291         substr[1] = (char)((timezone_offset / 60) % 10 + '0');
 1292         if (sublen < 3 ) {
 1293             goto string_too_short;
 1294         }
 1295         substr[2] = (char)(((timezone_offset % 60) / 10) % 10 + '0');
 1296         if (sublen < 4 ) {
 1297             goto string_too_short;
 1298         }
 1299         substr[3] = (char)((timezone_offset % 60) % 10 + '0');
 1300         substr += 4;
 1301         sublen -= 4;
 1302     }
 1303     /* UTC "Zulu" time */
 1304     else if (utc) {
 1305         if (sublen < 1) {
 1306             goto string_too_short;
 1307         }
 1308         substr[0] = 'Z';
 1309         substr += 1;
 1310         sublen -= 1;
 1311     }
 1312 
 1313     /* Add a NULL terminator, and return */
 1314     if (sublen > 0) {
 1315         substr[0] = '\0';
 1316     }
 1317 
 1318     return 0;
 1319 
 1320 string_too_short:
 1321     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     return -1;
 1326 }
 1327 
 1328 
 1329 /*
 1330  * This is the Python-exposed datetime_as_string function.
 1331  */
 1332 NPY_NO_EXPORT PyObject *
 1333 array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args,
 1334                                 PyObject *kwds)
 1335 {
 1336     PyObject *arr_in = NULL, *unit_in = NULL, *timezone_obj = NULL;
 1337     NPY_DATETIMEUNIT unit;
 1338     NPY_CASTING casting = NPY_SAME_KIND_CASTING;
 1339 
 1340     int local = 0;
 1341     int utc = 0;
 1342     PyArray_DatetimeMetaData *meta;
 1343     int strsize;
 1344 
 1345     PyArrayObject *ret = NULL;
 1346 
 1347     NpyIter *iter = NULL;
 1348     PyArrayObject *op[2] = {NULL, NULL};
 1349     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     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     Py_XINCREF(timezone_obj);
 1365 
 1366     op[0] = (PyArrayObject *)PyArray_FROM_O(arr_in);
 1367     if (op[0] == NULL) {
 1368         goto fail;
 1369     }
 1370     if (PyArray_DESCR(op[0])->type_num != NPY_DATETIME) {
 1371         PyErr_SetString(PyExc_TypeError,
 1372                     "input must have type NumPy datetime");
 1373         goto fail;
 1374     }
 1375 
 1376     /* Get the datetime metadata */
 1377     meta = get_datetime_metadata_from_dtype(PyArray_DESCR(op[0]));
 1378     if (meta == NULL) {
 1379         goto fail;
 1380     }
 1381 
 1382     /* Use the metadata's unit for printing by default */
 1383     unit = meta->base;
 1384 
 1385     /* Parse the input unit if provided */
 1386     if (unit_in != NULL && unit_in != Py_None) {
 1387         PyObject *strobj;
 1388         char *str = NULL;
 1389         Py_ssize_t len = 0;
 1390 
 1391         if (PyUnicode_Check(unit_in)) {
 1392             strobj = PyUnicode_AsASCIIString(unit_in);
 1393             if (strobj == NULL) {
 1394                 goto fail;
 1395             }
 1396         }
 1397         else {
 1398             strobj = unit_in;
 1399             Py_INCREF(strobj);
 1400         }
 1401 
 1402         if (PyBytes_AsStringAndSize(strobj, &str, &len) < 0) {
 1403             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         if (strcmp(str, "auto") == 0) {
 1412             unit = NPY_FR_ERROR;
 1413         }
 1414         else {
 1415             unit = parse_datetime_unit_from_string(str, len, NULL);
 1416             if (unit == NPY_FR_ERROR) {
 1417                 Py_DECREF(strobj);
 1418                 goto fail;
 1419             }
 1420         }
 1421         Py_DECREF(strobj);
 1422 
 1423         if (unit != NPY_FR_ERROR &&
 1424                 !can_cast_datetime64_units(meta->base, unit, casting)) {
 1425             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                         _datetime_strings[meta->base],
 1430                          npy_casting_to_string(casting));
 1431             goto fail;
 1432         }
 1433     }
 1434 
 1435     /* Get the input time zone */
 1436     if (timezone_obj != NULL) {
 1437         /* Convert to ASCII if it's unicode */
 1438         if (PyUnicode_Check(timezone_obj)) {
 1439             /* accept unicode input */
 1440             PyObject *obj_str;
 1441             obj_str = PyUnicode_AsASCIIString(timezone_obj);
 1442             if (obj_str == NULL) {
 1443                 goto fail;
 1444             }
 1445             Py_DECREF(timezone_obj);
 1446             timezone_obj = obj_str;
 1447         }
 1448 
 1449         /* Check for the supported string inputs */
 1450         if (PyBytes_Check(timezone_obj)) {
 1451             char *str;
 1452             Py_ssize_t len;
 1453 
 1454             if (PyBytes_AsStringAndSize(timezone_obj, &str, &len) < 0) {
 1455                 goto fail;
 1456             }
 1457 
 1458             if (strcmp(str, "local") == 0) {
 1459                 local = 1;
 1460                 utc = 0;
 1461                 Py_DECREF(timezone_obj);
 1462                 timezone_obj = NULL;
 1463             }
 1464             else if (strcmp(str, "UTC") == 0) {
 1465                 local = 0;
 1466                 utc = 1;
 1467                 Py_DECREF(timezone_obj);
 1468                 timezone_obj = NULL;
 1469             }
 1470             else if (strcmp(str, "naive") == 0) {
 1471                 local = 0;
 1472                 utc = 0;
 1473                 Py_DECREF(timezone_obj);
 1474                 timezone_obj = NULL;
 1475             }
 1476             else {
 1477                 PyErr_Format(PyExc_ValueError, "Unsupported timezone "
 1478                             "input string \"%s\"", str);
 1479                 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     strsize = get_datetime_iso_8601_strlen(local, unit);
 1490 #if defined(NPY_PY3K)
 1491     /*
 1492      * For Python3, allocate the output array as a UNICODE array, so
 1493      * that it will behave as strings properly
 1494      */
 1495     op_dtypes[1] = PyArray_DescrNewFromType(NPY_UNICODE);
 1496     if (op_dtypes[1] == NULL) {
 1497         goto fail;
 1498     }
 1499     op_dtypes[1]->elsize = strsize * 4;
 1500     /* This steals the UNICODE dtype reference in op_dtypes[1] */
 1501     op[1] = (PyArrayObject *)PyArray_NewLikeArray(op[0],
 1502                                         NPY_KEEPORDER, op_dtypes[1], 1);
 1503     if (op[1] == NULL) {
 1504         op_dtypes[1] = NULL;
 1505         goto fail;
 1506     }
 1507 #endif
 1508     /* Create the iteration string data type (always ASCII string) */
 1509     op_dtypes[1] = PyArray_DescrNewFromType(NPY_STRING);
 1510     if (op_dtypes[1] == NULL) {
 1511         goto fail;
 1512     }
 1513     op_dtypes[1]->elsize = strsize;
 1514 
 1515     flags = NPY_ITER_ZEROSIZE_OK|
 1516             NPY_ITER_BUFFERED;
 1517     op_flags[0] = NPY_ITER_READONLY|
 1518                   NPY_ITER_ALIGNED;
 1519     op_flags[1] = NPY_ITER_WRITEONLY|
 1520                   NPY_ITER_ALLOCATE;
 1521 
 1522     iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_UNSAFE_CASTING,
 1523                             op_flags, op_dtypes);
 1524     if (iter == NULL) {
 1525         goto fail;
 1526     }
 1527 
 1528     if (NpyIter_GetIterSize(iter) != 0) {
 1529         NpyIter_IterNextFunc *iternext;
 1530         char **dataptr;
 1531         npy_datetime dt;
 1532         npy_datetimestruct dts;
 1533 
 1534         iternext = NpyIter_GetIterNext(iter, NULL);
 1535         if (iternext == NULL) {
 1536             goto fail;
 1537         }
 1538         dataptr = NpyIter_GetDataPtrArray(iter);
 1539 
 1540         do {
 1541             int tzoffset = -1;
 1542 
 1543             /* Get the datetime */
 1544             dt = *(npy_datetime *)dataptr[0];
 1545 
 1546             /* Convert it to a struct */
 1547             if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) {
 1548                 goto fail;
 1549             }
 1550 
 1551             /* Get the tzoffset from the timezone if provided */
 1552             if (local && timezone_obj != NULL) {
 1553                 tzoffset = get_tzoffset_from_pytzinfo(timezone_obj, &dts);
 1554                 if (tzoffset == -1) {
 1555                     goto fail;
 1556                 }
 1557             }
 1558 
 1559             /* Zero the destination string completely */
 1560             memset(dataptr[1], 0, strsize);
 1561             /* Convert that into a string */
 1562             if (make_iso_8601_datetime(&dts, (char *)dataptr[1], strsize,
 1563                                 local, utc, unit, tzoffset, casting) < 0) {
 1564                 goto fail;
 1565             }
 1566         } while(iternext(iter));
 1567     }
 1568 
 1569     ret = NpyIter_GetOperandArray(iter)[1];
 1570     Py_INCREF(ret);
 1571 
 1572     Py_XDECREF(timezone_obj);
 1573     Py_XDECREF(op[0]);
 1574     Py_XDECREF(op[1]);
 1575     Py_XDECREF(op_dtypes[0]);
 1576     Py_XDECREF(op_dtypes[1]);
 1577     if (iter != NULL) {
 1578         NpyIter_Deallocate(iter);
 1579     }
 1580 
 1581     return PyArray_Return(ret);
 1582 
 1583 fail:
 1584     Py_XDECREF(timezone_obj);
 1585     Py_XDECREF(op[0]);
 1586     Py_XDECREF(op[1]);
 1587     Py_XDECREF(op_dtypes[0]);
 1588     Py_XDECREF(op_dtypes[1]);
 1589     if (iter != NULL) {
 1590         NpyIter_Deallocate(iter);
 1591     }
 1592 
 1593     return NULL;
 1594 }