Skip to content

Commit 863f059

Browse files
committed
added datetime_helper to pull numpy 1.7 code we need
1 parent a6f7b36 commit 863f059

File tree

6 files changed

+337
-29
lines changed

6 files changed

+337
-29
lines changed

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
build
55
dist
66
MANIFEST
7-
*.c
7+
*.c !datetime_helper.c
88
*.cpp
99
*.so
1010
*.pyd
@@ -16,4 +16,4 @@ doc/source/vbench.rst
1616
*flymake*
1717
scikits
1818
.coverage
19-
pandas.egg-info
19+
pandas.egg-info

pandas/src/datetime.pxd

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from numpy cimport int64_t
2+
from cpython cimport PyObject
23

34
cdef extern from "datetime.h":
45

@@ -53,3 +54,9 @@ cdef extern from "numpy/ndarrayobject.h":
5354
void PyArray_DatetimeToDatetimeStruct(npy_datetime val,
5455
NPY_DATETIMEUNIT fr,
5556
npy_datetimestruct *result)
57+
58+
cdef extern from "datetime_helper.h":
59+
60+
int convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out,
61+
NPY_DATETIMEUNIT *out_bestunit,
62+
int apply_tzinfo)

pandas/src/datetime.pyx

+11-26
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ PyDateTime_IMPORT
1414
# initialize numpy
1515
import_array()
1616

17+
# in numpy 1.7, will prop need this
18+
# numpy_pydatetime_import
19+
1720
cdef class Date:
1821
'''
1922
This is the custom pandas Date box for the numpy datetime64 dtype.
@@ -67,31 +70,13 @@ cdef class Date:
6770
def __get__(self):
6871
return self.dts.us
6972

73+
cdef:
74+
npy_datetimestruct g_dts
75+
NPY_DATETIMEUNIT g_out_bestunit
7076

71-
# TODO: this is wrong calculation, wtf is going on
72-
def datetime_to_datetime64_WRONG(object boxed):
73-
cdef int64_t y, M, d, h, m, s, u
74-
cdef npy_datetimestruct dts
75-
76-
if PyDateTime_Check(boxed):
77-
dts.year = PyDateTime_GET_YEAR(boxed)
78-
dts.month = PyDateTime_GET_MONTH(boxed)
79-
dts.day = PyDateTime_GET_DAY(boxed)
80-
dts.hour = PyDateTime_TIME_GET_HOUR(boxed)
81-
dts.min = PyDateTime_TIME_GET_MINUTE(boxed)
82-
dts.sec = PyDateTime_TIME_GET_SECOND(boxed)
83-
dts.us = PyDateTime_TIME_GET_MICROSECOND(boxed)
84-
dts.ps = 0
85-
dts.as = 0
86-
87-
return PyArray_DatetimeStructToDatetime(NPY_FR_us, &dts)
88-
89-
def from_datetime(object dt, object freq=None):
90-
cdef int64_t converted
91-
92-
if PyDateTime_Check(dt):
93-
converted = np.datetime64(dt).view('i8')
94-
return Date(converted, freq, dt.tzinfo)
95-
96-
raise ValueError("Expected a datetime, received a %s" % type(dt))
77+
def pydt_to_dt64(object pydt):
78+
if PyDateTime_Check(pydt):
79+
convert_pydatetime_to_datetimestruct(<PyObject *>pydt, &g_dts, &g_out_bestunit, 1)
80+
return PyArray_DatetimeStructToDatetime(g_out_bestunit, &g_dts)
9781

82+
raise ValueError("Expected a datetime, received a %s" % type(pydt))

pandas/src/datetime_helper.c

+302
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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+
}

pandas/src/datetime_helper.h

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* NB: This is derived from numpy 1.7 datetime.c, just enough code to
3+
* do some conversions. Copyrights from that file apply.
4+
*/
5+
6+
#ifndef _PANDAS_DATETIME_H_
7+
#define _PANDAS_DATETIME_H_
8+
9+
int convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out,
10+
NPY_DATETIMEUNIT *out_bestunit,
11+
int apply_tzinfo);
12+
13+
#endif

0 commit comments

Comments
 (0)