From 912275bc490e261ecc15b18a7a50a62c564d97da Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 14:26:54 +0200 Subject: [PATCH 1/6] infer the dtype + cast where possible --- xarray/core/variable.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 1ea86254d35..48c4dadc1c1 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -210,8 +210,9 @@ def _maybe_wrap_data(data): def _possibly_convert_objects(values): """Convert object arrays into datetime64 and timedelta64 according - to the pandas convention. For backwards compat, as of 3.0.0 pandas, - object dtype inputs are cast to strings by `pandas.Series` + to the pandas convention. Object dtype inputs that are inferred to be + strings are returned unchanged. For backwards compat, as of 3.0.0 pandas, + the remaining object dtype inputs are cast to strings by `pandas.Series` but we output them as object dtype with the input metadata preserved as well. @@ -220,8 +221,18 @@ def _possibly_convert_objects(values): * pd.Timestamp * pd.Timedelta """ - as_series = pd.Series(values.ravel(), copy=False) - result = np.asarray(as_series).reshape(values.shape) + inferred = pd.api.types.infer_dtype(values.ravel(), skipna=True) + + if inferred == "string": + return values + elif inferred == "datetime": + result = pd.to_datetime(values.ravel()).to_numpy().reshape(values.shape) + elif inferred == "timedelta": + result = pd.to_timedelta(values.ravel()).to_numpy().reshape(values.shape) + else: + as_series = pd.Series(values.ravel(), copy=False) + result = np.asarray(as_series).reshape(values.shape) + if not result.flags.writeable: # GH8843, pandas copy-on-write mode creates read-only arrays by default try: From e8d915a8f6b811973baa0abb17683a2ef55b8c40 Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 14:32:47 +0200 Subject: [PATCH 2/6] add more cases to the test_as_compatible_data_writeable --- xarray/tests/test_variable.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py index c2bc73f70b9..0ebde435697 100644 --- a/xarray/tests/test_variable.py +++ b/xarray/tests/test_variable.py @@ -70,6 +70,9 @@ def var(): [ np.array(["a", "bc", "def"], dtype=object), np.array(["2019-01-01", "2019-01-02", "2019-01-03"], dtype="datetime64[ns]"), + np.array([datetime(2000, 1, 1), datetime(2000, 1, 2)], dtype=object), + np.array([timedelta(seconds=1), timedelta(seconds=2)], dtype=object), + np.array([1, "a"], dtype=object), ], ) def test_as_compatible_data_writeable(data): From acd4a4cdd1d3a39eff285c4a787bb3ec1e51b2dd Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 16:29:25 +0200 Subject: [PATCH 3/6] make if elif else logic more explicit --- xarray/core/variable.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 48c4dadc1c1..2931d9947e4 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -229,9 +229,11 @@ def _possibly_convert_objects(values): result = pd.to_datetime(values.ravel()).to_numpy().reshape(values.shape) elif inferred == "timedelta": result = pd.to_timedelta(values.ravel()).to_numpy().reshape(values.shape) - else: + elif inferred in ["datetime64", "timedelta64"]: as_series = pd.Series(values.ravel(), copy=False) result = np.asarray(as_series).reshape(values.shape) + else: + result = values if not result.flags.writeable: # GH8843, pandas copy-on-write mode creates read-only arrays by default From fe7c09118e08e4af991e09ec83e508cbc09cca07 Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 16:32:10 +0200 Subject: [PATCH 4/6] return values directly in else --- xarray/core/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 2931d9947e4..8a0b659f343 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -233,7 +233,7 @@ def _possibly_convert_objects(values): as_series = pd.Series(values.ravel(), copy=False) result = np.asarray(as_series).reshape(values.shape) else: - result = values + return values if not result.flags.writeable: # GH8843, pandas copy-on-write mode creates read-only arrays by default From 9f176e58d0d482872637147d9de6cca0a4e18be6 Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 17:25:19 +0200 Subject: [PATCH 5/6] add comment on datetime64 & timedelta64 cases --- xarray/core/variable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 8a0b659f343..841f53974d3 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -230,6 +230,8 @@ def _possibly_convert_objects(values): elif inferred == "timedelta": result = pd.to_timedelta(values.ravel()).to_numpy().reshape(values.shape) elif inferred in ["datetime64", "timedelta64"]: + # Casting drops unit info for these cases; + # fall back to pd.Series which preserves them. as_series = pd.Series(values.ravel(), copy=False) result = np.asarray(as_series).reshape(values.shape) else: From 5514a0f79dbca1a10cdaac20ae0b3dc55175588e Mon Sep 17 00:00:00 2001 From: JJFlorian Date: Thu, 30 Jul 2026 17:28:33 +0200 Subject: [PATCH 6/6] add comment on datetime64 & timedelta64 cases --- xarray/core/variable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/variable.py b/xarray/core/variable.py index 841f53974d3..52088090468 100644 --- a/xarray/core/variable.py +++ b/xarray/core/variable.py @@ -231,7 +231,7 @@ def _possibly_convert_objects(values): result = pd.to_timedelta(values.ravel()).to_numpy().reshape(values.shape) elif inferred in ["datetime64", "timedelta64"]: # Casting drops unit info for these cases; - # fall back to pd.Series which preserves them. + # fall back to pd.Series roundtrip, which preserves them. as_series = pd.Series(values.ravel(), copy=False) result = np.asarray(as_series).reshape(values.shape) else: