Skip to content

Refactored custom datetime functions #52634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 0 additions & 63 deletions pandas/_libs/tslibs/src/datetime/date_conversions.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,69 +73,6 @@ npy_datetime NpyDateTimeToEpoch(npy_datetime dt, NPY_DATETIMEUNIT base) {
return dt;
}

/* Convert PyDatetime To ISO C-string. mutates len */
char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base,
size_t *len) {
npy_datetimestruct dts;
int ret;

ret = convert_pydatetime_to_datetimestruct(obj, &dts);
if (ret != 0) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError,
"Could not convert PyDateTime to numpy datetime");
}
return NULL;
}

*len = (size_t)get_datetime_iso_8601_strlen(0, base);
char *result = PyObject_Malloc(*len);
// Check to see if PyDateTime has a timezone.
// Don't convert to UTC if it doesn't.
int is_tz_aware = 0;
if (PyObject_HasAttrString(obj, "tzinfo")) {
PyObject *offset = extract_utc_offset(obj);
if (offset == NULL) {
PyObject_Free(result);
return NULL;
}
is_tz_aware = offset != Py_None;
Py_DECREF(offset);
}
ret = make_iso_8601_datetime(&dts, result, *len, is_tz_aware, base);

if (ret != 0) {
PyErr_SetString(PyExc_ValueError,
"Could not convert datetime value to string");
PyObject_Free(result);
return NULL;
}

// Note that get_datetime_iso_8601_strlen just gives a generic size
// for ISO string conversion, not the actual size used
*len = strlen(result);
return result;
}

npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base) {
npy_datetimestruct dts;
int ret;

ret = convert_pydatetime_to_datetimestruct(dt, &dts);
if (ret != 0) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError,
"Could not convert PyDateTime to numpy datetime");
}
// TODO(username): is setting errMsg required?
// ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
// return NULL;
}

npy_datetime npy_dt = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts);
return NpyDateTimeToEpoch(npy_dt, base);
}

/* Converts the int64_t representation of a duration to ISO; mutates len */
char *int64ToIsoDuration(int64_t value, size_t *len) {
pandas_timedeltastruct tds;
Expand Down
9 changes: 0 additions & 9 deletions pandas/_libs/tslibs/src/datetime/date_conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ char *int64ToIso(int64_t value, NPY_DATETIMEUNIT base, size_t *len);
// replace with scaleNanosecToUnit
npy_datetime NpyDateTimeToEpoch(npy_datetime dt, NPY_DATETIMEUNIT base);

// Converts a Python object representing a Date / Datetime to ISO format
// up to precision `base` e.g. base="s" yields 2020-01-03T00:00:00Z
// while base="ns" yields "2020-01-01T00:00:00.000000000Z"
// len is mutated to save the length of the returned string
char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base, size_t *len);

// Convert a Python Date/Datetime to Unix epoch with resolution base
npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base);

char *int64ToIsoDuration(int64_t value, size_t *len);

#endif // PANDAS__LIBS_TSLIBS_SRC_DATETIME_DATE_CONVERSIONS_H_
88 changes: 0 additions & 88 deletions pandas/_libs/tslibs/src/datetime/np_datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -299,94 +299,6 @@ PyObject *extract_utc_offset(PyObject *obj) {
return tmp;
}

