Skip to content

Commit b0ff10f

Browse files
authored
Refactored custom datetime functions (#52634)
refactored custom datetime functions
1 parent 81b08a9 commit b0ff10f

File tree

5 files changed

+155
-163
lines changed

5 files changed

+155
-163
lines changed

pandas/_libs/tslibs/src/datetime/date_conversions.c

-63
Original file line numberDiff line numberDiff line change
@@ -73,69 +73,6 @@ npy_datetime NpyDateTimeToEpoch(npy_datetime dt, NPY_DATETIMEUNIT base) {
7373
return dt;
7474
}
7575

76-
/* Convert PyDatetime To ISO C-string. mutates len */
77-
char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base,
78-
size_t *len) {
79-
npy_datetimestruct dts;
80-
int ret;
81-
82-
ret = convert_pydatetime_to_datetimestruct(obj, &dts);
83-
if (ret != 0) {
84-
if (!PyErr_Occurred()) {
85-
PyErr_SetString(PyExc_ValueError,
86-
"Could not convert PyDateTime to numpy datetime");
87-
}
88-
return NULL;
89-
}
90-
91-
*len = (size_t)get_datetime_iso_8601_strlen(0, base);
92-
char *result = PyObject_Malloc(*len);
93-
// Check to see if PyDateTime has a timezone.
94-
// Don't convert to UTC if it doesn't.
95-
int is_tz_aware = 0;
96-
if (PyObject_HasAttrString(obj, "tzinfo")) {
97-
PyObject *offset = extract_utc_offset(obj);
98-
if (offset == NULL) {
99-
PyObject_Free(result);
100-
return NULL;
101-
}
102-
is_tz_aware = offset != Py_None;
103-
Py_DECREF(offset);
104-
}
105-
ret = make_iso_8601_datetime(&dts, result, *len, is_tz_aware, base);
106-
107-
if (ret != 0) {
108-
PyErr_SetString(PyExc_ValueError,
109-
"Could not convert datetime value to string");
110-
PyObject_Free(result);
111-
return NULL;
112-
}
113-
114-
// Note that get_datetime_iso_8601_strlen just gives a generic size
115-
// for ISO string conversion, not the actual size used
116-
*len = strlen(result);
117-
return result;
118-
}
119-
120-
npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base) {
121-
npy_datetimestruct dts;
122-
int ret;
123-
124-
ret = convert_pydatetime_to_datetimestruct(dt, &dts);
125-
if (ret != 0) {
126-
if (!PyErr_Occurred()) {
127-
PyErr_SetString(PyExc_ValueError,
128-
"Could not convert PyDateTime to numpy datetime");
129-
}
130-
// TODO(username): is setting errMsg required?
131-
// ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
132-
// return NULL;
133-
}
134-
135-
npy_datetime npy_dt = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts);
136-
return NpyDateTimeToEpoch(npy_dt, base);
137-
}
138-
13976
/* Converts the int64_t representation of a duration to ISO; mutates len */
14077
char *int64ToIsoDuration(int64_t value, size_t *len) {
14178
pandas_timedeltastruct tds;

pandas/_libs/tslibs/src/datetime/date_conversions.h

-9
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,4 @@ char *int64ToIso(int64_t value, NPY_DATETIMEUNIT base, size_t *len);
2424
// replace with scaleNanosecToUnit
2525
npy_datetime NpyDateTimeToEpoch(npy_datetime dt, NPY_DATETIMEUNIT base);
2626

27-
// Converts a Python object representing a Date / Datetime to ISO format
28-
// up to precision `base` e.g. base="s" yields 2020-01-03T00:00:00Z
29-
// while base="ns" yields "2020-01-01T00:00:00.000000000Z"
30-
// len is mutated to save the length of the returned string
31-
char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base, size_t *len);
32-
33-
// Convert a Python Date/Datetime to Unix epoch with resolution base
34-
npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base);
35-
3627
char *int64ToIsoDuration(int64_t value, size_t *len);

pandas/_libs/tslibs/src/datetime/np_datetime.c

-88
Original file line numberDiff line numberDiff line change
@@ -299,94 +299,6 @@ PyObject *extract_utc_offset(PyObject *obj) {
299299
return tmp;
300300
}
301301

