Skip to content

Commit d943c26

Browse files
authored
REF: formats.format functions that should be DTA methods (#55394)
* REF: move get_format_datetime64_from_values to DatetimeArray * REF: make is_dates_only a DatetimeArray method * mypy fixup * suggested simplification * redundant check
1 parent a5c7946 commit d943c26

File tree

4 files changed

+38
-59
lines changed

4 files changed

+38
-59
lines changed

pandas/core/arrays/datetimes.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@
2828
get_resolution,
2929
get_supported_reso,
3030
get_unit_from_dtype,
31+
iNaT,
3132
ints_to_pydatetime,
3233
is_date_array_normalized,
3334
is_supported_unit,
3435
is_unitless,
3536
normalize_i8_timestamps,
3637
npy_unit_to_abbrev,
38+
periods_per_day,
3739
timezones,
3840
to_offset,
3941
tz_convert_from_utc,
@@ -735,14 +737,33 @@ def astype(self, dtype, copy: bool = True):
735737
def _format_native_types(
736738
self, *, na_rep: str | float = "NaT", date_format=None, **kwargs
737739
) -> npt.NDArray[np.object_]:
738-
from pandas.io.formats.format import get_format_datetime64_from_values
739-
740-
fmt = get_format_datetime64_from_values(self, date_format)
740+
if date_format is None and self._is_dates_only:
741+
# Only dates and no timezone: provide a default format
742+
date_format = "%Y-%m-%d"
741743

742744
return tslib.format_array_from_datetime(
743-
self.asi8, tz=self.tz, format=fmt, na_rep=na_rep, reso=self._creso
745+
self.asi8, tz=self.tz, format=date_format, na_rep=na_rep, reso=self._creso
744746
)
745747

748+
@property
749+
def _is_dates_only(self) -> bool:
750+
"""
751+
Check if we are round times at midnight (and no timezone), which will
752+
be given a more compact __repr__ than other cases.
753+
"""
754+
if self.tz is not None:
755+
return False
756+
757+
values_int = self.asi8
758+
consider_values = values_int != iNaT
759+
dtype = cast(np.dtype, self.dtype) # since we checked tz above
760+
reso = get_unit_from_dtype(dtype)
761+
ppd = periods_per_day(reso)
762+
763+
# TODO: can we reuse is_date_array_normalized? would need a skipna kwd
764+
even_days = np.logical_and(consider_values, values_int % ppd != 0).sum() == 0
765+
return even_days
766+
746767
# -----------------------------------------------------------------
747768
# Comparison Methods
748769

pandas/core/indexes/datetimes.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ def _engine_type(self) -> type[libindex.DatetimeEngine]:
266266
return libindex.DatetimeEngine
267267

268268
_data: DatetimeArray
269+
_values: DatetimeArray
269270
tz: dt.tzinfo | None
270271

271272
# --------------------------------------------------------------------
@@ -393,19 +394,12 @@ def _is_dates_only(self) -> bool:
393394
-------
394395
bool
395396
"""
396-
397-
from pandas.io.formats.format import is_dates_only
398-
399397
delta = getattr(self.freq, "delta", None)
400398

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

404-
# error: Argument 1 to "is_dates_only" has incompatible type
405-
# "Union[ExtensionArray, ndarray]"; expected "Union[ndarray,
406-
# DatetimeArray, Index, DatetimeIndex]"
407-
408-
return self.tz is None and is_dates_only(self._values) # type: ignore[arg-type]
402+
return self._values._is_dates_only
409403

410404
def __reduce__(self):
411405
d = {"data": self._data, "name": self.name}
@@ -428,7 +422,7 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
428422
def _formatter_func(self):
429423
from pandas.io.formats.format import get_format_datetime64
430424

431-
formatter = get_format_datetime64(is_dates_only_=self._is_dates_only)
425+
formatter = get_format_datetime64(is_dates_only=self._is_dates_only)
432426
return lambda x: f"'{formatter(x)}'"
433427

434428
# --------------------------------------------------------------------

pandas/io/formats/format.py

+3-43
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@
4242
NaT,
4343
Timedelta,
4444
Timestamp,
45-
get_unit_from_dtype,
4645
iNaT,
47-
periods_per_day,
4846
)
4947
from pandas._libs.tslibs.nattype import NaTType
5048

@@ -1691,31 +1689,6 @@ def format_percentiles(
16911689
return [i + "%" for i in out]
16921690

16931691

1694-
def is_dates_only(values: np.ndarray | DatetimeArray | Index | DatetimeIndex) -> bool:
1695-
# return a boolean if we are only dates (and don't have a timezone)
1696-
if not isinstance(values, Index):
1697-
values = values.ravel()
1698-
1699-
if not isinstance(values, (DatetimeArray, DatetimeIndex)):
1700-
values = DatetimeIndex(values)
1701-
1702-
if values.tz is not None:
1703-
return False
1704-
1705-
values_int = values.asi8
1706-
consider_values = values_int != iNaT
1707-
# error: Argument 1 to "py_get_unit_from_dtype" has incompatible type
1708-
# "Union[dtype[Any], ExtensionDtype]"; expected "dtype[Any]"
1709-
reso = get_unit_from_dtype(values.dtype) # type: ignore[arg-type]
1710-
ppd = periods_per_day(reso)
1711-
1712-
# TODO: can we reuse is_date_array_normalized? would need a skipna kwd
1713-
even_days = np.logical_and(consider_values, values_int % ppd != 0).sum() == 0
1714-
if even_days:
1715-
return True
1716-
return False
1717-
1718-
17191692
def _format_datetime64(x: NaTType | Timestamp, nat_rep: str = "NaT") -> str:
17201693
if x is NaT:
17211694
return nat_rep
@@ -1741,38 +1714,25 @@ def _format_datetime64_dateonly(
17411714

17421715

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

1749-
if is_dates_only_:
1722+
if is_dates_only:
17501723
return lambda x: _format_datetime64_dateonly(
17511724
x, nat_rep=nat_rep, date_format=date_format
17521725
)
17531726
else:
17541727
return lambda x: _format_datetime64(x, nat_rep=nat_rep)
17551728

17561729

1757-
def get_format_datetime64_from_values(
1758-
values: DatetimeArray, date_format: str | None
1759-
) -> str | None:
1760-
"""given values and a date_format, return a string format"""
1761-
assert isinstance(values, DatetimeArray)
1762-
1763-
ido = is_dates_only(values)
1764-
if ido:
1765-
# Only dates and no timezone: provide a default format
1766-
return date_format or "%Y-%m-%d"
1767-
return date_format
1768-
1769-
17701730
class Datetime64TZFormatter(Datetime64Formatter):
17711731
values: DatetimeArray
17721732

17731733
def _format_strings(self) -> list[str]:
17741734
"""we by definition have a TZ"""
1775-
ido = is_dates_only(self.values)
1735+
ido = self.values._is_dates_only
17761736
values = self.values.astype(object)
17771737
formatter = self.formatter or get_format_datetime64(
17781738
ido, date_format=self.date_format

pandas/tests/io/formats/test_format.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -3308,9 +3308,13 @@ def format_func(x):
33083308
assert result == ["10:10", "12:12"]
33093309

33103310
def test_datetime64formatter_tz_ms(self):
3311-
x = Series(
3312-
np.array(["2999-01-01", "2999-01-02", "NaT"], dtype="datetime64[ms]")
3313-
).dt.tz_localize("US/Pacific")
3311+
x = (
3312+
Series(
3313+
np.array(["2999-01-01", "2999-01-02", "NaT"], dtype="datetime64[ms]")
3314+
)
3315+
.dt.tz_localize("US/Pacific")
3316+
._values
3317+
)
33143318
result = fmt.Datetime64TZFormatter(x).get_result()
33153319
assert result[0].strip() == "2999-01-01 00:00:00-08:00"
33163320
assert result[1].strip() == "2999-01-02 00:00:00-08:00"

0 commit comments

Comments
 (0)