diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py index ea938c924ae0c..2cf4930ebc801 100644 --- a/pandas/io/formats/format.py +++ b/pandas/io/formats/format.py @@ -1621,18 +1621,23 @@ def __init__( def _format_strings(self) -> list[str]: """we by definition have DO NOT have a TZ""" - values = self.values + values = self.values if not isinstance(values, DatetimeIndex): values = DatetimeIndex(values) if self.formatter is not None and callable(self.formatter): - return [self.formatter(x) for x in values] + fmt_values = np.array([self.formatter(x) for x in values]) + else: + fmt_values = values._data._format_native_types( + na_rep=self.nat_rep, date_format=self.date_format + ) - fmt_values = values._data._format_native_types( - na_rep=self.nat_rep, date_format=self.date_format - ) - return fmt_values.tolist() + if fmt_values.ndim > 1: + nested_formatter = GenericArrayFormatter(fmt_values) + return list(nested_formatter.get_result()) + + return list(fmt_values) class ExtensionArrayFormatter(GenericArrayFormatter): @@ -1811,9 +1816,12 @@ def _format_strings(self) -> list[str]: formatter = self.formatter or get_format_datetime64( ido, date_format=self.date_format ) - fmt_values = [formatter(x) for x in values] + fmt_values = np.frompyfunc(formatter, 1, 1)(values) + if fmt_values.ndim > 1: + nested_formatter = GenericArrayFormatter(fmt_values) + return list(nested_formatter.get_result()) - return fmt_values + return list(fmt_values) class Timedelta64Formatter(GenericArrayFormatter): diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index adcaeba5cfd8d..71531ccf4e02a 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -3158,6 +3158,97 @@ def format_func(x): result = formatter.get_result() assert result == ["10:10", "12:12"] + def test_datetime64formatter_2d_array(self): + # GH#38390 + x = date_range("2018-01-01", periods=10, freq="H").to_numpy() + + formatter = fmt.Datetime64Formatter(x.reshape((5, 2))) + result = formatter.get_result() + assert len(result) == 5 + assert result[0].strip() == "[2018-01-01 00:00:00, 2018-01-01 01:00:00]" + assert result[4].strip() == "[2018-01-01 08:00:00, 2018-01-01 09:00:00]" + + formatter = fmt.Datetime64Formatter(x.reshape((2, 5))) + result = formatter.get_result() + assert len(result) == 2 + assert result[0].strip() == "[2018-01-01 00:00:00, 2018-01-01 01:00:00, 201..." + assert result[1].strip() == "[2018-01-01 05:00:00, 2018-01-01 06:00:00, 201..." + + def test_datetime64formatter_3d_array(self): + # GH#38390 + x = date_range("2018-01-01", periods=10, freq="H").to_numpy() + + formatter = fmt.Datetime64Formatter(x.reshape((10, 1, 1))) + result = formatter.get_result() + assert len(result) == 10 + assert result[0].strip() == "[[2018-01-01 00:00:00]]" + assert result[9].strip() == "[[2018-01-01 09:00:00]]" + + def test_datetime64formatter_3d_array_format_func(self): + # GH#38390 + x = date_range("2018-01-01", periods=24, freq="H").to_numpy() + + def format_func(t): + return t.strftime("%H-%m") + + formatter = fmt.Datetime64Formatter(x.reshape((4, 2, 3)), formatter=format_func) + result = formatter.get_result() + assert len(result) == 4 + assert result[0].strip() == "[[00-01, 01-01, 02-01], [03-01, 04-01, 05-01]]" + assert result[3].strip() == "[[18-01, 19-01, 20-01], [21-01, 22-01, 23-01]]" + + +class TestDatetime64TZFormatter: + def test_mixed(self): + # GH#38390 + utc = dateutil.tz.tzutc() + x = Series( + [ + datetime(2013, 1, 1, tzinfo=utc), + datetime(2013, 1, 1, 12, tzinfo=utc), + NaT, + ] + ) + result = fmt.Datetime64TZFormatter(x).get_result() + assert len(result) == 3 + assert result[0].strip() == "2013-01-01 00:00:00+00:00" + assert result[1].strip() == "2013-01-01 12:00:00+00:00" + assert result[2].strip() == "NaT" + + def test_datetime64formatter_1d_array(self): + # GH#38390 + x = date_range("2018-01-01", periods=3, freq="H", tz="US/Pacific").to_numpy() + formatter = fmt.Datetime64TZFormatter(x) + result = formatter.get_result() + assert len(result) == 3 + assert result[0].strip() == "2018-01-01 00:00:00-08:00" + assert result[1].strip() == "2018-01-01 01:00:00-08:00" + assert result[2].strip() == "2018-01-01 02:00:00-08:00" + + def test_datetime64formatter_2d_array(self): + # GH#38390 + x = date_range("2018-01-01", periods=10, freq="H", tz="US/Pacific").to_numpy() + formatter = fmt.Datetime64TZFormatter(x.reshape((5, 2))) + result = formatter.get_result() + assert len(result) == 5 + assert result[0].strip() == "[2018-01-01 00:00:00-08:00, 2018-01-01 01:00:0..." + assert result[4].strip() == "[2018-01-01 08:00:00-08:00, 2018-01-01 09:00:0..." + + def test_datetime64formatter_2d_array_format_func(self): + # GH#38390 + x = date_range("2018-01-01", periods=16, freq="H", tz="US/Pacific").to_numpy() + + def format_func(t): + return t.strftime("%H-%m %Z") + + formatter = fmt.Datetime64TZFormatter( + x.reshape((4, 2, 2)), formatter=format_func + ) + result = formatter.get_result() + assert len(result) == 4 + assert result[0].strip() == "[[00-01 PST, 01-01 PST], [02-01 PST, 03-01 PST]]" + assert result[3].strip() == "[[12-01 PST, 13-01 PST], [14-01 PST, 15-01 PST]]" + class TestNaTFormatting: def test_repr(self):