summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/numerictypes.py4
-rw-r--r--numpy/core/src/multiarray/datetime_busday.c247
-rw-r--r--numpy/core/src/multiarray/datetime_busday.h7
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c3
-rw-r--r--numpy/core/tests/test_datetime.py19
5 files changed, 277 insertions, 3 deletions
diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py
index 0b61b7161..8d184e47d 100644
--- a/numpy/core/numerictypes.py
+++ b/numpy/core/numerictypes.py
@@ -93,11 +93,11 @@ __all__ = ['sctypeDict', 'sctypeNA', 'typeDict', 'typeNA', 'sctypes',
'ScalarType', 'obj2sctype', 'cast', 'nbytes', 'sctype2char',
'maximum_sctype', 'issctype', 'typecodes', 'find_common_type',
'issubdtype', 'datetime_data','datetime_as_string',
- 'busday_offset', 'busday_count', 'busdaydef']
+ 'busday_offset', 'busday_count', 'is_busday', 'busdaydef']
from numpy.core.multiarray import typeinfo, ndarray, array, \
empty, dtype, datetime_data, datetime_as_string, \
- busday_offset, busday_count, busdaydef
+ busday_offset, busday_count, is_busday, busdaydef
import types as _types
import sys
diff --git a/numpy/core/src/multiarray/datetime_busday.c b/numpy/core/src/multiarray/datetime_busday.c
index 5e1dec89d..57841f938 100644
--- a/numpy/core/src/multiarray/datetime_busday.c
+++ b/numpy/core/src/multiarray/datetime_busday.c
@@ -586,7 +586,7 @@ business_day_count(PyArrayObject *dates_begin, PyArrayObject *dates_end,
PyErr_SetString(PyExc_ValueError,
"the business day weekmask must have at least one "
"valid business day");
- return -1;
+ return NULL;
}
/* First create the data types for the dates and the int64 output */
@@ -684,6 +684,133 @@ finish:
return ret;
}
+/*
+ * Returns a boolean array with True for input dates which are valid
+ * business days, and False for dates which are not. This is the
+ * low-level function which requires already cleaned input data.
+ *
+ * dates: An array of dates with 'datetime64[D]' data type.
+ * out: Either NULL, or an array with 'bool' data type
+ * in which to place the resulting dates.
+ * weekmask: A 7-element boolean mask, 1 for possible business days and 0
+ * for non-business days.
+ * busdays_in_weekmask: A count of how many 1's there are in weekmask.
+ * holidays_begin/holidays_end: A sorted list of dates matching '[D]'
+ * unit metadata, with any dates falling on a day of the
+ * week without weekmask[i] == 1 already filtered out.
+ */
+NPY_NO_EXPORT PyArrayObject *
+is_business_day(PyArrayObject *dates, PyArrayObject *out,
+ npy_bool *weekmask, int busdays_in_weekmask,
+ npy_datetime *holidays_begin, npy_datetime *holidays_end)
+{
+ PyArray_DatetimeMetaData temp_meta;
+ PyArray_Descr *dtypes[2] = {NULL, NULL};
+
+ NpyIter *iter = NULL;
+ PyArrayObject *op[2] = {NULL, NULL};
+ npy_uint32 op_flags[2], flags;
+
+ PyArrayObject *ret = NULL;
+
+ if (busdays_in_weekmask == 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "the business day weekmask must have at least one "
+ "valid business day");
+ return NULL;
+ }
+
+ /* First create the data types for the dates and the bool output */
+ temp_meta.base = NPY_FR_D;
+ temp_meta.num = 1;
+ temp_meta.events = 1;
+ dtypes[0] = create_datetime_dtype(NPY_DATETIME, &temp_meta);
+ if (dtypes[0] == NULL) {
+ goto fail;
+ }
+ dtypes[1] = PyArray_DescrFromType(NPY_BOOL);
+ if (dtypes[1] == NULL) {
+ goto fail;
+ }
+
+ /* Set up the iterator parameters */
+ flags = NPY_ITER_EXTERNAL_LOOP|
+ NPY_ITER_BUFFERED|
+ NPY_ITER_ZEROSIZE_OK;
+ op[0] = dates;
+ op_flags[0] = NPY_ITER_READONLY | NPY_ITER_ALIGNED;
+ op[1] = out;
+ op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE | NPY_ITER_ALIGNED;
+
+ /* Allocate the iterator */
+ iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_SAFE_CASTING,
+ op_flags, dtypes);
+ if (iter == NULL) {
+ goto fail;
+ }
+
+ /* Loop over all elements */
+ if (NpyIter_GetIterSize(iter) > 0) {
+ NpyIter_IterNextFunc *iternext;
+ char **dataptr;
+ npy_intp *strideptr, *innersizeptr;
+
+ iternext = NpyIter_GetIterNext(iter, NULL);
+ if (iternext == NULL) {
+ goto fail;
+ }
+ dataptr = NpyIter_GetDataPtrArray(iter);
+ strideptr = NpyIter_GetInnerStrideArray(iter);
+ innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
+
+ do {
+ char *data_dates = dataptr[0];
+ char *data_out = dataptr[1];
+ npy_intp stride_dates = strideptr[0];
+ npy_intp stride_out = strideptr[1];
+ npy_intp count = *innersizeptr;
+
+ npy_datetime date;
+ int day_of_week;
+
+ while (count--) {
+ /* Check if it's a business day */
+ date = *(npy_datetime *)data_dates;
+ day_of_week = get_day_of_week(date);
+ *(npy_bool *)data_out = weekmask[day_of_week] &&
+ !is_holiday(date,
+ holidays_begin, holidays_end) &&
+ date != NPY_DATETIME_NAT;
+
+
+ data_dates += stride_dates;
+ data_out += stride_out;
+ }
+ } while (iternext(iter));
+ }
+
+ /* Get the return object from the iterator */
+ ret = NpyIter_GetOperandArray(iter)[1];
+ Py_INCREF(ret);
+
+ goto finish;
+
+fail:
+ Py_XDECREF(ret);
+ ret = NULL;
+
+finish:
+ Py_XDECREF(dtypes[0]);
+ Py_XDECREF(dtypes[1]);
+ if (iter != NULL) {
+ if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
+ Py_XDECREF(ret);
+ ret = NULL;
+ }
+ }
+ return ret;
+}
+
static int
PyArray_BusDayRollConverter(PyObject *roll_in, NPY_BUSDAY_ROLL *roll)
{
@@ -1054,3 +1181,121 @@ fail:
return NULL;
}
+
+/*
+ * This is the 'is_busday' function exposed for calling
+ * from Python.
+ */
+NPY_NO_EXPORT PyObject *
+array_is_busday(PyObject *NPY_UNUSED(self),
+ PyObject *args, PyObject *kwds)
+{
+ char *kwlist[] = {"dates",
+ "weekmask", "holidays", "busdaydef", "out", NULL};
+
+ PyObject *dates_in = NULL, *out_in = NULL;
+
+ PyArrayObject *dates = NULL,*out = NULL, *ret;
+ npy_bool weekmask[7] = {2, 1, 1, 1, 1, 0, 0};
+ NpyBusinessDayDef *busdaydef = NULL;
+ int i, busdays_in_weekmask;
+ npy_holidayslist holidays = {NULL, NULL};
+ int allocated_holidays = 1;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwds,
+ "O|O&O&O!O:busday_count", kwlist,
+ &dates_in,
+ &PyArray_WeekMaskConverter, &weekmask[0],
+ &PyArray_HolidaysConverter, &holidays,
+ &NpyBusinessDayDef_Type, &busdaydef,
+ &out_in)) {
+ goto fail;
+ }
+
+ /* Make sure only one of the weekmask/holidays and busdaydef is supplied */
+ if (busdaydef != NULL) {
+ if (weekmask[0] != 2 || holidays.begin != NULL) {
+ PyErr_SetString(PyExc_ValueError,
+ "Cannot supply both the weekmask/holidays and the "
+ "busdaydef parameters to is_busday()");
+ goto fail;
+ }
+
+ /* Indicate that the holidays weren't allocated by us */
+ allocated_holidays = 0;
+
+ /* Copy the private normalized weekmask/holidays data */
+ holidays = busdaydef->holidays;
+ busdays_in_weekmask = busdaydef->busdays_in_weekmask;
+ memcpy(weekmask, busdaydef->weekmask, 7);
+ }
+ else {
+ /*
+ * Fix up the weekmask from the uninitialized
+ * signal value to a proper default.
+ */
+ if (weekmask[0] == 2) {
+ weekmask[0] = 1;
+ }
+
+ /* Count the number of business days in a week */
+ busdays_in_weekmask = 0;
+ for (i = 0; i < 7; ++i) {
+ busdays_in_weekmask += weekmask[i];
+ }
+
+ /* The holidays list must be normalized before using it */
+ normalize_holidays_list(&holidays, weekmask);
+ }
+
+ /* Make 'dates' into an array */
+ if (PyArray_Check(dates_in)) {
+ dates = (PyArrayObject *)dates_in;
+ Py_INCREF(dates);
+ }
+ else {
+ PyArray_Descr *datetime_dtype;
+
+ /* Use the datetime dtype with generic units so it fills it in */
+ datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
+ if (datetime_dtype == NULL) {
+ goto fail;
+ }
+
+ /* This steals the datetime_dtype reference */
+ dates = (PyArrayObject *)PyArray_FromAny(dates_in,
+ datetime_dtype,
+ 0, 0, 0, dates_in);
+ if (dates == NULL) {
+ goto fail;
+ }
+ }
+
+ /* Make sure 'out' is an array if it's provided */
+ if (out_in != NULL) {
+ if (!PyArray_Check(out_in)) {
+ PyErr_SetString(PyExc_ValueError,
+ "busday_offset: must provide a NumPy array for 'out'");
+ goto fail;
+ }
+ out = (PyArrayObject *)out_in;
+ }
+
+ ret = is_business_day(dates, out,
+ weekmask, busdays_in_weekmask,
+ holidays.begin, holidays.end);
+
+ Py_DECREF(dates);
+ if (allocated_holidays && holidays.begin != NULL) {
+ PyArray_free(holidays.begin);
+ }
+
+ return out == NULL ? PyArray_Return(ret) : (PyObject *)ret;
+fail:
+ Py_XDECREF(dates);
+ if (allocated_holidays && holidays.begin != NULL) {
+ PyArray_free(holidays.begin);
+ }
+
+ return NULL;
+}
diff --git a/numpy/core/src/multiarray/datetime_busday.h b/numpy/core/src/multiarray/datetime_busday.h
index 5d817b255..483151122 100644
--- a/numpy/core/src/multiarray/datetime_busday.h
+++ b/numpy/core/src/multiarray/datetime_busday.h
@@ -17,5 +17,12 @@ NPY_NO_EXPORT PyObject *
array_busday_count(PyObject *NPY_UNUSED(self),
PyObject *args, PyObject *kwds);
+/*
+ * This is the 'is_busday' function exposed for calling
+ * from Python.
+ */
+NPY_NO_EXPORT PyObject *
+array_is_busday(PyObject *NPY_UNUSED(self),
+ PyObject *args, PyObject *kwds);
#endif
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index c515b0a82..60ee9dbe8 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -3606,6 +3606,9 @@ static struct PyMethodDef array_module_methods[] = {
{"busday_count",
(PyCFunction)array_busday_count,
METH_VARARGS | METH_KEYWORDS, NULL},
+ {"is_busday",
+ (PyCFunction)array_is_busday,
+ METH_VARARGS | METH_KEYWORDS, NULL},
#if !defined(NPY_PY3K)
{"newbuffer",
(PyCFunction)new_buffer,
diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py
index 28f53e3f0..a3adcb2a6 100644
--- a/numpy/core/tests/test_datetime.py
+++ b/numpy/core/tests/test_datetime.py
@@ -1412,7 +1412,26 @@ class TestDateTime(TestCase):
weekmask='1111100', busdaydef=bdd)
assert_raises(ValueError, np.busday_offset, '2012-01-03', '2012-02-03',
holidays=holidays, busdaydef=bdd)
+
+ # Number of Mondays in March 2011
+ assert_equal(np.busday_count('2011-03', '2011-04', weekmask='Mon'), 4)
+ def test_datetime_is_busday(self):
+ holidays=['2011-01-01', '2011-10-10', '2011-11-11', '2011-11-24',
+ '2011-12-25', '2011-05-30', '2011-02-21', '2011-01-17',
+ '2011-12-26', '2012-01-02', '2011-02-21', '2011-05-30',
+ '2011-07-01', '2011-07-04', '2011-09-05', '2011-10-10',
+ 'NaT']
+ bdd = np.busdaydef(weekmask='1111100', holidays=holidays)
+
+ # Weekend/weekday tests
+ assert_equal(np.is_busday('2011-01-01'), False)
+ assert_equal(np.is_busday('2011-01-02'), False)
+ assert_equal(np.is_busday('2011-01-03'), True)
+
+ # All the holidays are not business days
+ assert_equal(np.is_busday(holidays, busdaydef=bdd),
+ np.zeros(len(holidays), dtype='?'))
class TestDateTimeData(TestCase):