/*
*
* Converts a Python datetime.datetime or datetime.date
* object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
* to convert to UTC time.
*
* The following implementation just asks for attributes, and thus
* supports datetime duck typing. The tzinfo time zone conversion
* requires this style of access as well.
*
* Returns -1 on error, 0 on success, and 1 (with no error set)
* if obj doesn't have the needed date or datetime attributes.
*/
int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
npy_datetimestruct *out) {
// Assumes that obj is a valid datetime object
PyObject *tmp;
PyObject *obj = (PyObject*)dtobj;

/* Initialize the output to all zeros */
memset(out, 0, sizeof(npy_datetimestruct));
out->month = 1;
out->day = 1;

out->year = PyLong_AsLong(PyObject_GetAttrString(obj, "year"));
out->month = PyLong_AsLong(PyObject_GetAttrString(obj, "month"));
out->day = PyLong_AsLong(PyObject_GetAttrString(obj, "day"));

// TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
// PyDateTime_Check here, and less verbose attribute lookups.

/* Check for time attributes (if not there, return success as a date) */
if (!PyObject_HasAttrString(obj, "hour") ||
!PyObject_HasAttrString(obj, "minute") ||
!PyObject_HasAttrString(obj, "second") ||
!PyObject_HasAttrString(obj, "microsecond")) {
return 0;
}

out->hour = PyLong_AsLong(PyObject_GetAttrString(obj, "hour"));
out->min = PyLong_AsLong(PyObject_GetAttrString(obj, "minute"));
out->sec = PyLong_AsLong(PyObject_GetAttrString(obj, "second"));
out->us = PyLong_AsLong(PyObject_GetAttrString(obj, "microsecond"));

if (PyObject_HasAttrString(obj, "tzinfo")) {
PyObject *offset = extract_utc_offset(obj);
/* Apply the time zone offset if datetime obj is tz-aware */
if (offset != NULL) {
if (offset == Py_None) {
Py_DECREF(offset);
return 0;
}
PyObject *tmp_int;
int seconds_offset, minutes_offset;
/*
* The timedelta should have a function "total_seconds"
* which contains the value we want.
*/
tmp = PyObject_CallMethod(offset, "total_seconds", "");
Py_DECREF(offset);
if (tmp == NULL) {
return -1;
}
tmp_int = PyNumber_Long(tmp);
if (tmp_int == NULL) {
Py_DECREF(tmp);
return -1;
}
seconds_offset = PyLong_AsLong(tmp_int);
if (seconds_offset == -1 && PyErr_Occurred()) {
Py_DECREF(tmp_int);
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp_int);
Py_DECREF(tmp);

/* Convert to a minutes offset and apply it */
minutes_offset = seconds_offset / 60;

add_minutes_to_datetimestruct(out, -minutes_offset);
}
}

return 0;
}


/*
* Converts a datetime from a datetimestruct to a datetime based
* on a metadata unit. The date is assumed to be valid.
Expand Down
3 changes: 0 additions & 3 deletions pandas/_libs/tslibs/src/datetime/np_datetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ static const npy_datetimestruct _M_MAX_DTS = {

PyObject *extract_utc_offset(PyObject *obj);

int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
npy_datetimestruct *out);

npy_datetime npy_datetimestruct_to_datetime(NPY_DATETIMEUNIT base,
const npy_datetimestruct *dts);

Expand Down
155 changes: 155 additions & 0 deletions pandas/_libs/tslibs/src/datetime/pd_datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,160 @@ static void pandas_datetime_destructor(PyObject *op) {
PyMem_Free(ptr);
}

/*
*
* Converts a Python datetime.datetime or datetime.date
* object into a NumPy npy_datetimestruct. Uses tzinfo (if present)
* to convert to UTC time.
*
* The following implementation just asks for attributes, and thus
* supports datetime duck typing. The tzinfo time zone conversion
* requires this style of access as well.
*
* Returns -1 on error, 0 on success, and 1 (with no error set)
* if obj doesn't have the needed date or datetime attributes.
*/
static int convert_pydatetime_to_datetimestruct(PyObject *dtobj,
npy_datetimestruct *out) {
// Assumes that obj is a valid datetime object
PyObject *tmp;
PyObject *obj = (PyObject*)dtobj;

/* Initialize the output to all zeros */
memset(out, 0, sizeof(npy_datetimestruct));
out->month = 1;
out->day = 1;

out->year = PyLong_AsLong(PyObject_GetAttrString(obj, "year"));
out->month = PyLong_AsLong(PyObject_GetAttrString(obj, "month"));
out->day = PyLong_AsLong(PyObject_GetAttrString(obj, "day"));

// TODO(anyone): If we can get PyDateTime_IMPORT to work, we could use
// PyDateTime_Check here, and less verbose attribute lookups.

/* Check for time attributes (if not there, return success as a date) */
if (!PyObject_HasAttrString(obj, "hour") ||
!PyObject_HasAttrString(obj, "minute") ||
!PyObject_HasAttrString(obj, "second") ||
!PyObject_HasAttrString(obj, "microsecond")) {
return 0;
}

out->hour = PyLong_AsLong(PyObject_GetAttrString(obj, "hour"));
out->min = PyLong_AsLong(PyObject_GetAttrString(obj, "minute"));
out->sec = PyLong_AsLong(PyObject_GetAttrString(obj, "second"));
out->us = PyLong_AsLong(PyObject_GetAttrString(obj, "microsecond"));

if (PyObject_HasAttrString(obj, "tzinfo")) {
PyObject *offset = extract_utc_offset(obj);
/* Apply the time zone offset if datetime obj is tz-aware */
if (offset != NULL) {
if (offset == Py_None) {
Py_DECREF(offset);
return 0;
}
PyObject *tmp_int;
int seconds_offset, minutes_offset;
/*
* The timedelta should have a function "total_seconds"
* which contains the value we want.
*/
tmp = PyObject_CallMethod(offset, "total_seconds", "");
Py_DECREF(offset);
if (tmp == NULL) {
return -1;
}
tmp_int = PyNumber_Long(tmp);
if (tmp_int == NULL) {
Py_DECREF(tmp);
return -1;
}
seconds_offset = PyLong_AsLong(tmp_int);
if (seconds_offset == -1 && PyErr_Occurred()) {
Py_DECREF(tmp_int);
Py_DECREF(tmp);
return -1;
}
Py_DECREF(tmp_int);
Py_DECREF(tmp);

/* Convert to a minutes offset and apply it */
minutes_offset = seconds_offset / 60;

add_minutes_to_datetimestruct(out, -minutes_offset);
}
}

return 0;
}

