From d864330d832ea02b53828fd82f792fc82d68cb17 Mon Sep 17 00:00:00 2001 From: Fix 11417 Date: Fri, 26 Jun 2026 22:46:31 +0800 Subject: [PATCH] fix: skip CF encoding for zarr arrays with native DateTime64 dtype (GH#11350) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When writing to a zarr array with native DateTime64 dtype (zarr v3), skip CF datetime encoding. CF encoding converts datetime64 to int64 with units/calendar attributes, which corrupts data when written to a native DateTime64 zarr array — the int64 values get misinterpreted as raw epoch seconds instead of the encoded 'days since' values. Fixes #11350 --- xarray/backends/zarr.py | 25 +++++++++++++++++++++++-- xarray/tests/test_backends.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py index 6f3e1ad4eb4..5b34b6be2d6 100644 --- a/xarray/backends/zarr.py +++ b/xarray/backends/zarr.py @@ -1090,8 +1090,29 @@ def store( ) vars_with_encoding[vn] = variables[vn].copy(deep=False) vars_with_encoding[vn].encoding = existing_vars[vn].encoding - vars_with_encoding, _ = self.encode(vars_with_encoding, {}) - variables_encoded.update(vars_with_encoding) + + # Skip CF encoding for zarr v3 native DateTime64 arrays (GH#11350). + native_datetime_vars: set[str] = set() + if _zarr_v3() and self.zarr_group.metadata.zarr_format == 3: + from zarr.dtype import DateTime64 + + for vn in existing_variable_names: + name = _encode_variable_name(vn) + zarr_array = self.members[name] + if isinstance(zarr_array.metadata.data_type, DateTime64): + native_datetime_vars.add(vn) + + vars_to_encode = { + vn: v + for vn, v in vars_with_encoding.items() + if vn not in native_datetime_vars + } + if vars_to_encode: + encoded, _ = self.encode(vars_to_encode, {}) + variables_encoded.update(encoded) + + for vn in native_datetime_vars: + variables_encoded[vn] = vars_with_encoding[vn] for var_name in existing_variable_names: variables_encoded[var_name] = _validate_and_transpose_existing_dims( diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index 4e08b71260b..1bc3e143c02 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -8153,3 +8153,32 @@ def test_get_mtime_non_file_paths() -> None: # GDAL virtual filesystem paths are not real files assert _get_mtime("/vsicurl/https://example.com/file.nc") is None + + +@requires_zarr +@requires_zarr_v3 +def test_zarr_datetime64_roundtrip_no_corruption() -> None: + """Regression test for https://github.com/pydata/xarray/issues/11350. + + Writing a dataset back to a zarr store with native DateTime64 dtype + should not corrupt the datetime values. + """ + import zarr.dtype + + store = zarr.storage.MemoryStore() + g = zarr.create_group(store) + a = g.create_array( + "a", + shape=(2,), + dtype=zarr.dtype.DateTime64(unit="s", scale_factor=1), + dimension_names=["time"], + ) + a[:] = np.array(["2025-01-01", "2025-01-02"], dtype="