Skip to content

ENH: port time.pxd from cython #45864

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 5 commits into from
Feb 15, 2022
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
78 changes: 78 additions & 0 deletions pandas/_libs/tslibs/ctime.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Cython implementation of (parts of) the standard library time module.
"""

from cpython.exc cimport PyErr_SetFromErrno
from libc.stdint cimport int64_t


cdef extern from "Python.h":
ctypedef int64_t _PyTime_t
_PyTime_t _PyTime_GetSystemClock() nogil
double _PyTime_AsSecondsDouble(_PyTime_t t) nogil

from libc.time cimport (
localtime as libc_localtime,
time_t,
tm,
)


def pytime():
"""
python-exposed for testing
"""
return time()


def pylocaltime():
"""
python-exposed for testing
"""
lt = localtime()
# https://github.com/pandas-dev/pandas/pull/45864#issuecomment-1033021599
return {
"tm_year": lt.tm_year,
"tm_mon": lt.tm_mon,
"tm_mday": lt.tm_mday,
"tm_hour": lt.tm_hour,
"tm_min": lt.tm_min,
"tm_sec": lt.tm_sec,
"tm_wday": lt.tm_wday,
"tm_yday": lt.tm_yday,
"tm_isdst": lt.tm_isdst,
}


cdef inline double time() nogil:
cdef:
_PyTime_t tic

tic = _PyTime_GetSystemClock()
return _PyTime_AsSecondsDouble(tic)


cdef inline int _raise_from_errno() except -1 with gil:
PyErr_SetFromErrno(RuntimeError)
return <int>-1 # Let the C compiler know that this function always raises.


cdef inline tm localtime() nogil except *:
"""
Analogue to the stdlib time.localtime. The returned struct
has some entries that the stdlib version does not: tm_gmtoff, tm_zone
"""
cdef:
time_t tic = <time_t>time()
tm* result

result = libc_localtime(&tic)
if result is NULL:
_raise_from_errno()
# Fix 0-based date values (and the 1900-based year).
# See tmtotuple() in https://github.com/python/cpython/blob/master/Modules/timemodule.c
result.tm_year += 1900
result.tm_mon += 1
result.tm_wday = (result.tm_wday + 6) % 7
result.tm_yday += 1
return result[0]
3 changes: 2 additions & 1 deletion pandas/tests/tslibs/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ def test_namespace():
]

expected = set(submodules + api)
names = [x for x in dir(tslibs) if not x.startswith("__")]
# exclude "ctime" bc it is not (yet) imported outside of tests
names = [x for x in dir(tslibs) if not x.startswith("__") and x != "ctime"]
assert set(names) == expected
44 changes: 44 additions & 0 deletions pandas/tests/tslibs/test_ctime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
These tests are adapted from cython's tests
https://github.com/cython/cython/blob/master/tests/run/time_pxd.pyx
"""
# TODO(cython3): use cython's cpython.time implementation


import time

# error: Module "pandas._libs.tslibs" has no attribute "ctime"
from pandas._libs.tslibs import ctime # type: ignore[attr-defined]


def test_time():
# check that ctime.time() matches time.time() to within call-time tolerance
tic1 = time.time()
tic2 = ctime.pytime()
tic3 = time.time()

assert tic1 <= tic3 # sanity check
assert tic1 <= tic2
assert tic2 <= tic3


def test_localtime():
ltp = time.localtime()
ltc = ctime.pylocaltime()

if ltp.tm_sec != ltc["tm_sec"]:
# If the time.localtime call is just before the end of a second and the
# ctime.localtime call is just after the beginning of the next second,
# re-call. This should not occur twice in a row.
ltp = time.localtime()
ltc = ctime.pylocaltime()

assert ltp.tm_year == ltc["tm_year"] or (ltp.tm_year, ltc["tm_year"])
assert ltp.tm_mon == ltc["tm_mon"] or (ltp.tm_mon, ltc["tm_mon"])
assert ltp.tm_mday == ltc["tm_mday"] or (ltp.tm_mday, ltc["tm_mday"])
assert ltp.tm_hour == ltc["tm_hour"] or (ltp.tm_hour, ltc["tm_hour"])
assert ltp.tm_min == ltc["tm_min"] or (ltp.tm_min, ltc["tm_min"])
assert ltp.tm_sec == ltc["tm_sec"] or (ltp.tm_sec, ltc["tm_sec"])
assert ltp.tm_wday == ltc["tm_wday"] or (ltp.tm_wday, ltc["tm_wday"])
assert ltp.tm_yday == ltc["tm_yday"] or (ltp.tm_yday, ltc["tm_yday"])
assert ltp.tm_isdst == ltc["tm_isdst"] or (ltp.tm_isdst, ltc["tm_isdst"])
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class CheckSDist(sdist_class):
"pandas/_libs/parsers.pyx",
"pandas/_libs/tslibs/base.pyx",
"pandas/_libs/tslibs/ccalendar.pyx",
"pandas/_libs/tslibs/ctime.pyx",
"pandas/_libs/tslibs/dtypes.pyx",
"pandas/_libs/tslibs/period.pyx",
"pandas/_libs/tslibs/strptime.pyx",
Expand Down Expand Up @@ -495,6 +496,7 @@ def srcpath(name=None, suffix=".pyx", subdir="src"):
"_libs.tslib": {"pyxfile": "_libs/tslib", "depends": tseries_depends},
"_libs.tslibs.base": {"pyxfile": "_libs/tslibs/base"},
"_libs.tslibs.ccalendar": {"pyxfile": "_libs/tslibs/ccalendar"},
"_libs.tslibs.ctime": {"pyxfile": "_libs/tslibs/ctime"},
"_libs.tslibs.dtypes": {"pyxfile": "_libs/tslibs/dtypes"},
"_libs.tslibs.conversion": {
"pyxfile": "_libs/tslibs/conversion",
Expand Down