// Converts a Python object representing a Date / Datetime to ISO format
// up to precision `base` e.g. base="s" yields 2020-01-03T00:00:00Z
// while base="ns" yields "2020-01-01T00:00:00.000000000Z"
// len is mutated to save the length of the returned string
static char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base,
size_t *len) {
npy_datetimestruct dts;
int ret;

ret = convert_pydatetime_to_datetimestruct(obj, &dts);
if (ret != 0) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError,
"Could not convert PyDateTime to numpy datetime");
}
return NULL;
}

*len = (size_t)get_datetime_iso_8601_strlen(0, base);
char *result = PyObject_Malloc(*len);
// Check to see if PyDateTime has a timezone.
// Don't convert to UTC if it doesn't.
int is_tz_aware = 0;
if (PyObject_HasAttrString(obj, "tzinfo")) {
PyObject *offset = extract_utc_offset(obj);
if (offset == NULL) {
PyObject_Free(result);
return NULL;
}
is_tz_aware = offset != Py_None;
Py_DECREF(offset);
}
ret = make_iso_8601_datetime(&dts, result, *len, is_tz_aware, base);

if (ret != 0) {
PyErr_SetString(PyExc_ValueError,
"Could not convert datetime value to string");
PyObject_Free(result);
return NULL;
}

// Note that get_datetime_iso_8601_strlen just gives a generic size
// for ISO string conversion, not the actual size used
*len = strlen(result);
return result;
}

// Convert a Python Date/Datetime to Unix epoch with resolution base
static npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base) {
npy_datetimestruct dts;
int ret;

ret = convert_pydatetime_to_datetimestruct(dt, &dts);
if (ret != 0) {
if (!PyErr_Occurred()) {
PyErr_SetString(PyExc_ValueError,
"Could not convert PyDateTime to numpy datetime");
}
// TODO(username): is setting errMsg required?
// ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
// return NULL;
}

npy_datetime npy_dt = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts);
return NpyDateTimeToEpoch(npy_dt, base);
}

static int pandas_datetime_exec(PyObject *module) {
PyDateTime_IMPORT;
PandasDateTime_CAPI *capi = PyMem_Malloc(sizeof(PandasDateTime_CAPI));
Expand Down Expand Up @@ -94,5 +248,6 @@ static struct PyModuleDef pandas_datetimemodule = {
.m_slots = pandas_datetime_slots};

PyMODINIT_FUNC PyInit_pandas_datetime(void) {
PyDateTime_IMPORT;
return PyModuleDef_Init(&pandas_datetimemodule);
}