Skip to content

Commit

Permalink
REF: make is_dates_only a DatetimeArray method
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed Oct 4, 2023
1 parent df65e97 commit 2833660
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 45 deletions.
25 changes: 22 additions & 3 deletions pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@
get_resolution,
get_supported_reso,
get_unit_from_dtype,
iNaT,
ints_to_pydatetime,
is_date_array_normalized,
is_supported_unit,
is_unitless,
normalize_i8_timestamps,
npy_unit_to_abbrev,
periods_per_day,
timezones,
to_offset,
tz_convert_from_utc,
Expand Down Expand Up @@ -735,10 +737,8 @@ def astype(self, dtype, copy: bool = True):
def _format_native_types(
self, *, na_rep: str | float = "NaT", date_format=None, **kwargs
) -> npt.NDArray[np.object_]:
from pandas.io.formats.format import is_dates_only

if date_format is None:
ido = is_dates_only(self)
ido = self._is_dates_only
if ido:
# Only dates and no timezone: provide a default format
date_format = "%Y-%m-%d"
Expand All @@ -747,6 +747,25 @@ def _format_native_types(
self.asi8, tz=self.tz, format=date_format, na_rep=na_rep, reso=self._creso
)

@property
def _is_dates_only(self) -> bool:
"""
Check if we are round times at midnight (and no timezone), which will
be given a more compact __repr__ than other cases.
"""
if self.tz is not None:
return False

values_int = self.asi8
consider_values = values_int != iNaT
dtype = cast(np.dtype, self.dtype) # since we checked tz above
reso = get_unit_from_dtype(dtype)
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
return even_days

# -----------------------------------------------------------------
# Comparison Methods

Expand Down
11 changes: 2 additions & 9 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,19 +393,12 @@ def _is_dates_only(self) -> bool:
-------
bool
"""

from pandas.io.formats.format import is_dates_only

delta = getattr(self.freq, "delta", None)

if delta and delta % dt.timedelta(days=1) != dt.timedelta(days=0):
return False

# error: Argument 1 to "is_dates_only" has incompatible type
# "Union[ExtensionArray, ndarray]"; expected "Union[ndarray,
# DatetimeArray, Index, DatetimeIndex]"

return self.tz is None and is_dates_only(self._values) # type: ignore[arg-type]
return self.tz is None and self._values._is_dates_only

def __reduce__(self):
d = {"data": self._data, "name": self.name}
Expand All @@ -428,7 +421,7 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
def _formatter_func(self):
from pandas.io.formats.format import get_format_datetime64

formatter = get_format_datetime64(is_dates_only_=self._is_dates_only)
formatter = get_format_datetime64(is_dates_only=self._is_dates_only)
return lambda x: f"'{formatter(x)}'"

# --------------------------------------------------------------------
Expand Down
33 changes: 3 additions & 30 deletions pandas/io/formats/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@
NaT,
Timedelta,
Timestamp,
get_unit_from_dtype,
iNaT,
periods_per_day,
)
from pandas._libs.tslibs.nattype import NaTType

Expand Down Expand Up @@ -1749,31 +1747,6 @@ def format_percentiles(
return [i + "%" for i in out]


def is_dates_only(values: np.ndarray | DatetimeArray | Index | DatetimeIndex) -> bool:
# return a boolean if we are only dates (and don't have a timezone)
if not isinstance(values, Index):
values = values.ravel()

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
# 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


def _format_datetime64(x: NaTType | Timestamp, nat_rep: str = "NaT") -> str:
if x is NaT:
return nat_rep
Expand All @@ -1799,12 +1772,12 @@ def _format_datetime64_dateonly(


def get_format_datetime64(
is_dates_only_: bool, nat_rep: str = "NaT", date_format: str | None = None
is_dates_only: bool, nat_rep: str = "NaT", date_format: str | None = None
) -> Callable:
"""Return a formatter callable taking a datetime64 as input and providing
a string as output"""

if is_dates_only_:
if is_dates_only:
return lambda x: _format_datetime64_dateonly(
x, nat_rep=nat_rep, date_format=date_format
)
Expand All @@ -1815,7 +1788,7 @@ def get_format_datetime64(
class Datetime64TZFormatter(Datetime64Formatter):
def _format_strings(self) -> list[str]:
"""we by definition have a TZ"""
ido = is_dates_only(self.values)
ido = self.values._is_dates_only
values = self.values.astype(object)
formatter = self.formatter or get_format_datetime64(
ido, date_format=self.date_format
Expand Down
10 changes: 7 additions & 3 deletions pandas/tests/io/formats/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -3308,9 +3308,13 @@ def format_func(x):
assert result == ["10:10", "12:12"]

def test_datetime64formatter_tz_ms(self):
x = Series(
np.array(["2999-01-01", "2999-01-02", "NaT"], dtype="datetime64[ms]")
).dt.tz_localize("US/Pacific")
x = (
Series(
np.array(["2999-01-01", "2999-01-02", "NaT"], dtype="datetime64[ms]")
)
.dt.tz_localize("US/Pacific")
._values
)
result = fmt.Datetime64TZFormatter(x).get_result()
assert result[0].strip() == "2999-01-01 00:00:00-08:00"
assert result[1].strip() == "2999-01-02 00:00:00-08:00"
Expand Down

0 comments on commit 2833660

Please sign in to comment.