diff --git a/pandas/_libs/tslib.pyi b/pandas/_libs/tslib.pyi index 4b02235ac9925..2212f8db8ea1e 100644 --- a/pandas/_libs/tslib.pyi +++ b/pandas/_libs/tslib.pyi @@ -9,6 +9,7 @@ def format_array_from_datetime( tz: tzinfo | None = ..., format: str | None = ..., na_rep: object = ..., + reso: int = ..., # NPY_DATETIMEUNIT ) -> npt.NDArray[np.object_]: ... def array_with_unit_to_datetime( values: np.ndarray, diff --git a/pandas/_libs/tslib.pyx b/pandas/_libs/tslib.pyx index e6bbf52ab1272..f94314297dc62 100644 --- a/pandas/_libs/tslib.pyx +++ b/pandas/_libs/tslib.pyx @@ -28,11 +28,12 @@ import pytz from pandas._libs.tslibs.np_datetime cimport ( NPY_DATETIMEUNIT, + NPY_FR_ns, check_dts_bounds, - dt64_to_dtstruct, dtstruct_to_dt64, get_datetime64_value, npy_datetimestruct, + pandas_datetime_to_datetimestruct, pydate_to_dt64, pydatetime_to_dt64, string_to_dts, @@ -107,7 +108,8 @@ def format_array_from_datetime( ndarray[int64_t] values, tzinfo tz=None, str format=None, - object na_rep=None + object na_rep=None, + NPY_DATETIMEUNIT reso=NPY_FR_ns, ) -> np.ndarray: """ return a np object array of the string formatted values @@ -120,6 +122,7 @@ def format_array_from_datetime( a strftime capable string na_rep : optional, default is None a nat format + reso : NPY_DATETIMEUNIT, default NPY_FR_ns Returns ------- @@ -141,7 +144,7 @@ def format_array_from_datetime( # a format based on precision basic_format = format is None and tz is None if basic_format: - reso_obj = get_resolution(values) + reso_obj = get_resolution(values, reso=reso) show_ns = reso_obj == Resolution.RESO_NS show_us = reso_obj == Resolution.RESO_US show_ms = reso_obj == Resolution.RESO_MS @@ -153,7 +156,7 @@ def format_array_from_datetime( result[i] = na_rep elif basic_format: - dt64_to_dtstruct(val, &dts) + pandas_datetime_to_datetimestruct(val, reso, &dts) res = (f'{dts.year}-{dts.month:02d}-{dts.day:02d} ' f'{dts.hour:02d}:{dts.min:02d}:{dts.sec:02d}') @@ -169,7 +172,7 @@ def format_array_from_datetime( else: - ts = Timestamp(val, tz=tz) + ts = Timestamp._from_value_and_reso(val, reso=reso, tz=tz) if format is None: result[i] = str(ts) else: diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 18133d7cf25ea..6ecb89b02afe3 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -696,7 +696,7 @@ def _format_native_types( fmt = get_format_datetime64_from_values(self, date_format) return tslib.format_array_from_datetime( - self.asi8, tz=self.tz, format=fmt, na_rep=na_rep + self.asi8, tz=self.tz, format=fmt, na_rep=na_rep, reso=self._reso ) # ----------------------------------------------------------------- diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index 045e74c1b6083..cf5e35f6ddcd1 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -42,7 +42,9 @@ NaT, Timedelta, Timestamp, + get_unit_from_dtype, iNaT, + periods_per_day, ) from pandas._libs.tslibs.nattype import NaTType from pandas._typing import ( @@ -1738,16 +1740,21 @@ def is_dates_only(values: np.ndarray | DatetimeArray | Index | DatetimeIndex) -> if not isinstance(values, Index): values = values.ravel() - values = DatetimeIndex(values) + if not isinstance(values, (DatetimeArray, DatetimeIndex)): + values = DatetimeIndex(values) + if values.tz is not None: return False values_int = values.asi8 consider_values = values_int != iNaT - one_day_nanos = 86400 * 10**9 - even_days = ( - np.logical_and(consider_values, values_int % int(one_day_nanos) != 0).sum() == 0 - ) + # error: Argument 1 to "py_get_unit_from_dtype" has incompatible type + # "Union[dtype[Any], ExtensionDtype]"; expected "dtype[Any]" + reso = get_unit_from_dtype(values.dtype) # type: ignore[arg-type] + ppd = periods_per_day(reso) + + # TODO: can we reuse is_date_array_normalized? would need a skipna kwd + even_days = np.logical_and(consider_values, values_int % ppd != 0).sum() == 0 if even_days: return True return False diff --git a/pandas/tests/arrays/test_datetimes.py b/pandas/tests/arrays/test_datetimes.py index d82e865b069aa..6c6a8b269aee8 100644 --- a/pandas/tests/arrays/test_datetimes.py +++ b/pandas/tests/arrays/test_datetimes.py @@ -155,6 +155,20 @@ def test_time_date(self, dta_dti, meth): expected = getattr(dti, meth) tm.assert_numpy_array_equal(result, expected) + def test_format_native_types(self, unit, reso, dtype, dta_dti): + # In this case we should get the same formatted values with our nano + # version dti._data as we do with the non-nano dta + dta, dti = dta_dti + + res = dta._format_native_types() + exp = dti._data._format_native_types() + tm.assert_numpy_array_equal(res, exp) + + def test_repr(self, dta_dti, unit): + dta, dti = dta_dti + + assert repr(dta) == repr(dti._data).replace("[ns", f"[{unit}") + class TestDatetimeArrayComparisons: # TODO: merge this into tests/arithmetic/test_datetime64 once it is diff --git a/setup.py b/setup.py index 27e6d8cb10025..70adbd3c083af 100755 --- a/setup.py +++ b/setup.py @@ -492,7 +492,11 @@ def srcpath(name=None, suffix=".pyx", subdir="src"): "_libs.properties": {"pyxfile": "_libs/properties"}, "_libs.reshape": {"pyxfile": "_libs/reshape", "depends": []}, "_libs.sparse": {"pyxfile": "_libs/sparse", "depends": _pxi_dep["sparse"]}, - "_libs.tslib": {"pyxfile": "_libs/tslib", "depends": tseries_depends}, + "_libs.tslib": { + "pyxfile": "_libs/tslib", + "depends": tseries_depends, + "sources": ["pandas/_libs/tslibs/src/datetime/np_datetime.c"], + }, "_libs.tslibs.base": {"pyxfile": "_libs/tslibs/base"}, "_libs.tslibs.ccalendar": {"pyxfile": "_libs/tslibs/ccalendar"}, "_libs.tslibs.dtypes": {"pyxfile": "_libs/tslibs/dtypes"},