"Fossies" - the Fresh Open Source Software Archive

Member "numpy-1.16.4/numpy/core/src/multiarray/datetime_busday.c" (22 Feb 2019, 42554 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_busday.c" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * This file implements business day functionality 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 #define NPY_NO_DEPRECATED_API NPY_API_VERSION
   14 #define _MULTIARRAYMODULE
   15 #include <numpy/arrayobject.h>
   16 
   17 #include "npy_config.h"
   18 #include "npy_pycompat.h"
   19 
   20 #include "numpy/arrayscalars.h"
   21 #include "lowlevel_strided_loops.h"
   22 #include "_datetime.h"
   23 #include "datetime_busday.h"
   24 #include "datetime_busdaycal.h"
   25 
   26 /* Gets the day of the week for a datetime64[D] value */
   27 static int
   28 get_day_of_week(npy_datetime date)
   29 {
   30     int day_of_week;
   31 
   32     /* Get the day of the week for 'date' (1970-01-05 is Monday) */
   33     day_of_week = (int)((date - 4) % 7);
   34     if (day_of_week < 0) {
   35         day_of_week += 7;
   36     }
   37 
   38     return day_of_week;
   39 }
   40 
   41 /*
   42  * Returns 1 if the date is a holiday (contained in the sorted
   43  * list of dates), 0 otherwise.
   44  *
   45  * The holidays list should be normalized, which means any NaT (not-a-time)
   46  * values, duplicates, and dates already excluded by the weekmask should
   47  * be removed, and the list should be sorted.
   48  */
   49 static int
   50 is_holiday(npy_datetime date,
   51             npy_datetime *holidays_begin, npy_datetime *holidays_end)
   52 {
   53     npy_datetime *trial;
   54 
   55     /* Simple binary search */
   56     while (holidays_begin < holidays_end) {
   57         trial = holidays_begin + (holidays_end - holidays_begin) / 2;
   58 
   59         if (date < *trial) {
   60             holidays_end = trial;
   61         }
   62         else if (date > *trial) {
   63             holidays_begin = trial + 1;
   64         }
   65         else {
   66             return 1;
   67         }
   68     }
   69 
   70     /* Not found */
   71     return 0;
   72 }
   73 
   74 /*
   75  * Finds the earliest holiday which is on or after 'date'. If 'date' does not
   76  * appear within the holiday range, returns 'holidays_begin' if 'date'
   77  * is before all holidays, or 'holidays_end' if 'date' is after all
   78  * holidays.
   79  *
   80  * To remove all the holidays before 'date' from a holiday range, do:
   81  *
   82  *      holidays_begin = find_holiday_earliest_on_or_after(date,
   83  *                                          holidays_begin, holidays_end);
   84  *
   85  * The holidays list should be normalized, which means any NaT (not-a-time)
   86  * values, duplicates, and dates already excluded by the weekmask should
   87  * be removed, and the list should be sorted.
   88  */
   89 static npy_datetime *
   90 find_earliest_holiday_on_or_after(npy_datetime date,
   91             npy_datetime *holidays_begin, npy_datetime *holidays_end)
   92 {
   93     npy_datetime *trial;
   94 
   95     /* Simple binary search */
   96     while (holidays_begin < holidays_end) {
   97         trial = holidays_begin + (holidays_end - holidays_begin) / 2;
   98 
   99         if (date < *trial) {
  100             holidays_end = trial;
  101         }
  102         else if (date > *trial) {
  103             holidays_begin = trial + 1;
  104         }
  105         else {
  106             return trial;
  107         }
  108     }
  109 
  110     return holidays_begin;
  111 }
  112 
  113 /*
  114  * Finds the earliest holiday which is after 'date'. If 'date' does not
  115  * appear within the holiday range, returns 'holidays_begin' if 'date'
  116  * is before all holidays, or 'holidays_end' if 'date' is after all
  117  * holidays.
  118  *
  119  * To remove all the holidays after 'date' from a holiday range, do:
  120  *
  121  *      holidays_end = find_holiday_earliest_after(date,
  122  *                                          holidays_begin, holidays_end);
  123  *
  124  * The holidays list should be normalized, which means any NaT (not-a-time)
  125  * values, duplicates, and dates already excluded by the weekmask should
  126  * be removed, and the list should be sorted.
  127  */
  128 static npy_datetime *
  129 find_earliest_holiday_after(npy_datetime date,
  130             npy_datetime *holidays_begin, npy_datetime *holidays_end)
  131 {
  132     npy_datetime *trial;
  133 
  134     /* Simple binary search */
  135     while (holidays_begin < holidays_end) {
  136         trial = holidays_begin + (holidays_end - holidays_begin) / 2;
  137 
  138         if (date < *trial) {
  139             holidays_end = trial;
  140         }
  141         else if (date > *trial) {
  142             holidays_begin = trial + 1;
  143         }
  144         else {
  145             return trial + 1;
  146         }
  147     }
  148 
  149     return holidays_begin;
  150 }
  151 
  152 /*
  153  * Applies the 'roll' strategy to 'date', placing the result in 'out'
  154  * and setting 'out_day_of_week' to the day of the week that results.
  155  *
  156  * Returns 0 on success, -1 on failure.
  157  */
  158 static int
  159 apply_business_day_roll(npy_datetime date, npy_datetime *out,
  160                     int *out_day_of_week,
  161                     NPY_BUSDAY_ROLL roll,
  162                     npy_bool *weekmask,
  163                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  164 {
  165     int day_of_week;
  166 
  167     /* Deal with NaT input */
  168     if (date == NPY_DATETIME_NAT) {
  169         *out = NPY_DATETIME_NAT;
  170         if (roll == NPY_BUSDAY_RAISE) {
  171             PyErr_SetString(PyExc_ValueError,
  172                     "NaT input in busday_offset");
  173             return -1;
  174         }
  175         else {
  176             return 0;
  177         }
  178     }
  179 
  180     /* Get the day of the week for 'date' */
  181     day_of_week = get_day_of_week(date);
  182 
  183     /* Apply the 'roll' if it's not a business day */
  184     if (weekmask[day_of_week] == 0 ||
  185                         is_holiday(date, holidays_begin, holidays_end)) {
  186         npy_datetime start_date = date;
  187         int start_day_of_week = day_of_week;
  188 
  189         switch (roll) {
  190             case NPY_BUSDAY_FOLLOWING:
  191             case NPY_BUSDAY_MODIFIEDFOLLOWING: {
  192                 do {
  193                     ++date;
  194                     if (++day_of_week == 7) {
  195                         day_of_week = 0;
  196                     }
  197                 } while (weekmask[day_of_week] == 0 ||
  198                             is_holiday(date, holidays_begin, holidays_end));
  199 
  200                 if (roll == NPY_BUSDAY_MODIFIEDFOLLOWING) {
  201                     /* If we crossed a month boundary, do preceding instead */
  202                     if (days_to_month_number(start_date) !=
  203                                 days_to_month_number(date)) {
  204                         date = start_date;
  205                         day_of_week = start_day_of_week;
  206 
  207                         do {
  208                             --date;
  209                             if (--day_of_week == -1) {
  210                                 day_of_week = 6;
  211                             }
  212                         } while (weekmask[day_of_week] == 0 ||
  213                             is_holiday(date, holidays_begin, holidays_end));
  214                     }
  215                 }
  216                 break;
  217             }
  218             case NPY_BUSDAY_PRECEDING:
  219             case NPY_BUSDAY_MODIFIEDPRECEDING: {
  220                 do {
  221                     --date;
  222                     if (--day_of_week == -1) {
  223                         day_of_week = 6;
  224                     }
  225                 } while (weekmask[day_of_week] == 0 ||
  226                             is_holiday(date, holidays_begin, holidays_end));
  227 
  228                 if (roll == NPY_BUSDAY_MODIFIEDPRECEDING) {
  229                     /* If we crossed a month boundary, do following instead */
  230                     if (days_to_month_number(start_date) !=
  231                                 days_to_month_number(date)) {
  232                         date = start_date;
  233                         day_of_week = start_day_of_week;
  234 
  235                         do {
  236                             ++date;
  237                             if (++day_of_week == 7) {
  238                                 day_of_week = 0;
  239                             }
  240                         } while (weekmask[day_of_week] == 0 ||
  241                             is_holiday(date, holidays_begin, holidays_end));
  242                     }
  243                 }
  244                 break;
  245             }
  246             case NPY_BUSDAY_NAT: {
  247                 date = NPY_DATETIME_NAT;
  248                 break;
  249             }
  250             case NPY_BUSDAY_RAISE: {
  251                 *out = NPY_DATETIME_NAT;
  252                 PyErr_SetString(PyExc_ValueError,
  253                         "Non-business day date in busday_offset");
  254                 return -1;
  255             }
  256         }
  257     }
  258 
  259     *out = date;
  260     *out_day_of_week = day_of_week;
  261 
  262     return 0;
  263 }
  264 
  265 /*
  266  * Applies a single business day offset. See the function
  267  * business_day_offset for the meaning of all the parameters.
  268  *
  269  * Returns 0 on success, -1 on failure.
  270  */
  271 static int
  272 apply_business_day_offset(npy_datetime date, npy_int64 offset,
  273                     npy_datetime *out,
  274                     NPY_BUSDAY_ROLL roll,
  275                     npy_bool *weekmask, int busdays_in_weekmask,
  276                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  277 {
  278     int day_of_week = 0;
  279     npy_datetime *holidays_temp;
  280 
  281     /* Roll the date to a business day */
  282     if (apply_business_day_roll(date, &date, &day_of_week,
  283                                 roll,
  284                                 weekmask,
  285                                 holidays_begin, holidays_end) < 0) {
  286         return -1;
  287     }
  288 
  289     /* If we get a NaT, just return it */
  290     if (date == NPY_DATETIME_NAT) {
  291         *out = NPY_DATETIME_NAT;
  292         return 0;
  293     }
  294 
  295     /* Now we're on a valid business day */
  296     if (offset > 0) {
  297         /* Remove any earlier holidays */
  298         holidays_begin = find_earliest_holiday_on_or_after(date,
  299                                             holidays_begin, holidays_end);
  300 
  301         /* Jump by as many weeks as we can */
  302         date += (offset / busdays_in_weekmask) * 7;
  303         offset = offset % busdays_in_weekmask;
  304 
  305         /* Adjust based on the number of holidays we crossed */
  306         holidays_temp = find_earliest_holiday_after(date,
  307                                             holidays_begin, holidays_end);
  308         offset += holidays_temp - holidays_begin;
  309         holidays_begin = holidays_temp;
  310 
  311         /* Step until we use up the rest of the offset */
  312         while (offset > 0) {
  313             ++date;
  314             if (++day_of_week == 7) {
  315                 day_of_week = 0;
  316             }
  317             if (weekmask[day_of_week] && !is_holiday(date,
  318                                             holidays_begin, holidays_end)) {
  319                 offset--;
  320             }
  321         }
  322     }
  323     else if (offset < 0) {
  324         /* Remove any later holidays */
  325         holidays_end = find_earliest_holiday_after(date,
  326                                             holidays_begin, holidays_end);
  327 
  328         /* Jump by as many weeks as we can */
  329         date += (offset / busdays_in_weekmask) * 7;
  330         offset = offset % busdays_in_weekmask;
  331 
  332         /* Adjust based on the number of holidays we crossed */
  333         holidays_temp = find_earliest_holiday_on_or_after(date,
  334                                             holidays_begin, holidays_end);
  335         offset -= holidays_end - holidays_temp;
  336         holidays_end = holidays_temp;
  337 
  338         /* Step until we use up the rest of the offset */
  339         while (offset < 0) {
  340             --date;
  341             if (--day_of_week == -1) {
  342                 day_of_week = 6;
  343             }
  344             if (weekmask[day_of_week] && !is_holiday(date,
  345                                             holidays_begin, holidays_end)) {
  346                 offset++;
  347             }
  348         }
  349     }
  350 
  351     *out = date;
  352     return 0;
  353 }
  354 
  355 /*
  356  * Applies a single business day count operation. See the function
  357  * business_day_count for the meaning of all the parameters.
  358  *
  359  * Returns 0 on success, -1 on failure.
  360  */
  361 static int
  362 apply_business_day_count(npy_datetime date_begin, npy_datetime date_end,
  363                     npy_int64 *out,
  364                     npy_bool *weekmask, int busdays_in_weekmask,
  365                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  366 {
  367     npy_int64 count, whole_weeks;
  368     int day_of_week = 0;
  369     int swapped = 0;
  370 
  371     /* If we get a NaT, raise an error */
  372     if (date_begin == NPY_DATETIME_NAT || date_end == NPY_DATETIME_NAT) {
  373         PyErr_SetString(PyExc_ValueError,
  374                 "Cannot compute a business day count with a NaT (not-a-time) "
  375                 "date");
  376         return -1;
  377     }
  378 
  379     /* Trivial empty date range */
  380     if (date_begin == date_end) {
  381         *out = 0;
  382         return 0;
  383     }
  384     else if (date_begin > date_end) {
  385         npy_datetime tmp = date_begin;
  386         date_begin = date_end;
  387         date_end = tmp;
  388         swapped = 1;
  389     }
  390 
  391     /* Remove any earlier holidays */
  392     holidays_begin = find_earliest_holiday_on_or_after(date_begin,
  393                                         holidays_begin, holidays_end);
  394     /* Remove any later holidays */
  395     holidays_end = find_earliest_holiday_on_or_after(date_end,
  396                                         holidays_begin, holidays_end);
  397 
  398     /* Start the count as negative the number of holidays in the range */
  399     count = -(holidays_end - holidays_begin);
  400 
  401     /* Add the whole weeks between date_begin and date_end */
  402     whole_weeks = (date_end - date_begin) / 7;
  403     count += whole_weeks * busdays_in_weekmask;
  404     date_begin += whole_weeks * 7;
  405 
  406     if (date_begin < date_end) {
  407         /* Get the day of the week for 'date_begin' */
  408         day_of_week = get_day_of_week(date_begin);
  409 
  410         /* Count the remaining days one by one */
  411         while (date_begin < date_end) {
  412             if (weekmask[day_of_week]) {
  413                 count++;
  414             }
  415             ++date_begin;
  416             if (++day_of_week == 7) {
  417                 day_of_week = 0;
  418             }
  419         }
  420     }
  421 
  422     if (swapped) {
  423         count = -count;
  424     }
  425 
  426     *out = count;
  427     return 0;
  428 }
  429 
  430 /*
  431  * Applies the given offsets in business days to the dates provided.
  432  * This is the low-level function which requires already cleaned input
  433  * data.
  434  *
  435  * dates:    An array of dates with 'datetime64[D]' data type.
  436  * offsets:  An array safely convertible into type int64.
  437  * out:      Either NULL, or an array with 'datetime64[D]' data type
  438  *              in which to place the resulting dates.
  439  * roll:     A rule for how to treat non-business day dates.
  440  * weekmask: A 7-element boolean mask, 1 for possible business days and 0
  441  *              for non-business days.
  442  * busdays_in_weekmask: A count of how many 1's there are in weekmask.
  443  * holidays_begin/holidays_end: A sorted list of dates matching '[D]'
  444  *           unit metadata, with any dates falling on a day of the
  445  *           week without weekmask[i] == 1 already filtered out.
  446  *
  447  * For each (date, offset) in the broadcasted pair of (dates, offsets),
  448  * does the following:
  449  *  + Applies the 'roll' rule to the date to either produce NaT, raise
  450  *    an exception, or land on a valid business day.
  451  *  + Adds 'offset' business days to the valid business day found.
  452  *  + Sets the value in 'out' if provided, or the allocated output array
  453  *    otherwise.
  454  */
  455 NPY_NO_EXPORT PyArrayObject *
  456 business_day_offset(PyArrayObject *dates, PyArrayObject *offsets,
  457                     PyArrayObject *out,
  458                     NPY_BUSDAY_ROLL roll,
  459                     npy_bool *weekmask, int busdays_in_weekmask,
  460                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  461 {
  462     PyArray_DatetimeMetaData temp_meta;
  463     PyArray_Descr *dtypes[3] = {NULL, NULL, NULL};
  464 
  465     NpyIter *iter = NULL;
  466     PyArrayObject *op[3] = {NULL, NULL, NULL};
  467     npy_uint32 op_flags[3], flags;
  468 
  469     PyArrayObject *ret = NULL;
  470 
  471     if (busdays_in_weekmask == 0) {
  472         PyErr_SetString(PyExc_ValueError,
  473                 "the business day weekmask must have at least one "
  474                 "valid business day");
  475         return NULL;
  476     }
  477 
  478     /* First create the data types for dates and offsets */
  479     temp_meta.base = NPY_FR_D;
  480     temp_meta.num = 1;
  481     dtypes[0] = create_datetime_dtype(NPY_DATETIME, &temp_meta);
  482     if (dtypes[0] == NULL) {
  483         goto fail;
  484     }
  485     dtypes[1] = PyArray_DescrFromType(NPY_INT64);
  486     if (dtypes[1] == NULL) {
  487         goto fail;
  488     }
  489     dtypes[2] = dtypes[0];
  490     Py_INCREF(dtypes[2]);
  491 
  492     /* Set up the iterator parameters */
  493     flags = NPY_ITER_EXTERNAL_LOOP|
  494             NPY_ITER_BUFFERED|
  495             NPY_ITER_ZEROSIZE_OK;
  496     op[0] = dates;
  497     op_flags[0] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
  498     op[1] = offsets;
  499     op_flags[1] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
  500     op[2] = out;
  501     op_flags[2] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE | NPY_ITER_ALIGNED;
  502 
  503     /* Allocate the iterator */
  504     iter = NpyIter_MultiNew(3, op, flags, NPY_KEEPORDER, NPY_SAFE_CASTING,
  505                             op_flags, dtypes);
  506     if (iter == NULL) {
  507         goto fail;
  508     }
  509 
  510     /* Loop over all elements */
  511     if (NpyIter_GetIterSize(iter) > 0) {
  512         NpyIter_IterNextFunc *iternext;
  513         char **dataptr;
  514         npy_intp *strideptr, *innersizeptr;
  515 
  516         iternext = NpyIter_GetIterNext(iter, NULL);
  517         if (iternext == NULL) {
  518             goto fail;
  519         }
  520         dataptr = NpyIter_GetDataPtrArray(iter);
  521         strideptr = NpyIter_GetInnerStrideArray(iter);
  522         innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
  523 
  524         do {
  525             char *data_dates = dataptr[0];
  526             char *data_offsets = dataptr[1];
  527             char *data_out = dataptr[2];
  528             npy_intp stride_dates = strideptr[0];
  529             npy_intp stride_offsets = strideptr[1];
  530             npy_intp stride_out = strideptr[2];
  531             npy_intp count = *innersizeptr;
  532 
  533             while (count--) {
  534                 if (apply_business_day_offset(*(npy_int64 *)data_dates,
  535                                        *(npy_int64 *)data_offsets,
  536                                        (npy_int64 *)data_out,
  537                                        roll,
  538                                        weekmask, busdays_in_weekmask,
  539                                        holidays_begin, holidays_end) < 0) {
  540                     goto fail;
  541                 }
  542 
  543                 data_dates += stride_dates;
  544                 data_offsets += stride_offsets;
  545                 data_out += stride_out;
  546             }
  547         } while (iternext(iter));
  548     }
  549 
  550     /* Get the return object from the iterator */
  551     ret = NpyIter_GetOperandArray(iter)[2];
  552     Py_INCREF(ret);
  553 
  554     goto finish;
  555 
  556 fail:
  557     Py_XDECREF(ret);
  558     ret = NULL;
  559 
  560 finish:
  561     Py_XDECREF(dtypes[0]);
  562     Py_XDECREF(dtypes[1]);
  563     Py_XDECREF(dtypes[2]);
  564     if (iter != NULL) {
  565         if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
  566             Py_XDECREF(ret);
  567             ret = NULL;
  568         }
  569     }
  570     return ret;
  571 }
  572 
  573 /*
  574  * Counts the number of business days between two dates, not including
  575  * the end date. This is the low-level function which requires already
  576  * cleaned input data.
  577  *
  578  * If dates_begin is before dates_end, the result is positive.  If
  579  * dates_begin is after dates_end, it is negative.
  580  *
  581  * dates_begin:  An array of dates with 'datetime64[D]' data type.
  582  * dates_end:    An array of dates with 'datetime64[D]' data type.
  583  * out:      Either NULL, or an array with 'int64' data type
  584  *              in which to place the resulting dates.
  585  * weekmask: A 7-element boolean mask, 1 for possible business days and 0
  586  *              for non-business days.
  587  * busdays_in_weekmask: A count of how many 1's there are in weekmask.
  588  * holidays_begin/holidays_end: A sorted list of dates matching '[D]'
  589  *           unit metadata, with any dates falling on a day of the
  590  *           week without weekmask[i] == 1 already filtered out.
  591  */
  592 NPY_NO_EXPORT PyArrayObject *
  593 business_day_count(PyArrayObject *dates_begin, PyArrayObject *dates_end,
  594                     PyArrayObject *out,
  595                     npy_bool *weekmask, int busdays_in_weekmask,
  596                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  597 {
  598     PyArray_DatetimeMetaData temp_meta;
  599     PyArray_Descr *dtypes[3] = {NULL, NULL, NULL};
  600 
  601     NpyIter *iter = NULL;
  602     PyArrayObject *op[3] = {NULL, NULL, NULL};
  603     npy_uint32 op_flags[3], flags;
  604 
  605     PyArrayObject *ret = NULL;
  606 
  607     if (busdays_in_weekmask == 0) {
  608         PyErr_SetString(PyExc_ValueError,
  609                 "the business day weekmask must have at least one "
  610                 "valid business day");
  611         return NULL;
  612     }
  613 
  614     /* First create the data types for the dates and the int64 output */
  615     temp_meta.base = NPY_FR_D;
  616     temp_meta.num = 1;
  617     dtypes[0] = create_datetime_dtype(NPY_DATETIME, &temp_meta);
  618     if (dtypes[0] == NULL) {
  619         goto fail;
  620     }
  621     dtypes[1] = dtypes[0];
  622     Py_INCREF(dtypes[1]);
  623     dtypes[2] = PyArray_DescrFromType(NPY_INT64);
  624     if (dtypes[2] == NULL) {
  625         goto fail;
  626     }
  627 
  628     /* Set up the iterator parameters */
  629     flags = NPY_ITER_EXTERNAL_LOOP|
  630             NPY_ITER_BUFFERED|
  631             NPY_ITER_ZEROSIZE_OK;
  632     op[0] = dates_begin;
  633     op_flags[0] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
  634     op[1] = dates_end;
  635     op_flags[1] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
  636     op[2] = out;
  637     op_flags[2] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE | NPY_ITER_ALIGNED;
  638 
  639     /* Allocate the iterator */
  640     iter = NpyIter_MultiNew(3, op, flags, NPY_KEEPORDER, NPY_SAFE_CASTING,
  641                             op_flags, dtypes);
  642     if (iter == NULL) {
  643         goto fail;
  644     }
  645 
  646     /* Loop over all elements */
  647     if (NpyIter_GetIterSize(iter) > 0) {
  648         NpyIter_IterNextFunc *iternext;
  649         char **dataptr;
  650         npy_intp *strideptr, *innersizeptr;
  651 
  652         iternext = NpyIter_GetIterNext(iter, NULL);
  653         if (iternext == NULL) {
  654             goto fail;
  655         }
  656         dataptr = NpyIter_GetDataPtrArray(iter);
  657         strideptr = NpyIter_GetInnerStrideArray(iter);
  658         innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
  659 
  660         do {
  661             char *data_dates_begin = dataptr[0];
  662             char *data_dates_end = dataptr[1];
  663             char *data_out = dataptr[2];
  664             npy_intp stride_dates_begin = strideptr[0];
  665             npy_intp stride_dates_end = strideptr[1];
  666             npy_intp stride_out = strideptr[2];
  667             npy_intp count = *innersizeptr;
  668 
  669             while (count--) {
  670                 if (apply_business_day_count(*(npy_int64 *)data_dates_begin,
  671                                        *(npy_int64 *)data_dates_end,
  672                                        (npy_int64 *)data_out,
  673                                        weekmask, busdays_in_weekmask,
  674                                        holidays_begin, holidays_end) < 0) {
  675                     goto fail;
  676                 }
  677 
  678                 data_dates_begin += stride_dates_begin;
  679                 data_dates_end += stride_dates_end;
  680                 data_out += stride_out;
  681             }
  682         } while (iternext(iter));
  683     }
  684 
  685     /* Get the return object from the iterator */
  686     ret = NpyIter_GetOperandArray(iter)[2];
  687     Py_INCREF(ret);
  688 
  689     goto finish;
  690 
  691 fail:
  692     Py_XDECREF(ret);
  693     ret = NULL;
  694 
  695 finish:
  696     Py_XDECREF(dtypes[0]);
  697     Py_XDECREF(dtypes[1]);
  698     Py_XDECREF(dtypes[2]);
  699     if (iter != NULL) {
  700         if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
  701             Py_XDECREF(ret);
  702             ret = NULL;
  703         }
  704     }
  705     return ret;
  706 }
  707 
  708 /*
  709  * Returns a boolean array with True for input dates which are valid
  710  * business days, and False for dates which are not. This is the
  711  * low-level function which requires already cleaned input data.
  712  *
  713  * dates:  An array of dates with 'datetime64[D]' data type.
  714  * out:      Either NULL, or an array with 'bool' data type
  715  *              in which to place the resulting dates.
  716  * weekmask: A 7-element boolean mask, 1 for possible business days and 0
  717  *              for non-business days.
  718  * busdays_in_weekmask: A count of how many 1's there are in weekmask.
  719  * holidays_begin/holidays_end: A sorted list of dates matching '[D]'
  720  *           unit metadata, with any dates falling on a day of the
  721  *           week without weekmask[i] == 1 already filtered out.
  722  */
  723 NPY_NO_EXPORT PyArrayObject *
  724 is_business_day(PyArrayObject *dates, PyArrayObject *out,
  725                     npy_bool *weekmask, int busdays_in_weekmask,
  726                     npy_datetime *holidays_begin, npy_datetime *holidays_end)
  727 {
  728     PyArray_DatetimeMetaData temp_meta;
  729     PyArray_Descr *dtypes[2] = {NULL, NULL};
  730 
  731     NpyIter *iter = NULL;
  732     PyArrayObject *op[2] = {NULL, NULL};
  733     npy_uint32 op_flags[2], flags;
  734 
  735     PyArrayObject *ret = NULL;
  736 
  737     if (busdays_in_weekmask == 0) {
  738         PyErr_SetString(PyExc_ValueError,
  739                 "the business day weekmask must have at least one "
  740                 "valid business day");
  741         return NULL;
  742     }
  743 
  744     /* First create the data types for the dates and the bool output */
  745     temp_meta.base = NPY_FR_D;
  746     temp_meta.num = 1;
  747     dtypes[0] = create_datetime_dtype(NPY_DATETIME, &temp_meta);
  748     if (dtypes[0] == NULL) {
  749         goto fail;
  750     }
  751     dtypes[1] = PyArray_DescrFromType(NPY_BOOL);
  752     if (dtypes[1] == NULL) {
  753         goto fail;
  754     }
  755 
  756     /* Set up the iterator parameters */
  757     flags = NPY_ITER_EXTERNAL_LOOP|
  758             NPY_ITER_BUFFERED|
  759             NPY_ITER_ZEROSIZE_OK;
  760     op[0] = dates;
  761     op_flags[0] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
  762     op[1] = out;
  763     op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE | NPY_ITER_ALIGNED;
  764 
  765     /* Allocate the iterator */
  766     iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_SAFE_CASTING,
  767                             op_flags, dtypes);
  768     if (iter == NULL) {
  769         goto fail;
  770     }
  771 
  772     /* Loop over all elements */
  773     if (NpyIter_GetIterSize(iter) > 0) {
  774         NpyIter_IterNextFunc *iternext;
  775         char **dataptr;
  776         npy_intp *strideptr, *innersizeptr;
  777 
  778         iternext = NpyIter_GetIterNext(iter, NULL);
  779         if (iternext == NULL) {
  780             goto fail;
  781         }
  782         dataptr = NpyIter_GetDataPtrArray(iter);
  783         strideptr = NpyIter_GetInnerStrideArray(iter);
  784         innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
  785 
  786         do {
  787             char *data_dates = dataptr[0];
  788             char *data_out = dataptr[1];
  789             npy_intp stride_dates = strideptr[0];
  790             npy_intp stride_out = strideptr[1];
  791             npy_intp count = *innersizeptr;
  792 
  793             npy_datetime date;
  794             int day_of_week;
  795 
  796             while (count--) {
  797                 /* Check if it's a business day */
  798                 date = *(npy_datetime *)data_dates;
  799                 day_of_week = get_day_of_week(date);
  800                 *(npy_bool *)data_out = weekmask[day_of_week] &&
  801                                         !is_holiday(date,
  802                                             holidays_begin, holidays_end) &&
  803                                         date != NPY_DATETIME_NAT;
  804 
  805                 data_dates += stride_dates;
  806                 data_out += stride_out;
  807             }
  808         } while (iternext(iter));
  809     }
  810 
  811     /* Get the return object from the iterator */
  812     ret = NpyIter_GetOperandArray(iter)[1];
  813     Py_INCREF(ret);
  814 
  815     goto finish;
  816 
  817 fail:
  818     Py_XDECREF(ret);
  819     ret = NULL;
  820 
  821 finish:
  822     Py_XDECREF(dtypes[0]);
  823     Py_XDECREF(dtypes[1]);
  824     if (iter != NULL) {
  825         if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
  826             Py_XDECREF(ret);
  827             ret = NULL;
  828         }
  829     }
  830     return ret;
  831 }
  832 
  833 static int
  834 PyArray_BusDayRollConverter(PyObject *roll_in, NPY_BUSDAY_ROLL *roll)
  835 {
  836     PyObject *obj = roll_in;
  837     char *str;
  838     Py_ssize_t len;
  839 
  840     /* Make obj into an ASCII string */
  841     Py_INCREF(obj);
  842     if (PyUnicode_Check(obj)) {
  843         /* accept unicode input */
  844         PyObject *obj_str;
  845         obj_str = PyUnicode_AsASCIIString(obj);
  846         if (obj_str == NULL) {
  847             Py_DECREF(obj);
  848             return 0;
  849         }
  850         Py_DECREF(obj);
  851         obj = obj_str;
  852     }
  853 
  854     if (PyBytes_AsStringAndSize(obj, &str, &len) < 0) {
  855         Py_DECREF(obj);
  856         return 0;
  857     }
  858 
  859     /* Use switch statements to quickly isolate the right enum value */
  860     switch (str[0]) {
  861         case 'b':
  862             if (strcmp(str, "backward") == 0) {
  863                 *roll = NPY_BUSDAY_BACKWARD;
  864                 goto finish;
  865             }
  866             break;
  867         case 'f':
  868             if (len > 2) switch (str[2]) {
  869                 case 'r':
  870                     if (strcmp(str, "forward") == 0) {
  871                         *roll = NPY_BUSDAY_FORWARD;
  872                         goto finish;
  873                     }
  874                     break;
  875                 case 'l':
  876                     if (strcmp(str, "following") == 0) {
  877                         *roll = NPY_BUSDAY_FOLLOWING;
  878                         goto finish;
  879                     }
  880                     break;
  881             }
  882             break;
  883         case 'm':
  884             if (len > 8) switch (str[8]) {
  885                 case 'f':
  886                     if (strcmp(str, "modifiedfollowing") == 0) {
  887                         *roll = NPY_BUSDAY_MODIFIEDFOLLOWING;
  888                         goto finish;
  889                     }
  890                     break;
  891                 case 'p':
  892                     if (strcmp(str, "modifiedpreceding") == 0) {
  893                         *roll = NPY_BUSDAY_MODIFIEDPRECEDING;
  894                         goto finish;
  895                     }
  896                     break;
  897             }
  898             break;
  899         case 'n':
  900             if (strcmp(str, "nat") == 0) {
  901                 *roll = NPY_BUSDAY_NAT;
  902                 goto finish;
  903             }
  904             break;
  905         case 'p':
  906             if (strcmp(str, "preceding") == 0) {
  907                 *roll = NPY_BUSDAY_PRECEDING;
  908                 goto finish;
  909             }
  910             break;
  911         case 'r':
  912             if (strcmp(str, "raise") == 0) {
  913                 *roll = NPY_BUSDAY_RAISE;
  914                 goto finish;
  915             }
  916             break;
  917     }
  918 
  919     PyErr_Format(PyExc_ValueError,
  920             "Invalid business day roll parameter \"%s\"",
  921             str);
  922     Py_DECREF(obj);
  923     return 0;
  924 
  925 finish:
  926     Py_DECREF(obj);
  927     return 1;
  928 }
  929 
  930 /*
  931  * This is the 'busday_offset' function exposed for calling
  932  * from Python.
  933  */
  934 NPY_NO_EXPORT PyObject *
  935 array_busday_offset(PyObject *NPY_UNUSED(self),
  936                       PyObject *args, PyObject *kwds)
  937 {
  938     char *kwlist[] = {"dates", "offsets", "roll",
  939                       "weekmask", "holidays", "busdaycal", "out", NULL};
  940 
  941     PyObject *dates_in = NULL, *offsets_in = NULL, *out_in = NULL;
  942 
  943     PyArrayObject *dates = NULL, *offsets = NULL, *out = NULL, *ret;
  944     NPY_BUSDAY_ROLL roll = NPY_BUSDAY_RAISE;
  945     npy_bool weekmask[7] = {2, 1, 1, 1, 1, 0, 0};
  946     NpyBusDayCalendar *busdaycal = NULL;
  947     int i, busdays_in_weekmask;
  948     npy_holidayslist holidays = {NULL, NULL};
  949     int allocated_holidays = 1;
  950 
  951     if (!PyArg_ParseTupleAndKeywords(args, kwds,
  952                                     "OO|O&O&O&O!O:busday_offset", kwlist,
  953                                     &dates_in,
  954                                     &offsets_in,
  955                                     &PyArray_BusDayRollConverter, &roll,
  956                                     &PyArray_WeekMaskConverter, &weekmask[0],
  957                                     &PyArray_HolidaysConverter, &holidays,
  958                                     &NpyBusDayCalendar_Type, &busdaycal,
  959                                     &out_in)) {
  960         goto fail;
  961     }
  962 
  963     /* Make sure only one of the weekmask/holidays and busdaycal is supplied */
  964     if (busdaycal != NULL) {
  965         if (weekmask[0] != 2 || holidays.begin != NULL) {
  966             PyErr_SetString(PyExc_ValueError,
  967                     "Cannot supply both the weekmask/holidays and the "
  968                     "busdaycal parameters to busday_offset()");
  969             goto fail;
  970         }
  971 
  972         /* Indicate that the holidays weren't allocated by us */
  973         allocated_holidays = 0;
  974 
  975         /* Copy the private normalized weekmask/holidays data */
  976         holidays = busdaycal->holidays;
  977         busdays_in_weekmask = busdaycal->busdays_in_weekmask;
  978         memcpy(weekmask, busdaycal->weekmask, 7);
  979     }
  980     else {
  981         /*
  982          * Fix up the weekmask from the uninitialized
  983          * signal value to a proper default.
  984          */
  985         if (weekmask[0] == 2) {
  986             weekmask[0] = 1;
  987         }
  988 
  989         /* Count the number of business days in a week */
  990         busdays_in_weekmask = 0;
  991         for (i = 0; i < 7; ++i) {
  992             busdays_in_weekmask += weekmask[i];
  993         }
  994 
  995         /* The holidays list must be normalized before using it */
  996         normalize_holidays_list(&holidays, weekmask);
  997     }
  998 
  999     /* Make 'dates' into an array */
 1000     if (PyArray_Check(dates_in)) {
 1001         dates = (PyArrayObject *)dates_in;
 1002         Py_INCREF(dates);
 1003     }
 1004     else {
 1005         PyArray_Descr *datetime_dtype;
 1006 
 1007         /* Use the datetime dtype with generic units so it fills it in */
 1008         datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
 1009         if (datetime_dtype == NULL) {
 1010             goto fail;
 1011         }
 1012 
 1013         /* This steals the datetime_dtype reference */
 1014         dates = (PyArrayObject *)PyArray_FromAny(dates_in, datetime_dtype,
 1015                                                 0, 0, 0, dates_in);
 1016         if (dates == NULL) {
 1017             goto fail;
 1018         }
 1019     }
 1020 
 1021     /* Make 'offsets' into an array */
 1022     offsets = (PyArrayObject *)PyArray_FromAny(offsets_in,
 1023                             PyArray_DescrFromType(NPY_INT64),
 1024                             0, 0, 0, offsets_in);
 1025     if (offsets == NULL) {
 1026         goto fail;
 1027     }
 1028 
 1029     /* Make sure 'out' is an array if it's provided */
 1030     if (out_in != NULL) {
 1031         if (!PyArray_Check(out_in)) {
 1032             PyErr_SetString(PyExc_ValueError,
 1033                     "busday_offset: must provide a NumPy array for 'out'");
 1034             goto fail;
 1035         }
 1036         out = (PyArrayObject *)out_in;
 1037     }
 1038 
 1039     ret = business_day_offset(dates, offsets, out, roll,
 1040                     weekmask, busdays_in_weekmask,
 1041                     holidays.begin, holidays.end);
 1042 
 1043     Py_DECREF(dates);
 1044     Py_DECREF(offsets);
 1045     if (allocated_holidays && holidays.begin != NULL) {
 1046         PyArray_free(holidays.begin);
 1047     }
 1048 
 1049     return out == NULL ? PyArray_Return(ret) : (PyObject *)ret;
 1050 
 1051 fail:
 1052     Py_XDECREF(dates);
 1053     Py_XDECREF(offsets);
 1054     if (allocated_holidays && holidays.begin != NULL) {
 1055         PyArray_free(holidays.begin);
 1056     }
 1057 
 1058     return NULL;
 1059 }
 1060 
 1061 /*
 1062  * This is the 'busday_count' function exposed for calling
 1063  * from Python.
 1064  */
 1065 NPY_NO_EXPORT PyObject *
 1066 array_busday_count(PyObject *NPY_UNUSED(self),
 1067                       PyObject *args, PyObject *kwds)
 1068 {
 1069     char *kwlist[] = {"begindates", "enddates",
 1070                       "weekmask", "holidays", "busdaycal", "out", NULL};
 1071 
 1072     PyObject *dates_begin_in = NULL, *dates_end_in = NULL, *out_in = NULL;
 1073 
 1074     PyArrayObject *dates_begin = NULL, *dates_end = NULL, *out = NULL, *ret;
 1075     npy_bool weekmask[7] = {2, 1, 1, 1, 1, 0, 0};
 1076     NpyBusDayCalendar *busdaycal = NULL;
 1077     int i, busdays_in_weekmask;
 1078     npy_holidayslist holidays = {NULL, NULL};
 1079     int allocated_holidays = 1;
 1080 
 1081     if (!PyArg_ParseTupleAndKeywords(args, kwds,
 1082                                     "OO|O&O&O!O:busday_count", kwlist,
 1083                                     &dates_begin_in,
 1084                                     &dates_end_in,
 1085                                     &PyArray_WeekMaskConverter, &weekmask[0],
 1086                                     &PyArray_HolidaysConverter, &holidays,
 1087                                     &NpyBusDayCalendar_Type, &busdaycal,
 1088                                     &out_in)) {
 1089         goto fail;
 1090     }
 1091 
 1092     /* Make sure only one of the weekmask/holidays and busdaycal is supplied */
 1093     if (busdaycal != NULL) {
 1094         if (weekmask[0] != 2 || holidays.begin != NULL) {
 1095             PyErr_SetString(PyExc_ValueError,
 1096                     "Cannot supply both the weekmask/holidays and the "
 1097                     "busdaycal parameters to busday_count()");
 1098             goto fail;
 1099         }
 1100 
 1101         /* Indicate that the holidays weren't allocated by us */
 1102         allocated_holidays = 0;
 1103 
 1104         /* Copy the private normalized weekmask/holidays data */
 1105         holidays = busdaycal->holidays;
 1106         busdays_in_weekmask = busdaycal->busdays_in_weekmask;
 1107         memcpy(weekmask, busdaycal->weekmask, 7);
 1108     }
 1109     else {
 1110         /*
 1111          * Fix up the weekmask from the uninitialized
 1112          * signal value to a proper default.
 1113          */
 1114         if (weekmask[0] == 2) {
 1115             weekmask[0] = 1;
 1116         }
 1117 
 1118         /* Count the number of business days in a week */
 1119         busdays_in_weekmask = 0;
 1120         for (i = 0; i < 7; ++i) {
 1121             busdays_in_weekmask += weekmask[i];
 1122         }
 1123 
 1124         /* The holidays list must be normalized before using it */
 1125         normalize_holidays_list(&holidays, weekmask);
 1126     }
 1127 
 1128     /* Make 'dates_begin' into an array */
 1129     if (PyArray_Check(dates_begin_in)) {
 1130         dates_begin = (PyArrayObject *)dates_begin_in;
 1131         Py_INCREF(dates_begin);
 1132     }
 1133     else {
 1134         PyArray_Descr *datetime_dtype;
 1135 
 1136         /* Use the datetime dtype with generic units so it fills it in */
 1137         datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
 1138         if (datetime_dtype == NULL) {
 1139             goto fail;
 1140         }
 1141 
 1142         /* This steals the datetime_dtype reference */
 1143         dates_begin = (PyArrayObject *)PyArray_FromAny(dates_begin_in,
 1144                                                 datetime_dtype,
 1145                                                 0, 0, 0, dates_begin_in);
 1146         if (dates_begin == NULL) {
 1147             goto fail;
 1148         }
 1149     }
 1150 
 1151     /* Make 'dates_end' into an array */
 1152     if (PyArray_Check(dates_end_in)) {
 1153         dates_end = (PyArrayObject *)dates_end_in;
 1154         Py_INCREF(dates_end);
 1155     }
 1156     else {
 1157         PyArray_Descr *datetime_dtype;
 1158 
 1159         /* Use the datetime dtype with generic units so it fills it in */
 1160         datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
 1161         if (datetime_dtype == NULL) {
 1162             goto fail;
 1163         }
 1164 
 1165         /* This steals the datetime_dtype reference */
 1166         dates_end = (PyArrayObject *)PyArray_FromAny(dates_end_in,
 1167                                                 datetime_dtype,
 1168                                                 0, 0, 0, dates_end_in);
 1169         if (dates_end == NULL) {
 1170             goto fail;
 1171         }
 1172     }
 1173 
 1174     /* Make sure 'out' is an array if it's provided */
 1175     if (out_in != NULL) {
 1176         if (!PyArray_Check(out_in)) {
 1177             PyErr_SetString(PyExc_ValueError,
 1178                     "busday_offset: must provide a NumPy array for 'out'");
 1179             goto fail;
 1180         }
 1181         out = (PyArrayObject *)out_in;
 1182     }
 1183 
 1184     ret = business_day_count(dates_begin, dates_end, out,
 1185                     weekmask, busdays_in_weekmask,
 1186                     holidays.begin, holidays.end);
 1187 
 1188     Py_DECREF(dates_begin);
 1189     Py_DECREF(dates_end);
 1190     if (allocated_holidays && holidays.begin != NULL) {
 1191         PyArray_free(holidays.begin);
 1192     }
 1193 
 1194     return out == NULL ? PyArray_Return(ret) : (PyObject *)ret;
 1195 
 1196 fail:
 1197     Py_XDECREF(dates_begin);
 1198     Py_XDECREF(dates_end);
 1199     if (allocated_holidays && holidays.begin != NULL) {
 1200         PyArray_free(holidays.begin);
 1201     }
 1202 
 1203     return NULL;
 1204 }
 1205 
 1206 /*
 1207  * This is the 'is_busday' function exposed for calling
 1208  * from Python.
 1209  */
 1210 NPY_NO_EXPORT PyObject *
 1211 array_is_busday(PyObject *NPY_UNUSED(self),
 1212                       PyObject *args, PyObject *kwds)
 1213 {
 1214     char *kwlist[] = {"dates",
 1215                       "weekmask", "holidays", "busdaycal", "out", NULL};
 1216 
 1217     PyObject *dates_in = NULL, *out_in = NULL;
 1218 
 1219     PyArrayObject *dates = NULL,*out = NULL, *ret;
 1220     npy_bool weekmask[7] = {2, 1, 1, 1, 1, 0, 0};
 1221     NpyBusDayCalendar *busdaycal = NULL;
 1222     int i, busdays_in_weekmask;
 1223     npy_holidayslist holidays = {NULL, NULL};
 1224     int allocated_holidays = 1;
 1225 
 1226     if (!PyArg_ParseTupleAndKeywords(args, kwds,
 1227                                     "O|O&O&O!O:is_busday", kwlist,
 1228                                     &dates_in,
 1229                                     &PyArray_WeekMaskConverter, &weekmask[0],
 1230                                     &PyArray_HolidaysConverter, &holidays,
 1231                                     &NpyBusDayCalendar_Type, &busdaycal,
 1232                                     &out_in)) {
 1233         goto fail;
 1234     }
 1235 
 1236     /* Make sure only one of the weekmask/holidays and busdaycal is supplied */
 1237     if (busdaycal != NULL) {
 1238         if (weekmask[0] != 2 || holidays.begin != NULL) {
 1239             PyErr_SetString(PyExc_ValueError,
 1240                     "Cannot supply both the weekmask/holidays and the "
 1241                     "busdaycal parameters to is_busday()");
 1242             goto fail;
 1243         }
 1244 
 1245         /* Indicate that the holidays weren't allocated by us */
 1246         allocated_holidays = 0;
 1247 
 1248         /* Copy the private normalized weekmask/holidays data */
 1249         holidays = busdaycal->holidays;
 1250         busdays_in_weekmask = busdaycal->busdays_in_weekmask;
 1251         memcpy(weekmask, busdaycal->weekmask, 7);
 1252     }
 1253     else {
 1254         /*
 1255          * Fix up the weekmask from the uninitialized
 1256          * signal value to a proper default.
 1257          */
 1258         if (weekmask[0] == 2) {
 1259             weekmask[0] = 1;
 1260         }
 1261 
 1262         /* Count the number of business days in a week */
 1263         busdays_in_weekmask = 0;
 1264         for (i = 0; i < 7; ++i) {
 1265             busdays_in_weekmask += weekmask[i];
 1266         }
 1267 
 1268         /* The holidays list must be normalized before using it */
 1269         normalize_holidays_list(&holidays, weekmask);
 1270     }
 1271 
 1272     /* Make 'dates' into an array */
 1273     if (PyArray_Check(dates_in)) {
 1274         dates = (PyArrayObject *)dates_in;
 1275         Py_INCREF(dates);
 1276     }
 1277     else {
 1278         PyArray_Descr *datetime_dtype;
 1279 
 1280         /* Use the datetime dtype with generic units so it fills it in */
 1281         datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
 1282         if (datetime_dtype == NULL) {
 1283             goto fail;
 1284         }
 1285 
 1286         /* This steals the datetime_dtype reference */
 1287         dates = (PyArrayObject *)PyArray_FromAny(dates_in,
 1288                                                 datetime_dtype,
 1289                                                 0, 0, 0, dates_in);
 1290         if (dates == NULL) {
 1291             goto fail;
 1292         }
 1293     }
 1294 
 1295     /* Make sure 'out' is an array if it's provided */
 1296     if (out_in != NULL) {
 1297         if (!PyArray_Check(out_in)) {
 1298             PyErr_SetString(PyExc_ValueError,
 1299                     "busday_offset: must provide a NumPy array for 'out'");
 1300             goto fail;
 1301         }
 1302         out = (PyArrayObject *)out_in;
 1303     }
 1304 
 1305     ret = is_business_day(dates, out,
 1306                     weekmask, busdays_in_weekmask,
 1307                     holidays.begin, holidays.end);
 1308 
 1309     Py_DECREF(dates);
 1310     if (allocated_holidays && holidays.begin != NULL) {
 1311         PyArray_free(holidays.begin);
 1312     }
 1313 
 1314     return out == NULL ? PyArray_Return(ret) : (PyObject *)ret;
 1315 
 1316 fail:
 1317     Py_XDECREF(dates);
 1318     if (allocated_holidays && holidays.begin != NULL) {
 1319         PyArray_free(holidays.begin);
 1320     }
 1321 
 1322     return NULL;
 1323 }