From aaefe4f9f922b4280517e19c5bc9e9e97cb7f093 Mon Sep 17 00:00:00 2001 From: somaz Date: Wed, 4 Nov 2026 16:35:07 +0900 Subject: [PATCH 1/3] Fix timezone-aware DatetimeIndex converting to an object array --- _plotly_utils/basevalidators.py | 6 ++++++ .../validators/test_pandas_series_input.py | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/_plotly_utils/basevalidators.py b/_plotly_utils/basevalidators.py index 1ffc63e5add..811cef8c5e5 100644 --- a/_plotly_utils/basevalidators.py +++ b/_plotly_utils/basevalidators.py @@ -105,6 +105,12 @@ def copy_to_readonly_numpy_array(v, kind=None, force_numeric=False): "O": "object", } + pd = get_module("pandas", should_load=False) + if pd is not None and isinstance(v, pd.DatetimeIndex) and v.tz is not None: + # Remove time zone so that local time is displayed, and so that converting + # to numpy doesn't result in object dtype + v = v.tz_localize(None) + # With `pass_through=True`, the original object will be returned if unable to convert # to a Narwhals DataFrame or Series. v = nw.from_native(v, allow_series=True, pass_through=True) diff --git a/tests/test_plotly_utils/validators/test_pandas_series_input.py b/tests/test_plotly_utils/validators/test_pandas_series_input.py index 04e41a722d2..57060251178 100644 --- a/tests/test_plotly_utils/validators/test_pandas_series_input.py +++ b/tests/test_plotly_utils/validators/test_pandas_series_input.py @@ -186,6 +186,15 @@ def test_data_array_validator_dates_series( np.testing.assert_array_equal(res, dates_array) +def test_data_array_validator_tz_aware_datetime_index(data_array_validator): + index = pd.date_range("2024-03-30", periods=3, freq="D", tz="Europe/Berlin") + res = data_array_validator.validate_coerce(index) + + assert isinstance(res, np.ndarray) + assert res.dtype.kind == "M" + np.testing.assert_array_equal(res, index.tz_localize(None).to_numpy()) + + def test_data_array_validator_dates_dataframe( data_array_validator, datetime_pandas, dates_array ): From 727807b6a077ff9137d12ede0ec8e62001db4171 Mon Sep 17 00:00:00 2001 From: somaz Date: Wed, 4 Nov 2026 16:40:16 +0900 Subject: [PATCH 2/3] Add changelog entry for timezone-aware DatetimeIndex fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8378fcf3477..a650665c87b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution! - Fix `plotly.io.write_images` ignoring `layout.width`/`layout.height` (and the template's) and exporting every figure at the default 700x500 size; it now uses the same width/height priority as `write_image` and `to_image` [[#5784](https://github.com/plotly/plotly.py/pull/5784)], with thanks to @breken-ai for the contribution! - Fix color utilities mutating the list of colors passed to them [[#5836](https://github.com/plotly/plotly.py/pull/5836)], with thanks to @Enesuygurs for the contribution! +- Fix timezone-aware `DatetimeIndex` values being converted to an object array, which made figure creation and `to_json()` much slower than for a timezone-aware Series; their JSON output now matches the Series output, without the UTC offset [[#5870](https://github.com/plotly/plotly.py/pull/5870)], with thanks to @somaz94 for the contribution! ## [7.1.0] - 2026-09-15 From 63c1771b619497a37df358dbf04837fef96135b6 Mon Sep 17 00:00:00 2001 From: somaz Date: Thu, 5 Nov 2026 11:38:52 +0900 Subject: [PATCH 3/3] Send a pandas Index down the existing Series path --- _plotly_utils/basevalidators.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/_plotly_utils/basevalidators.py b/_plotly_utils/basevalidators.py index 811cef8c5e5..6b26d0653ec 100644 --- a/_plotly_utils/basevalidators.py +++ b/_plotly_utils/basevalidators.py @@ -106,10 +106,9 @@ def copy_to_readonly_numpy_array(v, kind=None, force_numeric=False): } pd = get_module("pandas", should_load=False) - if pd is not None and isinstance(v, pd.DatetimeIndex) and v.tz is not None: - # Remove time zone so that local time is displayed, and so that converting - # to numpy doesn't result in object dtype - v = v.tz_localize(None) + if pd is not None and isinstance(v, pd.Index): + # Narwhals passes an Index through unchanged, so use the Series path for it + v = v.to_series() # With `pass_through=True`, the original object will be returned if unable to convert # to a Narwhals DataFrame or Series.