Skip to content

Handle "today" and "now" in cython instead of C #18666

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 4 commits into from
Dec 9, 2017
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
2 changes: 1 addition & 1 deletion pandas/_libs/src/datetime/np_datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ void pandas_datetime_to_datetimestruct(npy_datetime val, PANDAS_DATETIMEUNIT fr,
void pandas_timedelta_to_timedeltastruct(npy_timedelta val,
PANDAS_DATETIMEUNIT fr,
pandas_timedeltastruct *result) {
convert_timedelta_to_timedeltastruct(fr, val, result);
convert_timedelta_to_timedeltastruct(fr, val, result);
}


Expand Down
102 changes: 0 additions & 102 deletions pandas/_libs/src/datetime/np_datetime_strings.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,55 +33,6 @@ This file implements string parsing and creation for NumPy datetime.
#include "np_datetime_strings.h"


/* Platform-specific time_t typedef */
typedef time_t NPY_TIME_T;

/*
* Wraps `localtime` functionality for multiple platforms. This
* converts a time value to a time structure in the local timezone.
*
* Returns 0 on success, -1 on failure.
*/
static int get_localtime(NPY_TIME_T *ts, struct tm *tms) {
char *func_name = "<unknown>";
#if defined(_WIN32)
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
if (localtime_s(tms, ts) != 0) {
func_name = "localtime_s";
goto fail;
}
#elif defined(__GNUC__) && defined(NPY_MINGW_USE_CUSTOM_MSVCR)
if (_localtime64_s(tms, ts) != 0) {
func_name = "_localtime64_s";
goto fail;
}
#else
struct tm *tms_tmp;
localtime_r(ts, tms_tmp);
if (tms_tmp == NULL) {
func_name = "localtime";
goto fail;
}
memcpy(tms, tms_tmp, sizeof(struct tm));
#endif
#else
if (localtime_r(ts, tms) == NULL) {
func_name = "localtime_r";
goto fail;
}
#endif

return 0;

fail:
PyErr_Format(PyExc_OSError,
"Failed to use '%s' to convert "
"to a local time",
func_name);
return -1;
}


Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an 49 line version of time.time()

/*
* Parses (almost) standard ISO 8601 date strings. The differences are:
*
Expand Down Expand Up @@ -138,59 +89,6 @@ int parse_iso_8601_datetime(char *str, int len,
out->month = 1;
out->day = 1;

/*
* The string "today" means take today's date in local time, and
* convert it to a date representation. This date representation, if
* forced into a time unit, will be at midnight UTC.
* This is perhaps a little weird, but done so that the
* 'datetime64[D]' type produces the date you expect, rather than
* switching to an adjacent day depending on the current time and your
* timezone.
*/
if (len == 5 && tolower(str[0]) == 't' && tolower(str[1]) == 'o' &&
tolower(str[2]) == 'd' && tolower(str[3]) == 'a' &&
tolower(str[4]) == 'y') {
NPY_TIME_T rawtime = 0;
struct tm tm_;

time(&rawtime);
if (get_localtime(&rawtime, &tm_) < 0) {
return -1;
}
out->year = tm_.tm_year + 1900;
out->month = tm_.tm_mon + 1;
out->day = tm_.tm_mday;

/*
* Indicate that this was a special value, and
* is a date (unit 'D').
*/
if (out_local != NULL) {
*out_local = 0;
}

return 0;
}

/* The string "now" resolves to the current UTC time */
if (len == 3 && tolower(str[0]) == 'n' && tolower(str[1]) == 'o' &&
tolower(str[2]) == 'w') {
NPY_TIME_T rawtime = 0;

time(&rawtime);

/*
* Indicate that this was a special value, and
* use 's' because the time() function has resolution
* seconds.
*/
if (out_local != NULL) {
*out_local = 0;
}

return convert_datetime_to_datetimestruct(PANDAS_FR_s, rawtime, out);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side-benefit: this is the only usage of convert_datetime_to_datetimestruct outside of np_datetime.c, so after this we can get rid of the extra layer there.

}

substr = str;
sublen = len;

Expand Down
33 changes: 28 additions & 5 deletions pandas/_libs/tslib.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ def _test_parse_iso8601(object ts):

obj = _TSObject()

if ts == 'now':
return Timestamp.utcnow()
elif ts == 'today':
return Timestamp.utcnow().normalize()

_string_to_dts(ts, &obj.dts, &out_local, &out_tzoffset)
obj.value = dtstruct_to_dt64(&obj.dts)
check_dts_bounds(&obj.dts)
Expand Down Expand Up @@ -581,12 +586,13 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
elif is_string_object(val):
# string

try:
if len(val) == 0 or val in nat_strings:
iresult[i] = NPY_NAT
continue
if len(val) == 0 or val in nat_strings:
iresult[i] = NPY_NAT
continue

seen_string = 1

seen_string = 1
try:
_string_to_dts(val, &dts, &out_local, &out_tzoffset)
value = dtstruct_to_dt64(&dts)
if out_local == 1:
Expand All @@ -597,6 +603,8 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
except ValueError:
# if requiring iso8601 strings, skip trying other formats
if require_iso8601:
if _parse_today_now(val, &iresult[i]):
continue
if is_coerce:
iresult[i] = NPY_NAT
continue
Expand All @@ -611,6 +619,8 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
py_dt = parse_datetime_string(val, dayfirst=dayfirst,
yearfirst=yearfirst)
except Exception:
if _parse_today_now(val, &iresult[i]):
continue
if is_coerce:
iresult[i] = NPY_NAT
continue
Expand Down Expand Up @@ -706,6 +716,19 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
return oresult


cdef inline bint _parse_today_now(str val, int64_t* iresult):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are moving these routines to conversion, yes? (obviously not in this PR)

# We delay this check for as long as possible
# because it catches relatively rare cases
if val == 'now':
# Note: this is *not* the same as Timestamp('now')
iresult[0] = Timestamp.utcnow().value
return True
elif val == 'today':
# Note: this is *not* the same as Timestamp('today')
iresult[0] = Timestamp.utcnow().normalize().value
return True
return False

# ----------------------------------------------------------------------
# Some general helper functions

Expand Down