# -*- coding: utf-8 -*- # cython: profile=False from cpython cimport (Py_EQ, Py_NE, Py_GE, Py_GT, Py_LT, Py_LE, PyUnicode_Check, PyUnicode_AsASCIIString) from cpython.datetime cimport (datetime, date, PyDateTime_IMPORT, PyDateTime_GET_YEAR, PyDateTime_GET_MONTH, PyDateTime_GET_DAY, PyDateTime_DATE_GET_HOUR, PyDateTime_DATE_GET_MINUTE, PyDateTime_DATE_GET_SECOND, PyDateTime_DATE_GET_MICROSECOND) PyDateTime_IMPORT from numpy cimport int64_t cdef extern from "../src/datetime/np_datetime.h": int cmp_pandas_datetimestruct(pandas_datetimestruct *a, pandas_datetimestruct *b) npy_datetime pandas_datetimestruct_to_datetime(PANDAS_DATETIMEUNIT fr, pandas_datetimestruct *d ) nogil void pandas_datetime_to_datetimestruct(npy_datetime val, PANDAS_DATETIMEUNIT fr, pandas_datetimestruct *result) nogil void pandas_timedelta_to_timedeltastruct(npy_timedelta val, PANDAS_DATETIMEUNIT fr, pandas_timedeltastruct *result ) nogil pandas_datetimestruct _NS_MIN_DTS, _NS_MAX_DTS cdef extern from "../src/datetime/np_datetime_strings.h": int parse_iso_8601_datetime(char *str, int len, pandas_datetimestruct *out, int *out_local, int *out_tzoffset) # ---------------------------------------------------------------------- # numpy object inspection cdef inline npy_datetime get_datetime64_value(object obj) nogil: """ returns the int64 value underlying scalar numpy datetime64 object Note that to interpret this as a datetime, the corresponding unit is also needed. That can be found using `get_datetime64_unit`. """ return (obj).obval cdef inline npy_timedelta get_timedelta64_value(object obj) nogil: """ returns the int64 value underlying scalar numpy timedelta64 object """ return (obj).obval cdef inline PANDAS_DATETIMEUNIT get_datetime64_unit(object obj) nogil: """ returns the unit part of the dtype for a numpy datetime64 object. """ return (obj).obmeta.base # ---------------------------------------------------------------------- # Comparison cdef int reverse_ops[6] reverse_ops[Py_LT] = Py_GT reverse_ops[Py_LE] = Py_GE reverse_ops[Py_EQ] = Py_EQ reverse_ops[Py_NE] = Py_NE reverse_ops[Py_GT] = Py_LT reverse_ops[Py_GE] = Py_LE cdef inline bint cmp_scalar(int64_t lhs, int64_t rhs, int op) except -1: """ cmp_scalar is a more performant version of PyObject_RichCompare typed for int64_t arguments. """ if op == Py_EQ: return lhs == rhs elif op == Py_NE: return lhs != rhs elif op == Py_LT: return lhs < rhs elif op == Py_LE: return lhs <= rhs elif op == Py_GT: return lhs > rhs elif op == Py_GE: return lhs >= rhs class OutOfBoundsDatetime(ValueError): pass cdef inline check_dts_bounds(pandas_datetimestruct *dts): """Raises OutOfBoundsDatetime if the given date is outside the range that can be represented by nanosecond-resolution 64-bit integers.""" cdef: bint error = False if (dts.year <= 1677 and cmp_pandas_datetimestruct(dts, &_NS_MIN_DTS) == -1): error = True elif (dts.year >= 2262 and cmp_pandas_datetimestruct(dts, &_NS_MAX_DTS) == 1): error = True if error: fmt = '%d-%.2d-%.2d %.2d:%.2d:%.2d' % (dts.year, dts.month, dts.day, dts.hour, dts.min, dts.sec) raise OutOfBoundsDatetime( 'Out of bounds nanosecond timestamp: {fmt}'.format(fmt=fmt)) # ---------------------------------------------------------------------- # Conversion cdef inline int64_t dtstruct_to_dt64(pandas_datetimestruct* dts) nogil: """Convenience function to call pandas_datetimestruct_to_datetime with the by-far-most-common frequency PANDAS_FR_ns""" return pandas_datetimestruct_to_datetime(PANDAS_FR_ns, dts) cdef inline void dt64_to_dtstruct(int64_t dt64, pandas_datetimestruct* out) nogil: """Convenience function to call pandas_datetime_to_datetimestruct with the by-far-most-common frequency PANDAS_FR_ns""" pandas_datetime_to_datetimestruct(dt64, PANDAS_FR_ns, out) return cdef inline void td64_to_tdstruct(int64_t td64, pandas_timedeltastruct* out) nogil: """Convenience function to call pandas_timedelta_to_timedeltastruct with the by-far-most-common frequency PANDAS_FR_ns""" pandas_timedelta_to_timedeltastruct(td64, PANDAS_FR_ns, out) return cdef inline int64_t pydatetime_to_dt64(datetime val, pandas_datetimestruct *dts): dts.year = PyDateTime_GET_YEAR(val) dts.month = PyDateTime_GET_MONTH(val) dts.day = PyDateTime_GET_DAY(val) dts.hour = PyDateTime_DATE_GET_HOUR(val) dts.min = PyDateTime_DATE_GET_MINUTE(val) dts.sec = PyDateTime_DATE_GET_SECOND(val) dts.us = PyDateTime_DATE_GET_MICROSECOND(val) dts.ps = dts.as = 0 return dtstruct_to_dt64(dts) cdef inline int64_t pydate_to_dt64(date val, pandas_datetimestruct *dts): dts.year = PyDateTime_GET_YEAR(val) dts.month = PyDateTime_GET_MONTH(val) dts.day = PyDateTime_GET_DAY(val) dts.hour = dts.min = dts.sec = dts.us = 0 dts.ps = dts.as = 0 return dtstruct_to_dt64(dts) cdef inline int _string_to_dts(object val, pandas_datetimestruct* dts, int* out_local, int* out_tzoffset) except? -1: cdef: int result char *tmp if PyUnicode_Check(val): val = PyUnicode_AsASCIIString(val) tmp = val result = _cstring_to_dts(tmp, len(val), dts, out_local, out_tzoffset) if result == -1: raise ValueError('Unable to parse %s' % str(val)) return result cdef inline int _cstring_to_dts(char *val, int length, pandas_datetimestruct* dts, int* out_local, int* out_tzoffset) except? -1: # Note: without this "extra layer" between _string_to_dts # and parse_iso_8601_datetime, calling _string_to_dts raises # `SystemError: returned a result with an error set` # in Python3 cdef: int result result = parse_iso_8601_datetime(val, length, dts, out_local, out_tzoffset) return result