Skip to content

Commit 34a8d36

Browse files
jbrockmendeljreback
authored andcommitted
Handle "today" and "now" in cython instead of C (#18666)
1 parent 86606b2 commit 34a8d36

File tree

3 files changed

+29
-108
lines changed

3 files changed

+29
-108
lines changed

pandas/_libs/src/datetime/np_datetime.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ void pandas_datetime_to_datetimestruct(npy_datetime val, PANDAS_DATETIMEUNIT fr,
527527
void pandas_timedelta_to_timedeltastruct(npy_timedelta val,
528528
PANDAS_DATETIMEUNIT fr,
529529
pandas_timedeltastruct *result) {
530-
convert_timedelta_to_timedeltastruct(fr, val, result);
530+
convert_timedelta_to_timedeltastruct(fr, val, result);
531531
}
532532

533533

pandas/_libs/src/datetime/np_datetime_strings.c

-102
Original file line numberDiff line numberDiff line change
@@ -33,55 +33,6 @@ This file implements string parsing and creation for NumPy datetime.
3333
#include "np_datetime_strings.h"
3434

3535

36-
/* Platform-specific time_t typedef */
37-
typedef time_t NPY_TIME_T;
38-
39-
/*
40-
* Wraps `localtime` functionality for multiple platforms. This
41-
* converts a time value to a time structure in the local timezone.
42-
*
43-
* Returns 0 on success, -1 on failure.
44-
*/
45-
static int get_localtime(NPY_TIME_T *ts, struct tm *tms) {
46-
char *func_name = "<unknown>";
47-
#if defined(_WIN32)
48-
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
49-
if (localtime_s(tms, ts) != 0) {
50-
func_name = "localtime_s";
51-
goto fail;
52-
}
53-
#elif defined(__GNUC__) && defined(NPY_MINGW_USE_CUSTOM_MSVCR)
54-
if (_localtime64_s(tms, ts) != 0) {
55-
func_name = "_localtime64_s";
56-
goto fail;
57-
}
58-
#else
59-
struct tm *tms_tmp;
60-
localtime_r(ts, tms_tmp);
61-
if (tms_tmp == NULL) {
62-
func_name = "localtime";
63-
goto fail;
64-
}
65-
memcpy(tms, tms_tmp, sizeof(struct tm));
66-
#endif
67-
#else
68-
if (localtime_r(ts, tms) == NULL) {
69-
func_name = "localtime_r";
70-
goto fail;
71-
}
72-
#endif
73-
74-
return 0;
75-
76-
fail:
77-
PyErr_Format(PyExc_OSError,
78-
"Failed to use '%s' to convert "
79-
"to a local time",
80-
func_name);
81-
return -1;
82-
}
83-
84-
8536
/*
8637
* Parses (almost) standard ISO 8601 date strings. The differences are:
8738
*
@@ -138,59 +89,6 @@ int parse_iso_8601_datetime(char *str, int len,
13889
out->month = 1;
13990
out->day = 1;
14091

141-
/*
142-
* The string "today" means take today's date in local time, and
143-
* convert it to a date representation. This date representation, if
144-
* forced into a time unit, will be at midnight UTC.
145-
* This is perhaps a little weird, but done so that the
146-
* 'datetime64[D]' type produces the date you expect, rather than
147-
* switching to an adjacent day depending on the current time and your
148-
* timezone.
149-
*/
150-
if (len == 5 && tolower(str[0]) == 't' && tolower(str[1]) == 'o' &&
151-
tolower(str[2]) == 'd' && tolower(str[3]) == 'a' &&
152-
tolower(str[4]) == 'y') {
153-
NPY_TIME_T rawtime = 0;
154-
struct tm tm_;
155-
156-
time(&rawtime);
157-
if (get_localtime(&rawtime, &tm_) < 0) {
158-
return -1;
159-
}
160-
out->year = tm_.tm_year + 1900;
161-
out->month = tm_.tm_mon + 1;
162-
out->day = tm_.tm_mday;
163-
164-
/*
165-
* Indicate that this was a special value, and
166-
* is a date (unit 'D').
167-
*/
168-
if (out_local != NULL) {
169-
*out_local = 0;
170-
}
171-
172-
return 0;
173-
}
174-
175-
/* The string "now" resolves to the current UTC time */
176-
if (len == 3 && tolower(str[0]) == 'n' && tolower(str[1]) == 'o' &&
177-
tolower(str[2]) == 'w') {
178-
NPY_TIME_T rawtime = 0;
179-
180-
time(&rawtime);
181-
182-
/*
183-
* Indicate that this was a special value, and
184-
* use 's' because the time() function has resolution
185-
* seconds.
186-
*/
187-
if (out_local != NULL) {
188-
*out_local = 0;
189-
}
190-
191-
return convert_datetime_to_datetimestruct(PANDAS_FR_s, rawtime, out);
192-
}
193-
19492
substr = str;
19593
sublen = len;
19694

pandas/_libs/tslib.pyx

+28-5
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ def _test_parse_iso8601(object ts):
208208

209209
obj = _TSObject()
210210

211+
if ts == 'now':
212+
return Timestamp.utcnow()
213+
elif ts == 'today':
214+
return Timestamp.utcnow().normalize()
215+
211216
_string_to_dts(ts, &obj.dts, &out_local, &out_tzoffset)
212217
obj.value = dtstruct_to_dt64(&obj.dts)
213218
check_dts_bounds(&obj.dts)
@@ -581,12 +586,13 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
581586
elif is_string_object(val):
582587
# string
583588

584-
try:
585-
if len(val) == 0 or val in nat_strings:
586-
iresult[i] = NPY_NAT
587-
continue
589+
if len(val) == 0 or val in nat_strings:
590+
iresult[i] = NPY_NAT
591+
continue
592+
593+
seen_string = 1
588594

589-
seen_string = 1
595+
try:
590596
_string_to_dts(val, &dts, &out_local, &out_tzoffset)
591597
value = dtstruct_to_dt64(&dts)
592598
if out_local == 1:
@@ -597,6 +603,8 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
597603
except ValueError:
598604
# if requiring iso8601 strings, skip trying other formats
599605
if require_iso8601:
606+
if _parse_today_now(val, &iresult[i]):
607+
continue
600608
if is_coerce:
601609
iresult[i] = NPY_NAT
602610
continue
@@ -611,6 +619,8 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
611619
py_dt = parse_datetime_string(val, dayfirst=dayfirst,
612620
yearfirst=yearfirst)
613621
except Exception:
622+
if _parse_today_now(val, &iresult[i]):
623+
continue
614624
if is_coerce:
615625
iresult[i] = NPY_NAT
616626
continue
@@ -706,6 +716,19 @@ cpdef array_to_datetime(ndarray[object] values, errors='raise',
706716
return oresult
707717

708718

719+
cdef inline bint _parse_today_now(str val, int64_t* iresult):
720+
# We delay this check for as long as possible
721+
# because it catches relatively rare cases
722+
if val == 'now':
723+
# Note: this is *not* the same as Timestamp('now')
724+
iresult[0] = Timestamp.utcnow().value
725+
return True
726+
elif val == 'today':
727+
# Note: this is *not* the same as Timestamp('today')
728+
iresult[0] = Timestamp.utcnow().normalize().value
729+
return True
730+
return False
731+
709732
# ----------------------------------------------------------------------
710733
# Some general helper functions
711734

0 commit comments

Comments
 (0)