From eae8c2e87ea528f4ef08889c7768ebc3d5144006 Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Fri, 9 Jan 2026 23:37:33 +0200 Subject: [PATCH 1/6] fix: support datetime variables in `Dataset.interp` Signed-off-by: Emmanuel Ferdman --- xarray/core/dataset.py | 17 ++++++++ xarray/tests/test_interp.py | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index c05aca05d9e..d5e942a0604 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -3940,6 +3940,23 @@ def _validate_interp_indexer(x, new_x): # For normal number types do the interpolation: var_indexers = {k: v for k, v in use_indexers.items() if k in var.dims} variables[name] = missing.interp(var, var_indexers, method, **kwargs) + elif dtype_kind in "Mm" and (use_indexers.keys() & var.dims): + # For datetime-like types, interpolate as float64: + var_indexers = {k: v for k, v in use_indexers.items() if k in var.dims} + int_data = var.data.view(np.int64) + nat = np.iinfo(np.int64).min + as_float = np.where( + int_data == nat, np.nan, int_data.astype(np.float64) + ) + result = missing.interp( + var.copy(data=as_float), var_indexers, method, **kwargs + ) + as_int = np.where( + np.isnan(result.data), + nat, + np.round(np.nan_to_num(result.data)).astype(np.int64), + ) + variables[name] = result.copy(data=as_int.view(var.dtype)) elif dtype_kind in "ObU" and (use_indexers.keys() & var.dims): if all(var.sizes[d] == 1 for d in (use_indexers.keys() & var.dims)): # Broadcastable, can be handled quickly without reindex: diff --git a/xarray/tests/test_interp.py b/xarray/tests/test_interp.py index b02df976d47..394bd60ff24 100644 --- a/xarray/tests/test_interp.py +++ b/xarray/tests/test_interp.py @@ -1189,3 +1189,87 @@ def test_interp_vectorized_shared_dims(chunk: bool) -> None: coords={"u": [45, 55], "t": [10, 12], "x": dx, "y": dy}, ) assert_identical(actual, expected) + + +@requires_scipy +def test_dataset_interp_datetime_variable() -> None: + # GH#10900 + ds = xr.Dataset( + data_vars={ + "something": (["x", "y"], np.arange(25, dtype=float).reshape(5, 5)), + "time": ( + ["x", "y"], + np.datetime64("2024-01-01") + + np.arange(25).reshape(5, 5) * np.timedelta64(1, "D"), + ), + }, + coords={"x": np.arange(5), "y": np.arange(5)}, + ) + + result = ds.interp(x=[0.5, 1.5], y=[0.5, 1.5]) + + assert "time" in result.data_vars + expected_time = np.datetime64("2024-01-01") + np.timedelta64(3, "D") + np.testing.assert_equal(result["time"].values[0, 0], expected_time) + + +@requires_scipy +def test_dataset_interp_timedelta_variable() -> None: + # GH#10900 + ds = xr.Dataset( + data_vars={ + "duration": (["x"], np.array([1, 2, 3, 4, 5], dtype="timedelta64[D]")), + }, + coords={"x": np.arange(5)}, + ) + + result = ds.interp(x=[0.5, 1.5, 2.5]) + + assert "duration" in result.data_vars + expected_seconds = np.array([1.5, 2.5, 3.5]) * 86400 + actual_seconds = result["duration"].values.astype("timedelta64[s]").astype(float) + np.testing.assert_allclose(actual_seconds, expected_seconds, rtol=1e-10) + + +@requires_scipy +def test_dataset_interp_datetime_nat() -> None: + # GH#10900 - NaT propagates like NaN + time_data = np.array( + ["2024-01-01", "2024-01-02", "NaT", "2024-01-04", "2024-01-05"], + dtype="datetime64[D]", + ) + ds = xr.Dataset( + data_vars={"time": (["x"], time_data)}, + coords={"x": np.arange(5)}, + ) + + result = ds.interp(x=[0.5, 1.5, 2.5, 3.5]) + + assert not np.isnat(result["time"].values[0]) + assert np.isnat(result["time"].values[1]) + assert np.isnat(result["time"].values[2]) + assert not np.isnat(result["time"].values[3]) + + +@requires_scipy +@requires_dask +def test_dataset_interp_datetime_dask() -> None: + # GH#10900 + ds = xr.Dataset( + data_vars={ + "something": (["x", "y"], np.arange(25, dtype=float).reshape(5, 5)), + "time": ( + ["x", "y"], + np.datetime64("2024-01-01") + + np.arange(25).reshape(5, 5) * np.timedelta64(1, "D"), + ), + }, + coords={"x": np.arange(5), "y": np.arange(5)}, + ).chunk({"x": 2, "y": 2}) + + result = ds.interp(x=[0.5, 1.5], y=[0.5, 1.5]) + + assert "time" in result.data_vars + computed = result.compute() + expected_time = np.datetime64("2024-01-01") + np.timedelta64(3, "D") + np.testing.assert_equal(computed["time"].values[0, 0], expected_time) From e4bf80a82ebeb36865bac45a43f21e37bcf7d5cc Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Fri, 9 Jan 2026 23:43:21 +0200 Subject: [PATCH 2/6] fix: support datetime variables in `Dataset.interp` Signed-off-by: Emmanuel Ferdman --- doc/whats-new.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 2eb085bdfc6..bcff698e8e9 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -144,6 +144,9 @@ Bug Fixes By `David Bold `_. - Improve error message when scipy is missing for :py:class:`~xarray.indexes.NDPointIndex` (:pull:`11085`). By `Sakshee_D `_. +- Fix :py:meth:`Dataset.interp` silently dropping datetime64 and timedelta64 + variables (:issue:`10900`, :pull:`11081`). + By `Emmanuel Ferdman `_. Documentation ~~~~~~~~~~~~~ From 748d24422fbdf451a31a759676d015d8898d7eba Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Thu, 26 Feb 2026 00:07:42 +0200 Subject: [PATCH 3/6] fix: support datetime variables in `Dataset.interp` Signed-off-by: Emmanuel Ferdman --- xarray/core/dataarray.py | 4 ++-- xarray/core/dataset.py | 18 ++++++++---------- xarray/tests/test_interp.py | 3 ++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 5f14e280f34..3ac154095f5 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -2407,7 +2407,7 @@ def interp( * x (x) float64 32B 0.0 0.75 1.25 1.75 * y (y) int64 24B 11 13 15 """ - if self.dtype.kind not in "uifc": + if self.dtype.kind not in "uifcMm": raise TypeError( f"interp only works for a numeric type array. Given {self.dtype}." ) @@ -2548,7 +2548,7 @@ def interp_like( * y (y) int64 24B 70 80 90 """ - if self.dtype.kind not in "uifc": + if self.dtype.kind not in "uifcMm": raise TypeError( f"interp only works for a numeric type array. Given {self.dtype}." ) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index f95844523ac..0457e4698ec 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -34,7 +34,7 @@ from xarray.coding.calendar_ops import convert_calendar, interp_calendar from xarray.coding.cftimeindex import CFTimeIndex, _parse_array_of_cftime_strings from xarray.compat.array_api_compat import to_like_array -from xarray.computation import ops +from xarray.computation import computation, ops from xarray.computation.arithmetic import DatasetArithmetic from xarray.core import dtypes as xrdtypes from xarray.core import duck_array_ops, formatting, formatting_html, utils @@ -3943,20 +3943,18 @@ def _validate_interp_indexer(x, new_x): elif dtype_kind in "Mm" and (use_indexers.keys() & var.dims): # For datetime-like types, interpolate as float64: var_indexers = {k: v for k, v in use_indexers.items() if k in var.dims} - int_data = var.data.view(np.int64) + int_data = var.astype(np.int64) nat = np.iinfo(np.int64).min - as_float = np.where( + as_float = computation.where( int_data == nat, np.nan, int_data.astype(np.float64) ) - result = missing.interp( - var.copy(data=as_float), var_indexers, method, **kwargs - ) - as_int = np.where( - np.isnan(result.data), + result = missing.interp(as_float, var_indexers, method, **kwargs) + as_int = computation.where( + result.isnull(), nat, - np.round(np.nan_to_num(result.data)).astype(np.int64), + result.fillna(0).round().astype(np.int64), ) - variables[name] = result.copy(data=as_int.view(var.dtype)) + variables[name] = as_int.astype(var.dtype) elif dtype_kind in "ObU" and (use_indexers.keys() & var.dims): if all(var.sizes[d] == 1 for d in (use_indexers.keys() & var.dims)): # Broadcastable, can be handled quickly without reindex: diff --git a/xarray/tests/test_interp.py b/xarray/tests/test_interp.py index 394bd60ff24..18166173033 100644 --- a/xarray/tests/test_interp.py +++ b/xarray/tests/test_interp.py @@ -1267,7 +1267,8 @@ def test_dataset_interp_datetime_dask() -> None: coords={"x": np.arange(5), "y": np.arange(5)}, ).chunk({"x": 2, "y": 2}) - result = ds.interp(x=[0.5, 1.5], y=[0.5, 1.5]) + with raise_if_dask_computes(): + result = ds.interp(x=[0.5, 1.5], y=[0.5, 1.5]) assert "time" in result.data_vars computed = result.compute() From 007e88af3cc00c024f0ce6d48b787cf38206a4a7 Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Thu, 26 Feb 2026 19:26:32 +0200 Subject: [PATCH 4/6] fix: support datetime variables in `Dataset.interp` Signed-off-by: Emmanuel Ferdman --- xarray/core/dataset.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 0457e4698ec..5ade28e5059 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -3946,13 +3946,13 @@ def _validate_interp_indexer(x, new_x): int_data = var.astype(np.int64) nat = np.iinfo(np.int64).min as_float = computation.where( - int_data == nat, np.nan, int_data.astype(np.float64) + int_data != nat, int_data.astype(np.float64), np.nan ) result = missing.interp(as_float, var_indexers, method, **kwargs) as_int = computation.where( - result.isnull(), - nat, + ~result.isnull(), result.fillna(0).round().astype(np.int64), + nat, ) variables[name] = as_int.astype(var.dtype) elif dtype_kind in "ObU" and (use_indexers.keys() & var.dims): From 29ba14ce9a3d7ee337a6929c600c619e59b71f9c Mon Sep 17 00:00:00 2001 From: Emmanuel Ferdman Date: Fri, 27 Feb 2026 00:08:30 +0200 Subject: [PATCH 5/6] fix: support datetime variables in `Dataset.interp` Signed-off-by: Emmanuel Ferdman --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 486aabce894..2cd13b6bd73 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -152,7 +152,7 @@ Bug Fixes - Improve error message when scipy is missing for :py:class:`~xarray.indexes.NDPointIndex` (:pull:`11085`). By `Sakshee_D `_. - Fix :py:meth:`Dataset.interp` silently dropping datetime64 and timedelta64 - variables (:issue:`10900`, :pull:`11081`). + variables, though enabling their interpolation (:issue:`10900`, :pull:`11081`). By `Emmanuel Ferdman `_. Documentation From a5cb174882cd27cef6a094835f367c5fa79e3900 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Fri, 27 Feb 2026 04:55:20 -0500 Subject: [PATCH 6/6] Fix typo --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 2cd13b6bd73..65b4943c97e 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -152,7 +152,7 @@ Bug Fixes - Improve error message when scipy is missing for :py:class:`~xarray.indexes.NDPointIndex` (:pull:`11085`). By `Sakshee_D `_. - Fix :py:meth:`Dataset.interp` silently dropping datetime64 and timedelta64 - variables, though enabling their interpolation (:issue:`10900`, :pull:`11081`). + variables, through enabling their interpolation (:issue:`10900`, :pull:`11081`). By `Emmanuel Ferdman `_. Documentation