From fb4f221c72f640d498edd08b6e88d34bd89cf797 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Wed, 20 May 2020 21:44:43 -0700 Subject: [PATCH] REF: put cast_from_unit in conversion to de-circularize cimports --- pandas/_libs/tslib.pyx | 8 ++-- pandas/_libs/tslibs/conversion.pxd | 1 + pandas/_libs/tslibs/conversion.pyx | 77 +++++++++++++++++++++++++++++- pandas/_libs/tslibs/timedeltas.pxd | 1 - pandas/_libs/tslibs/timedeltas.pyx | 73 +--------------------------- pandas/core/arrays/timedeltas.py | 7 +-- 6 files changed, 84 insertions(+), 83 deletions(-) diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index 429848ed5c746..44693d60486a9 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -41,7 +41,6 @@ from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime from pandas._libs.tslibs.parsing import parse_datetime_string -from pandas._libs.tslibs.timedeltas cimport cast_from_unit from pandas._libs.tslibs.timezones cimport ( get_dst_info, is_utc, @@ -49,8 +48,11 @@ from pandas._libs.tslibs.timezones cimport ( utc_pytz as UTC, ) from pandas._libs.tslibs.conversion cimport ( - _TSObject, convert_datetime_to_tsobject, - get_datetime64_nanos) + _TSObject, + cast_from_unit, + convert_datetime_to_tsobject, + get_datetime64_nanos, +) from pandas._libs.tslibs.nattype cimport ( NPY_NAT, diff --git a/pandas/_libs/tslibs/conversion.pxd b/pandas/_libs/tslibs/conversion.pxd index e5b2a37860068..15313c0c2c3dd 100644 --- a/pandas/_libs/tslibs/conversion.pxd +++ b/pandas/_libs/tslibs/conversion.pxd @@ -23,3 +23,4 @@ cdef _TSObject convert_datetime_to_tsobject(datetime ts, object tz, cdef int64_t get_datetime64_nanos(object val) except? -1 cpdef datetime localize_pydatetime(datetime dt, object tz) +cdef int64_t cast_from_unit(object ts, str unit) except? -1 diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 199a41a6a0cf3..ae1da0ac18e58 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -25,7 +25,6 @@ from pandas._libs.tslibs.np_datetime import OutOfBoundsDatetime from pandas._libs.tslibs.util cimport ( is_datetime64_object, is_integer_object, is_float_object) -from pandas._libs.tslibs.timedeltas cimport cast_from_unit from pandas._libs.tslibs.timezones cimport ( is_utc, is_tzlocal, is_fixed_offset, get_utcoffset, get_dst_info, get_timezone, maybe_get_tz, tz_compare, @@ -55,7 +54,78 @@ TD64NS_DTYPE = np.dtype('m8[ns]') # ---------------------------------------------------------------------- -# Misc Helpers +# Unit Conversion Helpers + +cdef inline int64_t cast_from_unit(object ts, str unit) except? -1: + """ return a casting of the unit represented to nanoseconds + round the fractional part of a float to our precision, p """ + cdef: + int64_t m + int p + + m, p = precision_from_unit(unit) + + # just give me the unit back + if ts is None: + return m + + # cast the unit, multiply base/frace separately + # to avoid precision issues from float -> int + base = ts + frac = ts - base + if p: + frac = round(frac, p) + return (base * m) + (frac * m) + + +cpdef inline object precision_from_unit(str unit): + """ + Return a casting of the unit represented to nanoseconds + the precision + to round the fractional part. + + Notes + ----- + The caller is responsible for ensuring that the default value of "ns" + takes the place of None. + """ + cdef: + int64_t m + int p + + if unit == "Y": + m = 1_000_000_000 * 31556952 + p = 9 + elif unit == "M": + m = 1_000_000_000 * 2629746 + p = 9 + elif unit == "W": + m = 1_000_000_000 * 3600 * 24 * 7 + p = 9 + elif unit == "D" or unit == "d": + m = 1_000_000_000 * 3600 * 24 + p = 9 + elif unit == "h": + m = 1_000_000_000 * 3600 + p = 9 + elif unit == "m": + m = 1_000_000_000 * 60 + p = 9 + elif unit == "s": + m = 1_000_000_000 + p = 9 + elif unit == "ms": + m = 1_000_000 + p = 6 + elif unit == "us": + m = 1000 + p = 3 + elif unit == "ns" or unit is None: + m = 1 + p = 0 + else: + raise ValueError(f"cannot cast unit {unit}") + return m, p + cdef inline int64_t get_datetime64_nanos(object val) except? -1: """ @@ -155,6 +225,9 @@ def ensure_timedelta64ns(arr: ndarray, copy: bool=True): # TODO: check for overflows when going from a lower-resolution to nanos +# ---------------------------------------------------------------------- + + @cython.boundscheck(False) @cython.wraparound(False) def datetime_to_datetime64(ndarray[object] values): diff --git a/pandas/_libs/tslibs/timedeltas.pxd b/pandas/_libs/tslibs/timedeltas.pxd index d7af7636df753..8f9c1b190b021 100644 --- a/pandas/_libs/tslibs/timedeltas.pxd +++ b/pandas/_libs/tslibs/timedeltas.pxd @@ -1,6 +1,5 @@ from numpy cimport int64_t # Exposed for tslib, not intended for outside use. -cdef int64_t cast_from_unit(object ts, str unit) except? -1 cpdef int64_t delta_to_nanoseconds(delta) except? -1 cdef convert_to_timedelta64(object ts, object unit) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index c336e5f990f9a..651e79ebb737e 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -23,7 +23,7 @@ from pandas._libs.tslibs.util cimport ( from pandas._libs.tslibs.base cimport ABCTimedelta, ABCTimestamp -from pandas._libs.tslibs.ccalendar cimport DAY_NANOS +from pandas._libs.tslibs.conversion cimport cast_from_unit from pandas._libs.tslibs.np_datetime cimport ( cmp_scalar, td64_to_tdstruct, pandas_timedeltastruct) @@ -260,77 +260,6 @@ def array_to_timedelta64(object[:] values, unit='ns', errors='raise'): return iresult.base # .base to access underlying np.ndarray -cpdef inline object precision_from_unit(str unit): - """ - Return a casting of the unit represented to nanoseconds + the precision - to round the fractional part. - - Notes - ----- - The caller is responsible for ensuring that the default value of "ns" - takes the place of None. - """ - cdef: - int64_t m - int p - - if unit == 'Y': - m = 1000000000 * 31556952 - p = 9 - elif unit == 'M': - m = 1000000000 * 2629746 - p = 9 - elif unit == 'W': - m = DAY_NANOS * 7 - p = 9 - elif unit == 'D' or unit == 'd': - m = DAY_NANOS - p = 9 - elif unit == 'h': - m = 1000000000 * 3600 - p = 9 - elif unit == 'm': - m = 1000000000 * 60 - p = 9 - elif unit == 's': - m = 1000000000 - p = 9 - elif unit == 'ms': - m = 1000000 - p = 6 - elif unit == 'us': - m = 1000 - p = 3 - elif unit == 'ns' or unit is None: - m = 1 - p = 0 - else: - raise ValueError(f"cannot cast unit {unit}") - return m, p - - -cdef inline int64_t cast_from_unit(object ts, str unit) except? -1: - """ return a casting of the unit represented to nanoseconds - round the fractional part of a float to our precision, p """ - cdef: - int64_t m - int p - - m, p = precision_from_unit(unit) - - # just give me the unit back - if ts is None: - return m - - # cast the unit, multiply base/frace separately - # to avoid precision issues from float -> int - base = ts - frac = ts - base - if p: - frac = round(frac, p) - return (base * m) + (frac * m) - - cdef inline int64_t parse_timedelta_string(str ts) except? -1: """ Parse a regular format timedelta string. Return an int64_t (in ns) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index bc215eec4c345..4de105e8be364 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -5,12 +5,9 @@ from pandas._libs import lib, tslibs from pandas._libs.tslibs import NaT, Period, Timedelta, Timestamp, iNaT +from pandas._libs.tslibs.conversion import precision_from_unit from pandas._libs.tslibs.fields import get_timedelta_field -from pandas._libs.tslibs.timedeltas import ( - array_to_timedelta64, - parse_timedelta_unit, - precision_from_unit, -) +from pandas._libs.tslibs.timedeltas import array_to_timedelta64, parse_timedelta_unit from pandas.compat.numpy import function as nv from pandas.core.dtypes.common import (