diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index f54f859bd43ff..fbc4a28a0153c 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -453,6 +453,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ - Bug in :class:`Timestamp` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``tzinfo`` or data (:issue:`48688`) +- Bug in :class:`Timestamp` constructor ignoring the unit multiplier of a ``datetime64`` (:issue:`39977`) - Bug in :func:`date_range` where the last valid timestamp would sometimes not be produced (:issue:`56134`) - Bug in :func:`date_range` where using a negative frequency value would not include all points between the start and end values (:issue:`56382`) - Bug in :func:`tseries.api.guess_datetime_format` would fail to infer time format when "%Y" == "%H%M" (:issue:`57452`) diff --git a/pandas/_libs/tslibs/conversion.pyx b/pandas/_libs/tslibs/conversion.pyx index 3a55f5fa0c003..a8e6108064569 100644 --- a/pandas/_libs/tslibs/conversion.pyx +++ b/pandas/_libs/tslibs/conversion.pyx @@ -45,6 +45,7 @@ from pandas._libs.tslibs.np_datetime cimport ( dts_to_iso_string, get_conversion_factor, get_datetime64_unit, + get_datetime64_unit_num, get_implementation_bounds, import_pandas_datetime, npy_datetime, @@ -287,9 +288,10 @@ cdef int64_t get_datetime64_nanos(object val, NPY_DATETIMEUNIT reso) except? -1: return NPY_NAT unit = get_datetime64_unit(val) + nums = get_datetime64_unit_num(val) - if unit != reso: - pandas_datetime_to_datetimestruct(ival, unit, &dts) + if unit != reso or nums != 1: + pandas_datetime_to_datetimestruct(ival * nums, unit, &dts) try: ival = npy_datetimestruct_to_datetime(reso, &dts) except OverflowError as err: diff --git a/pandas/_libs/tslibs/np_datetime.pxd b/pandas/_libs/tslibs/np_datetime.pxd index cb2658d343772..92f73f2d47f44 100644 --- a/pandas/_libs/tslibs/np_datetime.pxd +++ b/pandas/_libs/tslibs/np_datetime.pxd @@ -81,6 +81,7 @@ cdef int64_t pydate_to_dt64( cdef void pydate_to_dtstruct(date val, npy_datetimestruct *dts) noexcept cdef NPY_DATETIMEUNIT get_datetime64_unit(object obj) noexcept nogil +cdef int get_datetime64_unit_num(object obj) noexcept nogil cdef int string_to_dts( str val, diff --git a/pandas/_libs/tslibs/np_datetime.pyx b/pandas/_libs/tslibs/np_datetime.pyx index 61095b3f034fd..fd65474e62c52 100644 --- a/pandas/_libs/tslibs/np_datetime.pyx +++ b/pandas/_libs/tslibs/np_datetime.pyx @@ -78,6 +78,13 @@ cdef NPY_DATETIMEUNIT get_datetime64_unit(object obj) noexcept nogil: return (obj).obmeta.base +cdef int get_datetime64_unit_num(object obj) noexcept nogil: + """ + returns the number of units of the dtype for a numpy datetime64 object. + """ + return (obj).obmeta.num + + cdef NPY_DATETIMEUNIT get_unit_from_dtype(cnp.dtype dtype): # NB: caller is responsible for ensuring this is *some* datetime64 or # timedelta64 dtype, otherwise we can segfault diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 79fd285073983..31b424a7aaf71 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -633,6 +633,12 @@ def test_comparison(self, dt64, ts): assert ts <= alt assert alt <= ts + def test_datetime64_unit_count(self): + # GH 39977 + ts = Timestamp(np.datetime64("2000-01-01", "10ns")) + + assert ts == Timestamp("2000-01-01") + def test_cmp_cross_reso(self): # numpy gets this wrong because of silent overflow dt64 = np.datetime64(9223372800, "s") # won't fit in M8[ns]