PageRenderTime 66ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/numpy/core/src/multiarray/datetime_busdaycal.c

https://github.com/wizzk42/numpy
C | 553 lines | 429 code | 67 blank | 57 comment | 109 complexity | 30263c02612dbc4200aef4982aa5aac9 MD5 | raw file
  1. /*
  2. * This file implements an object encapsulating a business day
  3. * calendar object for accelerating NumPy datetime business day functions.
  4. *
  5. * Written by Mark Wiebe (mwwiebe@gmail.com)
  6. * Copyright (c) 2011 by Enthought, Inc.
  7. *
  8. * See LICENSE.txt for the license.
  9. */
  10. #define PY_SSIZE_T_CLEAN
  11. #include <Python.h>
  12. #define NPY_NO_DEPRECATED_API
  13. #define _MULTIARRAYMODULE
  14. #include <numpy/arrayobject.h>
  15. #include "npy_config.h"
  16. #include "numpy/npy_3kcompat.h"
  17. #include "numpy/arrayscalars.h"
  18. #include "lowlevel_strided_loops.h"
  19. #include "_datetime.h"
  20. #include "datetime_busday.h"
  21. #include "datetime_busdaycal.h"
  22. NPY_NO_EXPORT int
  23. PyArray_WeekMaskConverter(PyObject *weekmask_in, npy_bool *weekmask)
  24. {
  25. PyObject *obj = weekmask_in;
  26. /* Make obj into an ASCII string if it is UNICODE */
  27. Py_INCREF(obj);
  28. if (PyUnicode_Check(obj)) {
  29. /* accept unicode input */
  30. PyObject *obj_str;
  31. obj_str = PyUnicode_AsASCIIString(obj);
  32. if (obj_str == NULL) {
  33. Py_DECREF(obj);
  34. return 0;
  35. }
  36. Py_DECREF(obj);
  37. obj = obj_str;
  38. }
  39. if (PyBytes_Check(obj)) {
  40. char *str;
  41. Py_ssize_t len;
  42. int i;
  43. if (PyBytes_AsStringAndSize(obj, &str, &len) < 0) {
  44. Py_DECREF(obj);
  45. return 0;
  46. }
  47. /* Length 7 is a string like "1111100" */
  48. if (len == 7) {
  49. for (i = 0; i < 7; ++i) {
  50. switch(str[i]) {
  51. case '0':
  52. weekmask[i] = 0;
  53. break;
  54. case '1':
  55. weekmask[i] = 1;
  56. break;
  57. default:
  58. goto general_weekmask_string;
  59. }
  60. }
  61. goto finish;
  62. }
  63. general_weekmask_string:
  64. /* a string like "SatSun" or "Mon Tue Wed" */
  65. memset(weekmask, 0, 7);
  66. for (i = 0; i < len; i += 3) {
  67. while (isspace(str[i]))
  68. ++i;
  69. if (i == len) {
  70. goto finish;
  71. }
  72. else if (i + 2 >= len) {
  73. goto invalid_weekmask_string;
  74. }
  75. switch (str[i]) {
  76. case 'M':
  77. if (str[i+1] == 'o' && str[i+2] == 'n') {
  78. weekmask[0] = 1;
  79. }
  80. else {
  81. goto invalid_weekmask_string;
  82. }
  83. break;
  84. case 'T':
  85. if (str[i+1] == 'u' && str[i+2] == 'e') {
  86. weekmask[1] = 1;
  87. }
  88. else if (str[i+1] == 'h' && str[i+2] == 'u') {
  89. weekmask[3] = 1;
  90. }
  91. else {
  92. goto invalid_weekmask_string;
  93. }
  94. break;
  95. case 'W':
  96. if (str[i+1] == 'e' && str[i+2] == 'd') {
  97. weekmask[2] = 1;
  98. }
  99. else {
  100. goto invalid_weekmask_string;
  101. }
  102. break;
  103. case 'F':
  104. if (str[i+1] == 'r' && str[i+2] == 'i') {
  105. weekmask[4] = 1;
  106. }
  107. else {
  108. goto invalid_weekmask_string;
  109. }
  110. break;
  111. case 'S':
  112. if (str[i+1] == 'a' && str[i+2] == 't') {
  113. weekmask[5] = 1;
  114. }
  115. else if (str[i+1] == 'u' && str[i+2] == 'n') {
  116. weekmask[6] = 1;
  117. }
  118. else {
  119. goto invalid_weekmask_string;
  120. }
  121. break;
  122. default:
  123. goto invalid_weekmask_string;
  124. }
  125. }
  126. goto finish;
  127. invalid_weekmask_string:
  128. PyErr_Format(PyExc_ValueError,
  129. "Invalid business day weekmask string \"%s\"",
  130. str);
  131. Py_DECREF(obj);
  132. return 0;
  133. }
  134. /* Something like [1,1,1,1,1,0,0] */
  135. else if (PySequence_Check(obj)) {
  136. if (PySequence_Size(obj) != 7 ||
  137. (PyArray_Check(obj) &&
  138. PyArray_NDIM((PyArrayObject *)obj) != 1)) {
  139. PyErr_SetString(PyExc_ValueError,
  140. "A business day weekmask array must have length 7");
  141. Py_DECREF(obj);
  142. return 0;
  143. }
  144. else {
  145. int i;
  146. PyObject *f;
  147. for (i = 0; i < 7; ++i) {
  148. long val;
  149. f = PySequence_GetItem(obj, i);
  150. if (f == NULL) {
  151. Py_DECREF(obj);
  152. return 0;
  153. }
  154. val = PyInt_AsLong(f);
  155. if (val == -1 && PyErr_Occurred()) {
  156. Py_DECREF(obj);
  157. return 0;
  158. }
  159. if (val == 0) {
  160. weekmask[i] = 0;
  161. }
  162. else if (val == 1) {
  163. weekmask[i] = 1;
  164. }
  165. else {
  166. PyErr_SetString(PyExc_ValueError,
  167. "A business day weekmask array must have all "
  168. "1's and 0's");
  169. Py_DECREF(obj);
  170. return 0;
  171. }
  172. }
  173. goto finish;
  174. }
  175. }
  176. PyErr_SetString(PyExc_ValueError,
  177. "Couldn't convert object into a business day weekmask");
  178. Py_DECREF(obj);
  179. return 0;
  180. finish:
  181. Py_DECREF(obj);
  182. return 1;
  183. }
  184. static int
  185. qsort_datetime_compare(const void *elem1, const void *elem2)
  186. {
  187. npy_datetime e1 = *(npy_datetime *)elem1;
  188. npy_datetime e2 = *(npy_datetime *)elem2;
  189. return (e1 < e2) ? -1 : (e1 == e2) ? 0 : 1;
  190. }
  191. /*
  192. * Sorts the the array of dates provided in place and removes
  193. * NaT, duplicates and any date which is already excluded on account
  194. * of the weekmask.
  195. *
  196. * Returns the number of dates left after removing weekmask-excluded
  197. * dates.
  198. */
  199. NPY_NO_EXPORT void
  200. normalize_holidays_list(npy_holidayslist *holidays, npy_bool *weekmask)
  201. {
  202. npy_datetime *dates = holidays->begin;
  203. npy_intp count = holidays->end - dates;
  204. npy_datetime lastdate = NPY_DATETIME_NAT;
  205. npy_intp trimcount, i;
  206. int day_of_week;
  207. /* Sort the dates */
  208. qsort(dates, count, sizeof(npy_datetime), &qsort_datetime_compare);
  209. /* Sweep throught the array, eliminating unnecessary values */
  210. trimcount = 0;
  211. for (i = 0; i < count; ++i) {
  212. npy_datetime date = dates[i];
  213. /* Skip any NaT or duplicate */
  214. if (date != NPY_DATETIME_NAT && date != lastdate) {
  215. /* Get the day of the week (1970-01-05 is Monday) */
  216. day_of_week = (int)((date - 4) % 7);
  217. if (day_of_week < 0) {
  218. day_of_week += 7;
  219. }
  220. /*
  221. * If the holiday falls on a possible business day,
  222. * then keep it.
  223. */
  224. if (weekmask[day_of_week] == 1) {
  225. dates[trimcount++] = date;
  226. lastdate = date;
  227. }
  228. }
  229. }
  230. /* Adjust the end of the holidays array */
  231. holidays->end = dates + trimcount;
  232. }
  233. /*
  234. * Converts a Python input into a non-normalized list of holidays.
  235. *
  236. * IMPORTANT: This function can't do the normalization, because it doesn't
  237. * know the weekmask. You must call 'normalize_holiday_list'
  238. * on the result before using it.
  239. */
  240. NPY_NO_EXPORT int
  241. PyArray_HolidaysConverter(PyObject *dates_in, npy_holidayslist *holidays)
  242. {
  243. PyArrayObject *dates = NULL;
  244. PyArray_Descr *date_dtype = NULL;
  245. npy_intp count;
  246. /* Make 'dates' into an array */
  247. if (PyArray_Check(dates_in)) {
  248. dates = (PyArrayObject *)dates_in;
  249. Py_INCREF(dates);
  250. }
  251. else {
  252. PyArray_Descr *datetime_dtype;
  253. /* Use the datetime dtype with generic units so it fills it in */
  254. datetime_dtype = PyArray_DescrFromType(NPY_DATETIME);
  255. if (datetime_dtype == NULL) {
  256. goto fail;
  257. }
  258. /* This steals the datetime_dtype reference */
  259. dates = (PyArrayObject *)PyArray_FromAny(dates_in, datetime_dtype,
  260. 0, 0, 0, dates_in);
  261. if (dates == NULL) {
  262. goto fail;
  263. }
  264. }
  265. date_dtype = create_datetime_dtype_with_unit(NPY_DATETIME, NPY_FR_D);
  266. if (date_dtype == NULL) {
  267. goto fail;
  268. }
  269. if (!PyArray_CanCastTypeTo(PyArray_DESCR(dates),
  270. date_dtype, NPY_SAFE_CASTING)) {
  271. PyErr_SetString(PyExc_ValueError, "Cannot safely convert "
  272. "provided holidays input into an array of dates");
  273. goto fail;
  274. }
  275. if (PyArray_NDIM(dates) != 1) {
  276. PyErr_SetString(PyExc_ValueError, "holidays must be a provided "
  277. "as a one-dimensional array");
  278. goto fail;
  279. }
  280. /* Allocate the memory for the dates */
  281. count = PyArray_DIM(dates, 0);
  282. holidays->begin = PyArray_malloc(sizeof(npy_datetime) * count);
  283. if (holidays->begin == NULL) {
  284. PyErr_NoMemory();
  285. goto fail;
  286. }
  287. holidays->end = holidays->begin + count;
  288. /* Cast the data into a raw date array */
  289. if (PyArray_CastRawArrays(count,
  290. PyArray_BYTES(dates), (char *)holidays->begin,
  291. PyArray_STRIDE(dates, 0), sizeof(npy_datetime),
  292. PyArray_DESCR(dates), date_dtype,
  293. 0) != NPY_SUCCEED) {
  294. goto fail;
  295. }
  296. Py_DECREF(dates);
  297. Py_DECREF(date_dtype);
  298. return 1;
  299. fail:
  300. Py_XDECREF(dates);
  301. Py_XDECREF(date_dtype);
  302. return 0;
  303. }
  304. static PyObject *
  305. busdaycalendar_new(PyTypeObject *subtype,
  306. PyObject *NPY_UNUSED(args), PyObject *NPY_UNUSED(kwds))
  307. {
  308. NpyBusDayCalendar *self;
  309. self = (NpyBusDayCalendar *)subtype->tp_alloc(subtype, 0);
  310. if (self != NULL) {
  311. /* Start with an empty holidays list */
  312. self->holidays.begin = NULL;
  313. self->holidays.end = NULL;
  314. /* Set the weekmask to the default */
  315. self->busdays_in_weekmask = 5;
  316. self->weekmask[0] = 1;
  317. self->weekmask[1] = 1;
  318. self->weekmask[2] = 1;
  319. self->weekmask[3] = 1;
  320. self->weekmask[4] = 1;
  321. self->weekmask[5] = 0;
  322. self->weekmask[6] = 0;
  323. }
  324. return (PyObject *)self;
  325. }
  326. static int
  327. busdaycalendar_init(NpyBusDayCalendar *self, PyObject *args, PyObject *kwds)
  328. {
  329. static char *kwlist[] = {"weekmask", "holidays", NULL};
  330. int i, busdays_in_weekmask;
  331. /* Clear the holidays if necessary */
  332. if (self->holidays.begin != NULL) {
  333. PyArray_free(self->holidays.begin);
  334. self->holidays.begin = NULL;
  335. self->holidays.end = NULL;
  336. }
  337. /* Reset the weekmask to the default */
  338. self->busdays_in_weekmask = 5;
  339. self->weekmask[0] = 1;
  340. self->weekmask[1] = 1;
  341. self->weekmask[2] = 1;
  342. self->weekmask[3] = 1;
  343. self->weekmask[4] = 1;
  344. self->weekmask[5] = 0;
  345. self->weekmask[6] = 0;
  346. /* Parse the parameters */
  347. if (!PyArg_ParseTupleAndKeywords(args, kwds,
  348. "|O&O&:busdaycal", kwlist,
  349. &PyArray_WeekMaskConverter, &self->weekmask[0],
  350. &PyArray_HolidaysConverter, &self->holidays)) {
  351. return -1;
  352. }
  353. /* Count the number of business days in a week */
  354. busdays_in_weekmask = 0;
  355. for (i = 0; i < 7; ++i) {
  356. busdays_in_weekmask += self->weekmask[i];
  357. }
  358. self->busdays_in_weekmask = busdays_in_weekmask;
  359. /* Normalize the holidays list */
  360. normalize_holidays_list(&self->holidays, self->weekmask);
  361. if (self->busdays_in_weekmask == 0) {
  362. PyErr_SetString(PyExc_ValueError,
  363. "Cannot construct a numpy.busdaycal with a weekmask of "
  364. "all zeros");
  365. return -1;
  366. }
  367. return 0;
  368. }
  369. static void
  370. busdaycalendar_dealloc(NpyBusDayCalendar *self)
  371. {
  372. /* Clear the holidays */
  373. if (self->holidays.begin != NULL) {
  374. PyArray_free(self->holidays.begin);
  375. self->holidays.begin = NULL;
  376. self->holidays.end = NULL;
  377. }
  378. Py_TYPE(self)->tp_free((PyObject*)self);
  379. }
  380. static PyObject *
  381. busdaycalendar_weekmask_get(NpyBusDayCalendar *self)
  382. {
  383. PyArrayObject *ret;
  384. npy_intp size = 7;
  385. /* Allocate a 7-element boolean array */
  386. ret = (PyArrayObject *)PyArray_SimpleNew(1, &size, NPY_BOOL);
  387. if (ret == NULL) {
  388. return NULL;
  389. }
  390. /* Copy the weekmask data */
  391. memcpy(PyArray_DATA(ret), self->weekmask, 7);
  392. return (PyObject *)ret;
  393. }
  394. static PyObject *
  395. busdaycalendar_holidays_get(NpyBusDayCalendar *self)
  396. {
  397. PyArrayObject *ret;
  398. PyArray_Descr *date_dtype;
  399. npy_intp size = self->holidays.end - self->holidays.begin;
  400. /* Create a date dtype */
  401. date_dtype = create_datetime_dtype_with_unit(NPY_DATETIME, NPY_FR_D);
  402. if (date_dtype == NULL) {
  403. return NULL;
  404. }
  405. /* Allocate a date array (this steals the date_dtype reference) */
  406. ret = (PyArrayObject *)PyArray_SimpleNewFromDescr(1, &size, date_dtype);
  407. if (ret == NULL) {
  408. return NULL;
  409. }
  410. /* Copy the holidays */
  411. if (size > 0) {
  412. memcpy(PyArray_DATA(ret), self->holidays.begin,
  413. size * sizeof(npy_datetime));
  414. }
  415. return (PyObject *)ret;
  416. }
  417. static PyGetSetDef busdaycalendar_getsets[] = {
  418. {"weekmask",
  419. (getter)busdaycalendar_weekmask_get,
  420. NULL, NULL, NULL},
  421. {"holidays",
  422. (getter)busdaycalendar_holidays_get,
  423. NULL, NULL, NULL},
  424. {NULL, NULL, NULL, NULL, NULL}
  425. };
  426. NPY_NO_EXPORT PyTypeObject NpyBusDayCalendar_Type = {
  427. #if defined(NPY_PY3K)
  428. PyVarObject_HEAD_INIT(NULL, 0)
  429. #else
  430. PyObject_HEAD_INIT(NULL)
  431. 0, /* ob_size */
  432. #endif
  433. "numpy.busdaycalendar", /* tp_name */
  434. sizeof(NpyBusDayCalendar), /* tp_basicsize */
  435. 0, /* tp_itemsize */
  436. /* methods */
  437. (destructor)busdaycalendar_dealloc, /* tp_dealloc */
  438. 0, /* tp_print */
  439. 0, /* tp_getattr */
  440. 0, /* tp_setattr */
  441. #if defined(NPY_PY3K)
  442. 0, /* tp_reserved */
  443. #else
  444. 0, /* tp_compare */
  445. #endif
  446. 0, /* tp_repr */
  447. 0, /* tp_as_number */
  448. 0, /* tp_as_sequence */
  449. 0, /* tp_as_mapping */
  450. 0, /* tp_hash */
  451. 0, /* tp_call */
  452. 0, /* tp_str */
  453. 0, /* tp_getattro */
  454. 0, /* tp_setattro */
  455. 0, /* tp_as_buffer */
  456. Py_TPFLAGS_DEFAULT, /* tp_flags */
  457. 0, /* tp_doc */
  458. 0, /* tp_traverse */
  459. 0, /* tp_clear */
  460. 0, /* tp_richcompare */
  461. 0, /* tp_weaklistoffset */
  462. 0, /* tp_iter */
  463. 0, /* tp_iternext */
  464. 0, /* tp_methods */
  465. 0, /* tp_members */
  466. busdaycalendar_getsets, /* tp_getset */
  467. 0, /* tp_base */
  468. 0, /* tp_dict */
  469. 0, /* tp_descr_get */
  470. 0, /* tp_descr_set */
  471. 0, /* tp_dictoffset */
  472. (initproc)busdaycalendar_init, /* tp_init */
  473. 0, /* tp_alloc */
  474. busdaycalendar_new, /* tp_new */
  475. 0, /* tp_free */
  476. 0, /* tp_is_gc */
  477. 0, /* tp_bases */
  478. 0, /* tp_mro */
  479. 0, /* tp_cache */
  480. 0, /* tp_subclasses */
  481. 0, /* tp_weaklist */
  482. 0, /* tp_del */
  483. #if PY_VERSION_HEX >= 0x02060000
  484. 0, /* tp_version_tag */
  485. #endif
  486. };