|
| 1 | +/* |
| 2 | + * NB: This is derived from numpy 1.7 datetime.c, just enough code to |
| 3 | + * do what we need in numpy 1.6. |
| 4 | + */ |
| 5 | + |
| 6 | +#include <Python.h> |
| 7 | +#include <datetime.h> |
| 8 | + |
| 9 | +#include <time.h> |
| 10 | + |
| 11 | +#include <numpy/arrayobject.h> |
| 12 | + |
| 13 | +#include "numpy/npy_3kcompat.h" |
| 14 | + |
| 15 | +#include "numpy/arrayscalars.h" |
| 16 | +#include "datetime_helper.h" |
| 17 | + |
| 18 | +/* Days per month, regular year and leap year */ |
| 19 | +NPY_NO_EXPORT int _days_per_month_table[2][12] = { |
| 20 | + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, |
| 21 | + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } |
| 22 | +}; |
| 23 | + |
| 24 | +/* |
| 25 | + * Returns 1 if the given year is a leap year, 0 otherwise. |
| 26 | + */ |
| 27 | +NPY_NO_EXPORT int |
| 28 | +is_leapyear(npy_int64 year) |
| 29 | +{ |
| 30 | + return (year & 0x3) == 0 && /* year % 4 == 0 */ |
| 31 | + ((year % 100) != 0 || |
| 32 | + (year % 400) == 0); |
| 33 | +} |
| 34 | + |
| 35 | +/* |
| 36 | + * Adjusts a datetimestruct based on a minutes offset. Assumes |
| 37 | + * the current values are valid. |
| 38 | + */ |
| 39 | +NPY_NO_EXPORT void |
| 40 | +add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes) |
| 41 | +{ |
| 42 | + int isleap; |
| 43 | + |
| 44 | + /* MINUTES */ |
| 45 | + dts->min += minutes; |
| 46 | + while (dts->min < 0) { |
| 47 | + dts->min += 60; |
| 48 | + dts->hour--; |
| 49 | + } |
| 50 | + while (dts->min >= 60) { |
| 51 | + dts->min -= 60; |
| 52 | + dts->hour++; |
| 53 | + } |
| 54 | + |
| 55 | + /* HOURS */ |
| 56 | + while (dts->hour < 0) { |
| 57 | + dts->hour += 24; |
| 58 | + dts->day--; |
| 59 | + } |
| 60 | + while (dts->hour >= 24) { |
| 61 | + dts->hour -= 24; |
| 62 | + dts->day++; |
| 63 | + } |
| 64 | + |
| 65 | + /* DAYS */ |
| 66 | + if (dts->day < 1) { |
| 67 | + dts->month--; |
| 68 | + if (dts->month < 1) { |
| 69 | + dts->year--; |
| 70 | + dts->month = 12; |
| 71 | + } |
| 72 | + isleap = is_leapyear(dts->year); |
| 73 | + dts->day += _days_per_month_table[isleap][dts->month-1]; |
| 74 | + } |
| 75 | + else if (dts->day > 28) { |
| 76 | + isleap = is_leapyear(dts->year); |
| 77 | + if (dts->day > _days_per_month_table[isleap][dts->month-1]) { |
| 78 | + dts->day -= _days_per_month_table[isleap][dts->month-1]; |
| 79 | + dts->month++; |
| 80 | + if (dts->month > 12) { |
| 81 | + dts->year++; |
| 82 | + dts->month = 1; |
| 83 | + } |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +/* |
| 89 | + * |
| 90 | + * Tests for and converts a Python datetime.datetime or datetime.date |
| 91 | + * object into a NumPy npy_datetimestruct. |
| 92 | + * |
| 93 | + * While the C API has PyDate_* and PyDateTime_* functions, the following |
| 94 | + * implementation just asks for attributes, and thus supports |
| 95 | + * datetime duck typing. The tzinfo time zone conversion would require |
| 96 | + * this style of access anyway. |
| 97 | + * |
| 98 | + * 'out_bestunit' gives a suggested unit based on whether the object |
| 99 | + * was a datetime.date or datetime.datetime object. |
| 100 | + * |
| 101 | + * If 'apply_tzinfo' is 1, this function uses the tzinfo to convert |
| 102 | + * to UTC time, otherwise it returns the struct with the local time. |
| 103 | + * |
| 104 | + * Returns -1 on error, 0 on success, and 1 (with no error set) |
| 105 | + * if obj doesn't have the neeeded date or datetime attributes. |
| 106 | + */ |
| 107 | +int |
| 108 | +convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out, |
| 109 | + NPY_DATETIMEUNIT *out_bestunit, |
| 110 | + int apply_tzinfo) |
| 111 | +{ |
| 112 | + PyObject *tmp; |
| 113 | + int isleap; |
| 114 | + |
| 115 | + /* Initialize the output to all zeros */ |
| 116 | + memset(out, 0, sizeof(npy_datetimestruct)); |
| 117 | + out->month = 1; |
| 118 | + out->day = 1; |
| 119 | + |
| 120 | + /* Need at least year/month/day attributes */ |
| 121 | + if (!PyObject_HasAttrString(obj, "year") || |
| 122 | + !PyObject_HasAttrString(obj, "month") || |
| 123 | + !PyObject_HasAttrString(obj, "day")) { |
| 124 | + return 1; |
| 125 | + } |
| 126 | + |
| 127 | + /* Get the year */ |
| 128 | + tmp = PyObject_GetAttrString(obj, "year"); |
| 129 | + if (tmp == NULL) { |
| 130 | + return -1; |
| 131 | + } |
| 132 | + out->year = PyInt_AsLong(tmp); |
| 133 | + if (out->year == -1 && PyErr_Occurred()) { |
| 134 | + Py_DECREF(tmp); |
| 135 | + return -1; |
| 136 | + } |
| 137 | + Py_DECREF(tmp); |
| 138 | + |
| 139 | + /* Get the month */ |
| 140 | + tmp = PyObject_GetAttrString(obj, "month"); |
| 141 | + if (tmp == NULL) { |
| 142 | + return -1; |
| 143 | + } |
| 144 | + out->month = PyInt_AsLong(tmp); |
| 145 | + if (out->month == -1 && PyErr_Occurred()) { |
| 146 | + Py_DECREF(tmp); |
| 147 | + return -1; |
| 148 | + } |
| 149 | + Py_DECREF(tmp); |
| 150 | + |
| 151 | + /* Get the day */ |
| 152 | + tmp = PyObject_GetAttrString(obj, "day"); |
| 153 | + if (tmp == NULL) { |
| 154 | + return -1; |
| 155 | + } |
| 156 | + out->day = PyInt_AsLong(tmp); |
| 157 | + if (out->day == -1 && PyErr_Occurred()) { |
| 158 | + Py_DECREF(tmp); |
| 159 | + return -1; |
| 160 | + } |
| 161 | + Py_DECREF(tmp); |
| 162 | + |
| 163 | + /* Validate that the month and day are valid for the year */ |
| 164 | + if (out->month < 1 || out->month > 12) { |
| 165 | + goto invalid_date; |
| 166 | + } |
| 167 | + isleap = is_leapyear(out->year); |
| 168 | + if (out->day < 1 || |
| 169 | + out->day > _days_per_month_table[isleap][out->month-1]) { |
| 170 | + goto invalid_date; |
| 171 | + } |
| 172 | + |
| 173 | + /* Check for time attributes (if not there, return success as a date) */ |
| 174 | + if (!PyObject_HasAttrString(obj, "hour") || |
| 175 | + !PyObject_HasAttrString(obj, "minute") || |
| 176 | + !PyObject_HasAttrString(obj, "second") || |
| 177 | + !PyObject_HasAttrString(obj, "microsecond")) { |
| 178 | + /* The best unit for date is 'D' */ |
| 179 | + if (out_bestunit != NULL) { |
| 180 | + *out_bestunit = NPY_FR_D; |
| 181 | + } |
| 182 | + return 0; |
| 183 | + } |
| 184 | + |
| 185 | + /* Get the hour */ |
| 186 | + tmp = PyObject_GetAttrString(obj, "hour"); |
| 187 | + if (tmp == NULL) { |
| 188 | + return -1; |
| 189 | + } |
| 190 | + out->hour = PyInt_AsLong(tmp); |
| 191 | + if (out->hour == -1 && PyErr_Occurred()) { |
| 192 | + Py_DECREF(tmp); |
| 193 | + return -1; |
| 194 | + } |
| 195 | + Py_DECREF(tmp); |
| 196 | + |
| 197 | + /* Get the minute */ |
| 198 | + tmp = PyObject_GetAttrString(obj, "minute"); |
| 199 | + if (tmp == NULL) { |
| 200 | + return -1; |
| 201 | + } |
| 202 | + out->min = PyInt_AsLong(tmp); |
| 203 | + if (out->min == -1 && PyErr_Occurred()) { |
| 204 | + Py_DECREF(tmp); |
| 205 | + return -1; |
| 206 | + } |
| 207 | + Py_DECREF(tmp); |
| 208 | + |
| 209 | + /* Get the second */ |
| 210 | + tmp = PyObject_GetAttrString(obj, "second"); |
| 211 | + if (tmp == NULL) { |
| 212 | + return -1; |
| 213 | + } |
| 214 | + out->sec = PyInt_AsLong(tmp); |
| 215 | + if (out->sec == -1 && PyErr_Occurred()) { |
| 216 | + Py_DECREF(tmp); |
| 217 | + return -1; |
| 218 | + } |
| 219 | + Py_DECREF(tmp); |
| 220 | + |
| 221 | + /* Get the microsecond */ |
| 222 | + tmp = PyObject_GetAttrString(obj, "microsecond"); |
| 223 | + if (tmp == NULL) { |
| 224 | + return -1; |
| 225 | + } |
| 226 | + out->us = PyInt_AsLong(tmp); |
| 227 | + if (out->us == -1 && PyErr_Occurred()) { |
| 228 | + Py_DECREF(tmp); |
| 229 | + return -1; |
| 230 | + } |
| 231 | + Py_DECREF(tmp); |
| 232 | + |
| 233 | + if (out->hour < 0 || out->hour >= 24 || |
| 234 | + out->min < 0 || out->min >= 60 || |
| 235 | + out->sec < 0 || out->sec >= 60 || |
| 236 | + out->us < 0 || out->us >= 1000000) { |
| 237 | + goto invalid_time; |
| 238 | + } |
| 239 | + |
| 240 | + /* Apply the time zone offset if it exists */ |
| 241 | + if (apply_tzinfo && PyObject_HasAttrString(obj, "tzinfo")) { |
| 242 | + tmp = PyObject_GetAttrString(obj, "tzinfo"); |
| 243 | + if (tmp == NULL) { |
| 244 | + return -1; |
| 245 | + } |
| 246 | + if (tmp == Py_None) { |
| 247 | + Py_DECREF(tmp); |
| 248 | + } |
| 249 | + else { |
| 250 | + PyObject *offset; |
| 251 | + int seconds_offset, minutes_offset; |
| 252 | + |
| 253 | + /* The utcoffset function should return a timedelta */ |
| 254 | + offset = PyObject_CallMethod(tmp, "utcoffset", "O", obj); |
| 255 | + if (offset == NULL) { |
| 256 | + Py_DECREF(tmp); |
| 257 | + return -1; |
| 258 | + } |
| 259 | + Py_DECREF(tmp); |
| 260 | + |
| 261 | + /* |
| 262 | + * The timedelta should have a function "total_seconds" |
| 263 | + * which contains the value we want. |
| 264 | + */ |
| 265 | + tmp = PyObject_CallMethod(offset, "total_seconds", ""); |
| 266 | + if (tmp == NULL) { |
| 267 | + return -1; |
| 268 | + } |
| 269 | + seconds_offset = PyInt_AsLong(tmp); |
| 270 | + if (seconds_offset == -1 && PyErr_Occurred()) { |
| 271 | + Py_DECREF(tmp); |
| 272 | + return -1; |
| 273 | + } |
| 274 | + Py_DECREF(tmp); |
| 275 | + |
| 276 | + /* Convert to a minutes offset and apply it */ |
| 277 | + minutes_offset = seconds_offset / 60; |
| 278 | + |
| 279 | + add_minutes_to_datetimestruct(out, -minutes_offset); |
| 280 | + } |
| 281 | + } |
| 282 | + |
| 283 | + /* The resolution of Python's datetime is 'us' */ |
| 284 | + if (out_bestunit != NULL) { |
| 285 | + *out_bestunit = NPY_FR_us; |
| 286 | + } |
| 287 | + |
| 288 | + return 0; |
| 289 | + |
| 290 | +invalid_date: |
| 291 | + PyErr_Format(PyExc_ValueError, |
| 292 | + "Invalid date (%d,%d,%d) when converting to NumPy datetime", |
| 293 | + (int)out->year, (int)out->month, (int)out->day); |
| 294 | + return -1; |
| 295 | + |
| 296 | +invalid_time: |
| 297 | + PyErr_Format(PyExc_ValueError, |
| 298 | + "Invalid time (%d,%d,%d,%d) when converting " |
| 299 | + "to NumPy datetime", |
| 300 | + (int)out->hour, (int)out->min, (int)out->sec, (int)out->us); |
| 301 | + return -1; |
| 302 | +} |
0 commit comments