diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 8cc7bcb2a1aad..833ba4ce70bd7 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -6,6 +6,7 @@ from pandas._libs.tslibs.np_datetime cimport NPY_DATETIMEUNIT cdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit) cdef NPY_DATETIMEUNIT freq_group_code_to_npy_unit(int freq) nogil cdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=*) except? -1 +cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1 cdef dict attrname_to_abbrevs diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index 0c4d4c5c235b5..3be21ba754f27 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -348,6 +348,19 @@ cdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=NPY_DATETIMEUNIT.NPY_FR_ns) e return day_units +cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1: + if reso == NPY_DATETIMEUNIT.NPY_FR_ns: + return 1_000_000_000 + elif reso == NPY_DATETIMEUNIT.NPY_FR_us: + return 1_000_000 + elif reso == NPY_DATETIMEUNIT.NPY_FR_ms: + return 1_000 + elif reso == NPY_DATETIMEUNIT.NPY_FR_s: + return 1 + else: + raise NotImplementedError(reso) + + cdef dict _reso_str_map = { Resolution.RESO_NS.value: "nanosecond", Resolution.RESO_US.value: "microsecond", diff --git a/pandas/_libs/tslibs/timestamps.pyx b/pandas/_libs/tslibs/timestamps.pyx index 923d1f830e1a9..fcc9390a2cccd 100644 --- a/pandas/_libs/tslibs/timestamps.pyx +++ b/pandas/_libs/tslibs/timestamps.pyx @@ -52,7 +52,11 @@ from pandas._libs.tslibs.conversion cimport ( convert_datetime_to_tsobject, convert_to_tsobject, ) -from pandas._libs.tslibs.dtypes cimport npy_unit_to_abbrev +from pandas._libs.tslibs.dtypes cimport ( + npy_unit_to_abbrev, + periods_per_day, + periods_per_second, +) from pandas._libs.tslibs.util cimport ( is_array, is_datetime64_object, @@ -811,11 +815,12 @@ cdef class _Timestamp(ABCTimestamp): cdef: local_val = self._maybe_convert_value_to_local() int64_t normalized + int64_t ppd = periods_per_day(self._reso) if self._reso != NPY_FR_ns: raise NotImplementedError(self._reso) - normalized = normalize_i8_stamp(local_val) + normalized = normalize_i8_stamp(local_val, ppd) return Timestamp(normalized).tz_localize(self.tzinfo) # ----------------------------------------------------------------- @@ -834,8 +839,8 @@ cdef class _Timestamp(ABCTimestamp): if len(state) == 3: # pre-non-nano pickle + # TODO: no tests get here 2022-05-10 reso = NPY_FR_ns - assert False # checking for coverage else: reso = state[4] self._reso = reso @@ -982,10 +987,10 @@ cdef class _Timestamp(ABCTimestamp): """ # GH 17329 # Note: Naive timestamps will not match datetime.stdlib - if self._reso != NPY_FR_ns: - raise NotImplementedError(self._reso) - return round(self.value / 1e9, 6) + denom = periods_per_second(self._reso) + + return round(self.value / denom, 6) cpdef datetime to_pydatetime(_Timestamp self, bint warn=True): """ @@ -1080,9 +1085,6 @@ cdef class _Timestamp(ABCTimestamp): """ from pandas import Period - if self._reso != NPY_FR_ns: - raise NotImplementedError(self._reso) - if self.tz is not None: # GH#21333 warnings.warn( @@ -2252,16 +2254,18 @@ Timestamp.resolution = Timedelta(nanoseconds=1) # GH#21336, GH#21365 @cython.cdivision(False) -cdef inline int64_t normalize_i8_stamp(int64_t local_val) nogil: +cdef inline int64_t normalize_i8_stamp(int64_t local_val, int64_t ppd) nogil: """ Round the localized nanosecond timestamp down to the previous midnight. Parameters ---------- local_val : int64_t + ppd : int64_t + Periods per day in the Timestamp's resolution. Returns ------- int64_t """ - return local_val - (local_val % ccalendar.DAY_NANOS) + return local_val - (local_val % ppd) diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index c892816629462..108d58bcc251d 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -840,3 +840,11 @@ def test_to_datetime64(self, dt64, ts): res = ts.to_datetime64() assert res == dt64 assert res.dtype == dt64.dtype + + def test_timestamp(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.timestamp() == alt.timestamp() + + def test_to_period(self, dt64, ts): + alt = Timestamp(dt64) + assert ts.to_period("D") == alt.to_period("D")