302-
/*
303-
*
304-
* Converts a Python datetime.datetime or datetime.date
305-
* object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
306-
* to convert to UTC time.
307-
*
308-
* The following implementation just asks for attributes, and thus
309-
* supports datetime duck typing. The tzinfo time zone conversion
310-
* requires this style of access as well.
311-
*
312-
* Returns -1 on error, 0 on success, and 1 (with no error set)
313-
* if obj doesn't have the needed date or datetime attributes.
314-
*/
315-
int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
316-
npy_datetimestruct *out) {
317-
// Assumes that obj is a valid datetime object
318-
PyObject *tmp;
319-
PyObject *obj = (PyObject*)dtobj;
320-
321-
/* Initialize the output to all zeros */
322-
memset(out, 0, sizeof(npy_datetimestruct));
323-
out->month = 1;
324-
out->day = 1;
325-
326-
out->year = PyLong_AsLong(PyObject_GetAttrString(obj, "year"));
327-
out->month = PyLong_AsLong(PyObject_GetAttrString(obj, "month"));
328-
out->day = PyLong_AsLong(PyObject_GetAttrString(obj, "day"));
329-
330-
// TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
331-
// PyDateTime_Check here, and less verbose attribute lookups.
332-
333-
/* Check for time attributes (if not there, return success as a date) */
334-
if (!PyObject_HasAttrString(obj, "hour") ||
335-
!PyObject_HasAttrString(obj, "minute") ||
336-
!PyObject_HasAttrString(obj, "second") ||
337-
!PyObject_HasAttrString(obj, "microsecond")) {
338-
return 0;
339-
}
340-
341-
out->hour = PyLong_AsLong(PyObject_GetAttrString(obj, "hour"));
342-
out->min = PyLong_AsLong(PyObject_GetAttrString(obj, "minute"));
343-
out->sec = PyLong_AsLong(PyObject_GetAttrString(obj, "second"));
344-
out->us = PyLong_AsLong(PyObject_GetAttrString(obj, "microsecond"));
345-
346-
if (PyObject_HasAttrString(obj, "tzinfo")) {
347-
PyObject *offset = extract_utc_offset(obj);
348-
/* Apply the time zone offset if datetime obj is tz-aware */
349-
if (offset != NULL) {
350-
if (offset == Py_None) {
351-
Py_DECREF(offset);
352-
return 0;
353-
}
354-
PyObject *tmp_int;
355-
int seconds_offset, minutes_offset;
356-
/*
357-
* The timedelta should have a function "total_seconds"
358-
* which contains the value we want.
359-
*/
360-
tmp = PyObject_CallMethod(offset, "total_seconds", "");
361-
Py_DECREF(offset);
362-
if (tmp == NULL) {
363-
return -1;
364-
}
365-
tmp_int = PyNumber_Long(tmp);
366-
if (tmp_int == NULL) {
367-
Py_DECREF(tmp);
368-
return -1;
369-
}
370-
seconds_offset = PyLong_AsLong(tmp_int);
371-
if (seconds_offset == -1 && PyErr_Occurred()) {
372-
Py_DECREF(tmp_int);
373-
Py_DECREF(tmp);
374-
return -1;
375-
}
376-
Py_DECREF(tmp_int);
377-
Py_DECREF(tmp);
378-
379-
/* Convert to a minutes offset and apply it */
380-
minutes_offset = seconds_offset / 60;
381-
382-
add_minutes_to_datetimestruct(out, -minutes_offset);
383-
}
384-
}
385-
386-
return 0;
387-
}
388-
389-
390302
/*
391303
* Converts a datetime from a datetimestruct to a datetime based
392304
* on a metadata unit. The date is assumed to be valid.

pandas/_libs/tslibs/src/datetime/np_datetime.h

-3
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ static const npy_datetimestruct _M_MAX_DTS = {
6666

6767
PyObject *extract_utc_offset(PyObject *obj);
6868

69-
int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
70-
npy_datetimestruct *out);
71-
7269
npy_datetime npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT base,
7370
const npy_datetimestruct *dts);
7471

pandas/_libs/tslibs/src/datetime/pd_datetime.c

+155
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,160 @@ static void pandas_datetime_destructor(PyObject *op) {
2828
PyMem_Free(ptr);
2929
}
3030

31+
/*
32+
*
33+
* Converts a Python datetime.datetime or datetime.date
34+
* object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
35+
* to convert to UTC time.
36+
*
37+
* The following implementation just asks for attributes, and thus
38+
* supports datetime duck typing. The tzinfo time zone conversion
39+
* requires this style of access as well.
40+
*
41+
* Returns -1 on error, 0 on success, and 1 (with no error set)
42+
* if obj doesn't have the needed date or datetime attributes.
43+
*/
44+
static int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
45+
npy_datetimestruct *out) {
46+
// Assumes that obj is a valid datetime object
47+
PyObject *tmp;
48+
PyObject *obj = (PyObject*)dtobj;
49+
50+
/* Initialize the output to all zeros */
51+
memset(out, 0, sizeof(npy_datetimestruct));
52+
out->month = 1;
53+
out->day = 1;
54+
55+
out->year = PyLong_AsLong(PyObject_GetAttrString(obj, "year"));
56+
out->month = PyLong_AsLong(PyObject_GetAttrString(obj, "month"));
57+
out->day = PyLong_AsLong(PyObject_GetAttrString(obj, "day"));
58+
59+
// TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
60+
// PyDateTime_Check here, and less verbose attribute lookups.
61+
62+
/* Check for time attributes (if not there, return success as a date) */
63+
if (!PyObject_HasAttrString(obj, "hour") ||
64+
!PyObject_HasAttrString(obj, "minute") ||
65+
!PyObject_HasAttrString(obj, "second") ||
66+
!PyObject_HasAttrString(obj, "microsecond")) {
67+
return 0;
68+
}
69+
70+
out->hour = PyLong_AsLong(PyObject_GetAttrString(obj, "hour"));
71+
out->min = PyLong_AsLong(PyObject_GetAttrString(obj, "minute"));
72+
out->sec = PyLong_AsLong(PyObject_GetAttrString(obj, "second"));
73+
out->us = PyLong_AsLong(PyObject_GetAttrString(obj, "microsecond"));
74+
75+
if (PyObject_HasAttrString(obj, "tzinfo")) {
76+
PyObject *offset = extract_utc_offset(obj);
77+
/* Apply the time zone offset if datetime obj is tz-aware */
78+
if (offset != NULL) {
79+
if (offset == Py_None) {
80+
Py_DECREF(offset);
81+
return 0;
82+
}
83+
PyObject *tmp_int;
84+
int seconds_offset, minutes_offset;
85+
/*
86+
* The timedelta should have a function "total_seconds"
87+
* which contains the value we want.
88+
*/
89+
tmp = PyObject_CallMethod(offset, "total_seconds", "");
90+
Py_DECREF(offset);
91+
if (tmp == NULL) {
92+
return -1;
93+
}
94+
tmp_int = PyNumber_Long(tmp);
95+
if (tmp_int == NULL) {
96+
Py_DECREF(tmp);
97+
return -1;
98+
}
99+
seconds_offset = PyLong_AsLong(tmp_int);
100+
if (seconds_offset == -1 && PyErr_Occurred()) {
101+
Py_DECREF(tmp_int);
102+
Py_DECREF(tmp);
103+
return -1;
104+
}
105+
Py_DECREF(tmp_int);
106+
Py_DECREF(tmp);
107+
108+
/* Convert to a minutes offset and apply it */
109+
minutes_offset = seconds_offset / 60;
110+
111+
add_minutes_to_datetimestruct(out, -minutes_offset);
112+
}
113+
}
114+
115+
return 0;
116+
}
117+
118+
// Converts a Python object representing a Date / Datetime to ISO format
119+
// up to precision `base` e.g. base="s" yields 2020-01-03T00:00:00Z
120+
// while base="ns" yields "2020-01-01T00:00:00.000000000Z"
121+
// len is mutated to save the length of the returned string
122+
static char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base,
123+
size_t *len) {
124+
npy_datetimestruct dts;
125+
int ret;
126+
127+
ret = convert_pydatetime_to_datetimestruct(obj, &dts);
128+
if (ret != 0) {
129+
if (!PyErr_Occurred()) {
130+
PyErr_SetString(PyExc_ValueError,
131+
"Could not convert PyDateTime to numpy datetime");
132+
}
133+
return NULL;
134+
}
135+
136+
*len = (size_t)get_datetime_iso_8601_strlen(0, base);
137+
char *result = PyObject_Malloc(*len);
138+
// Check to see if PyDateTime has a timezone.
139+
// Don't convert to UTC if it doesn't.
140+
int is_tz_aware = 0;
141+
if (PyObject_HasAttrString(obj, "tzinfo")) {
142+
PyObject *offset = extract_utc_offset(obj);
143+
if (offset == NULL) {
144+
PyObject_Free(result);
145+
return NULL;
146+
}
147+
is_tz_aware = offset != Py_None;
148+
Py_DECREF(offset);
149+
}
150+
ret = make_iso_8601_datetime(&dts, result, *len, is_tz_aware, base);
151+
152+
if (ret != 0) {
153+
PyErr_SetString(PyExc_ValueError,
154+
"Could not convert datetime value to string");
155+
PyObject_Free(result);
156+
return NULL;
157+
}
158+
159+
// Note that get_datetime_iso_8601_strlen just gives a generic size
160+
// for ISO string conversion, not the actual size used
161+
*len = strlen(result);
162+
return result;
163+
}
164+
165+
// Convert a Python Date/Datetime to Unix epoch with resolution base
166+
static npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base) {
167+
npy_datetimestruct dts;
168+
int ret;
169+
170+
ret = convert_pydatetime_to_datetimestruct(dt, &dts);
171+
if (ret != 0) {
172+
if (!PyErr_Occurred()) {
173+
PyErr_SetString(PyExc_ValueError,
174+
"Could not convert PyDateTime to numpy datetime");
175+
}
176+
// TODO(username): is setting errMsg required?
177+
// ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
178+
// return NULL;
179+
}
180+
181+
npy_datetime npy_dt = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts);
182+
return NpyDateTimeToEpoch(npy_dt, base);
183+
}
184+
31185
static int pandas_datetime_exec(PyObject *module) {
32186
PyDateTime_IMPORT;
33187
PandasDateTime_CAPI *capi = PyMem_Malloc(sizeof(PandasDateTime_CAPI));
@@ -94,5 +248,6 @@ static struct PyModuleDef pandas_datetimemodule = {
94248
.m_slots = pandas_datetime_slots};
95249

96250
PyMODINIT_FUNC PyInit_pandas_datetime(void) {
251+
PyDateTime_IMPORT;
97252
return PyModuleDef_Init(&pandas_datetimemodule);
98253
}

0 commit comments

Comments
 (0)