Repository: pydata/xarray

Issue:
Writing to regions with unaligned chunks can lose data
### What happened?

Writing with `region` with chunks that aren't aligned can lose data.

I've recreated an example below. While it's unlikely that folks are passing different values to `.chunk` for the template vs. the regions, I had an `"auto"` chunk, which can then set different chunk values.

(FWIW, this was fairly painful, and I managed to lose a lot of time by not noticing this, and then not really considering this could happen as I was trying to debug. I think we should really strive to ensure that we don't lose data / incorrectly report that we've successfully written data...)

### What did you expect to happen?

If there's a risk of data loss, raise an error...

### Minimal Complete Verifiable Example

```Python
ds = xr.DataArray(np.arange(120).reshape(4,3,-1),dims=list("abc")).rename('var1').to_dataset().chunk(2)

ds

# <xarray.Dataset>
# Dimensions:  (a: 4, b: 3, c: 10)
# Dimensions without coordinates: a, b, c
# Data variables:
#     var1     (a, b, c) int64 dask.array<chunksize=(2, 2, 2), meta=np.ndarray>

def write(ds):
    ds.chunk(5).to_zarr('foo.zarr', compute=False, mode='w')
    for r in (range(ds.sizes['a'])):
        ds.chunk(3).isel(a=[r]).to_zarr('foo.zarr', region=dict(a=slice(r, r+1)))


def read(ds):
    result = xr.open_zarr('foo.zarr')
    assert result.compute().identical(ds)
    print(result.chunksizes, ds.chunksizes)

write(ds); read(ds)

# AssertionError

xr.open_zarr('foo.zarr').compute()['var1']

<xarray.DataArray 'var1' (a: 4, b: 3, c: 10)>
array([[[  0,   0,   0,   3,   4,   5,   0,   0,   0,   9],
        [  0,   0,   0,  13,  14,  15,   0,   0,   0,  19],
        [  0,   0,   0,  23,  24,  25,   0,   0,   0,  29]],

       [[ 30,  31,  32,   0,   0,  35,  36,  37,  38,   0],
        [ 40,  41,  42,   0,   0,  45,  46,  47,  48,   0],
        [ 50,  51,  52,   0,   0,  55,  56,  57,  58,   0]],

       [[ 60,  61,  62,   0,   0,  65,   0,   0,   0,  69],
        [ 70,  71,  72,   0,   0,  75,   0,   0,   0,  79],
        [ 80,  81,  82,   0,   0,  85,   0,   0,   0,  89]],

       [[  0,   0,   0,  93,  94,  95,  96,  97,  98,   0],
        [  0,   0,   0, 103, 104, 105, 106, 107, 108,   0],
        [  0,   0,   0, 113, 114, 115, 116, 117, 118,   0]]])
Dimensions without coordinates: a, b, c
```


### MVCE confirmation

- [X] Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
- [X] Complete example — the example is self-contained, including all data and the text of any traceback.
- [X] Verifiable example — the example copy & pastes into an IPython prompt or [Binder notebook](https://mybinder.org/v2/gh/pydata/xarray/main?urlpath=lab/tree/doc/examples/blank_template.ipynb), returning the result.
- [X] New issue — a search of GitHub Issues suggests this is not a duplicate.
- [X] Recent environment — the issue occurs with the latest version of xarray and its dependencies.

### Relevant log output

_No response_

### Anything else we need to know?

_No response_

### Environment

<details>


INSTALLED VERSIONS
------------------
commit: ccc8f9987b553809fb6a40c52fa1a8a8095c8c5f
python: 3.9.18 (main, Aug 24 2023, 21:19:58)
[Clang 14.0.3 (clang-1403.0.22.14.1)]
python-bits: 64
OS: Darwin
OS-release: 22.6.0
machine: arm64
processor: arm
byteorder: little
LC_ALL: en_US.UTF-8
LANG: None
LOCALE: ('en_US', 'UTF-8')
libhdf5: None
libnetcdf: None

xarray: 2023.10.2.dev10+gccc8f998
pandas: 2.1.1
numpy: 1.25.2
scipy: 1.11.1
netCDF4: None
pydap: None
h5netcdf: None
h5py: None
Nio: None
zarr: 2.16.0
cftime: None
nc_time_axis: None
PseudoNetCDF: None
iris: None
bottleneck: None
dask: 2023.4.0
distributed: 2023.7.1
matplotlib: 3.5.1
cartopy: None
seaborn: None
numbagg: 0.2.3.dev30+gd26e29e
fsspec: 2021.11.1
cupy: None
pint: None
sparse: None
flox: None
numpy_groupies: 0.9.19
setuptools: 68.1.2
pip: 23.2.1
conda: None
pytest: 7.4.0
mypy: 1.6.0
IPython: 8.15.0
sphinx: 4.3.2

</details>


Discussion:
rabernat: @max-sixty thanks so much for tackling this tricky issue. Can you help me understand whether the following edge case is covered here...

For our existing "safe chunks" check, we have a special case for the last chunk, which does not have to evenly divide the zarr chunks:

https://github.com/pydata/xarray/blob/bb8511e0894020e180d95d2edb29ed4036ac6447/xarray/backends/zarr.py#L185-L189

(i.e. we just skip the check for the last chunk; `dchunks[:-1]`) That is appropriate because, as implemented, the assumption is that we are always writing _from the start of the array_.

With `regions` (and also with `append`), that assumption is no longer true; we now have an initial offset to consider. Imagine, for example, an array with chunksize 10, where we want to write the region `slice(5, 35)` (offset 5).  An acceptable Dask chunking would be (5, 10, 10, 5). I'm fairly certain that the existing chunk alignment code does not consider this and would not allow it. On the other hand, it _would_ allow (10, 10, 10), **which would be wrong because of the offset**!

Is handling these sorts of scenarios in scope for this PR?
max-sixty: Excellent point @rabernat ! Let me consider that.

@dcherian could you confirm you agree with the change assuming we handle that? I wasn't sure where your were at in #8371. TY!
dcherian: > Would you confirm you agree with the change assuming we handle that? 

Yes! sorry for the delay, I didn't realize that we could skip this guardrail with `safe_chunks=False`
dcherian: >  we now have an initial offset to consider. Imagine, for example, an array with chunksize 10, where we want to write the region slice(5, 35) (offset 5). An acceptable Dask chunking would be (5, 10, 10, 5). I'm fairly certain that the existing chunk alignment code does not consider this and would not allow it. On the other hand, it would allow (10, 10, 10), which would be wrong because of the offset!

This is a great observation but I think we can merge this  PR for two reasons:
1. this is an improvement over the status quo
2. Users making region writes should ensure that the chunk boundaries line up. I've added a warning to the docstring for this.

Of course, it'd be nice to catch this case in the future.
rsemlal-murmuration: I think this adresses also, the problem of this issue: https://github.com/pydata/xarray/issues/8876

Patch:
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index e8ce0cff..291f331c 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -22,7 +22,10 @@ v2024.03.0 (unreleased)
 
 New Features
 ~~~~~~~~~~~~
-
+- Partial writes to existing chunks with ``region`` or ``append_dim`` will now raise an error
+  (unless ``safe_chunks=False``); previously an error would only be raised on
+  new variables. (:pull:`8459`, :issue:`8371`, :issue:`8882`)
+  By `Maximilian Roos <https://github.com/max-sixty>`_.
 - Grouped and resampling quantile calculations now use the vectorized algorithm in ``flox>=0.9.4`` if present.
   By `Deepak Cherian <https://github.com/dcherian>`_.
 - Do not broadcast in arithmetic operations when global option ``arithmetic_broadcast=False``
@@ -37,6 +40,8 @@ New Features
 - Allow creating :py:class:`xr.Coordinates` objects with no indexes (:pull:`8711`)
   By `Benoit Bovy <https://github.com/benbovy>`_ and `Tom Nicholas
   <https://github.com/TomNicholas>`_.
+- Enable plotting of ``datetime.dates``. (:issue:`8866`, :pull:`8873`)
+  By `Sascha Hofmann <https://github.com/saschahofmann>`_.
 
 Breaking changes
 ~~~~~~~~~~~~~~~~
@@ -73,6 +78,9 @@ Bug fixes
 - Warn and return bytes undecoded in case of UnicodeDecodeError in h5netcdf-backend
   (:issue:`5563`, :pull:`8874`).
   By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.
+- Fix bug incorrectly disallowing creation of a dataset with a multidimensional coordinate variable with the same name as one of its dims.
+  (:issue:`8884`, :pull:`8886`)
+  By `Tom Nicholas <https://github.com/TomNicholas>`_.
 
 
 Documentation
diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py
index 13b1819f..3d6baeef 100644
--- a/xarray/backends/zarr.py
+++ b/xarray/backends/zarr.py
@@ -195,7 +195,7 @@ def _determine_zarr_chunks(enc_chunks, var_chunks, ndim, name, safe_chunks):
                         f"Writing this array in parallel with dask could lead to corrupted data."
                     )
                     if safe_chunks:
-                        raise NotImplementedError(
+                        raise ValueError(
                             base_error
                             + " Consider either rechunking using `chunk()`, deleting "
                             "or modifying `encoding['chunks']`, or specify `safe_chunks=False`."
@@ -623,7 +623,12 @@ class ZarrStore(AbstractWritableDataStore):
             # avoid needing to load index variables into memory.
             # TODO: consider making loading indexes lazy again?
             existing_vars, _, _ = conventions.decode_cf_variables(
-                self.get_variables(), self.get_attrs()
+                {
+                    k: v
+                    for k, v in self.get_variables().items()
+                    if k in existing_variable_names
+                },
+                self.get_attrs(),
             )
             # Modified variables must use the same encoding as the store.
             vars_with_encoding = {}
@@ -702,6 +707,17 @@ class ZarrStore(AbstractWritableDataStore):
             if v.encoding == {"_FillValue": None} and fill_value is None:
                 v.encoding = {}
 
+            # We need to do this for both new and existing variables to ensure we're not
+            # writing to a partial chunk, even though we don't use the `encoding` value
+            # when writing to an existing variable. See
+            # https://github.com/pydata/xarray/issues/8371 for details.
+            encoding = extract_zarr_variable_encoding(
+                v,
+                raise_on_invalid=check,
+                name=vn,
+                safe_chunks=self._safe_chunks,
+            )
+
             if name in existing_keys:
                 # existing variable
                 # TODO: if mode="a", consider overriding the existing variable
@@ -732,9 +748,6 @@ class ZarrStore(AbstractWritableDataStore):
                     zarr_array = self.zarr_group[name]
             else:
                 # new variable
-                encoding = extract_zarr_variable_encoding(
-                    v, raise_on_invalid=check, name=vn, safe_chunks=self._safe_chunks
-                )
                 encoded_attrs = {}
                 # the magic for storing the hidden dimension data
                 encoded_attrs[DIMENSION_KEY] = dims
diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py
index 389316d6..80dcfe13 100644
--- a/xarray/core/dataarray.py
+++ b/xarray/core/dataarray.py
@@ -4120,7 +4120,7 @@ class DataArray(
         compute: Literal[True] = True,
         consolidated: bool | None = None,
         append_dim: Hashable | None = None,
-        region: Mapping[str, slice] | None = None,
+        region: Mapping[str, slice | Literal["auto"]] | Literal["auto"] | None = None,
         safe_chunks: bool = True,
         storage_options: dict[str, str] | None = None,
         zarr_version: int | None = None,
@@ -4140,7 +4140,7 @@ class DataArray(
         compute: Literal[False],
         consolidated: bool | None = None,
         append_dim: Hashable | None = None,
-        region: Mapping[str, slice] | None = None,
+        region: Mapping[str, slice | Literal["auto"]] | Literal["auto"] | None = None,
         safe_chunks: bool = True,
         storage_options: dict[str, str] | None = None,
         zarr_version: int | None = None,
@@ -4158,7 +4158,7 @@ class DataArray(
         compute: bool = True,
         consolidated: bool | None = None,
         append_dim: Hashable | None = None,
-        region: Mapping[str, slice] | None = None,
+        region: Mapping[str, slice | Literal["auto"]] | Literal["auto"] | None = None,
         safe_chunks: bool = True,
         storage_options: dict[str, str] | None = None,
         zarr_version: int | None = None,
@@ -4237,6 +4237,12 @@ class DataArray(
               in with ``region``, use a separate call to ``to_zarr()`` with
               ``compute=False``. See "Appending to existing Zarr stores" in
               the reference documentation for full details.
+
+            Users are expected to ensure that the specified region aligns with
+            Zarr chunk boundaries, and that dask chunks are also aligned.
+            Xarray makes limited checks that these multiple chunk boundaries line up.
+            It is possible to write incomplete chunks and corrupt the data with this
+            option if you are not careful.
         safe_chunks : bool, default: True
             If True, only allow writes to when there is a many-to-one relationship
             between Zarr chunks (specified in encoding) and Dask chunks.
diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py
index 10bf1466..2c0b3e89 100644
--- a/xarray/core/dataset.py
+++ b/xarray/core/dataset.py
@@ -2452,6 +2452,12 @@ class Dataset(
               in with ``region``, use a separate call to ``to_zarr()`` with
               ``compute=False``. See "Appending to existing Zarr stores" in
               the reference documentation for full details.
+
+            Users are expected to ensure that the specified region aligns with
+            Zarr chunk boundaries, and that dask chunks are also aligned.
+            Xarray makes limited checks that these multiple chunk boundaries line up.
+            It is possible to write incomplete chunks and corrupt the data with this
+            option if you are not careful.
         safe_chunks : bool, default: True
             If True, only allow writes to when there is a many-to-one relationship
             between Zarr chunks (specified in encoding) and Dask chunks.
diff --git a/xarray/core/merge.py b/xarray/core/merge.py
index cbd06c8f..a90e59e7 100644
--- a/xarray/core/merge.py
+++ b/xarray/core/merge.py
@@ -562,25 +562,6 @@ def merge_coords(
     return variables, out_indexes
 
 
-def assert_valid_explicit_coords(
-    variables: Mapping[Any, Any],
-    dims: Mapping[Any, int],
-    explicit_coords: Iterable[Hashable],
-) -> None:
-    """Validate explicit coordinate names/dims.
-
-    Raise a MergeError if an explicit coord shares a name with a dimension
-    but is comprised of arbitrary dimensions.
-    """
-    for coord_name in explicit_coords:
-        if coord_name in dims and variables[coord_name].dims != (coord_name,):
-            raise MergeError(
-                f"coordinate {coord_name} shares a name with a dataset dimension, but is "
-                "not a 1D variable along that dimension. This is disallowed "
-                "by the xarray data model."
-            )
-
-
 def merge_attrs(variable_attrs, combine_attrs, context=None):
     """Combine attributes from different variables according to combine_attrs"""
     if not variable_attrs:
@@ -728,7 +709,6 @@ def merge_core(
         # coordinates may be dropped in merged results
         coord_names.intersection_update(variables)
     if explicit_coords is not None:
-        assert_valid_explicit_coords(variables, dims, explicit_coords)
         coord_names.update(explicit_coords)
     for dim, size in dims.items():
         if dim in variables:
diff --git a/xarray/plot/utils.py b/xarray/plot/utils.py
index 804e1cfd..8789bc2f 100644
--- a/xarray/plot/utils.py
+++ b/xarray/plot/utils.py
@@ -4,7 +4,7 @@ import itertools
 import textwrap
 import warnings
 from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
-from datetime import datetime
+from datetime import date, datetime
 from inspect import getfullargspec
 from typing import TYPE_CHECKING, Any, Callable, Literal, overload
 
@@ -672,7 +672,7 @@ def _ensure_plottable(*args) -> None:
         np.bool_,
         np.str_,
     )
-    other_types: tuple[type[object], ...] = (datetime,)
+    other_types: tuple[type[object], ...] = (datetime, date)
     cftime_datetime_types: tuple[type[object], ...] = (
         () if cftime is None else (cftime.datetime,)
     )


Test patch:
diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py
index 07573066..be9b3ef0 100644
--- a/xarray/tests/test_backends.py
+++ b/xarray/tests/test_backends.py
@@ -2261,7 +2261,6 @@ class ZarrBase(CFEncodedBase):
         original = create_test_data().chunk({"dim1": 3, "dim2": 4, "dim3": 3})
         with self.roundtrip(original, open_kwargs={"chunks": {}}) as actual:
             for k, v in actual.data_vars.items():
-                print(k)
                 assert v.chunks == actual[k].chunks
 
     def test_chunk_encoding(self) -> None:
@@ -2305,7 +2304,7 @@ class ZarrBase(CFEncodedBase):
         # should fail if encoding["chunks"] clashes with dask_chunks
         badenc = ds.chunk({"x": 4})
         badenc.var1.encoding["chunks"] = (6,)
-        with pytest.raises(NotImplementedError, match=r"named 'var1' would overlap"):
+        with pytest.raises(ValueError, match=r"named 'var1' would overlap"):
             with self.roundtrip(badenc) as actual:
                 pass
 
@@ -2343,9 +2342,7 @@ class ZarrBase(CFEncodedBase):
         # but itermediate unaligned chunks are bad
         badenc = ds.chunk({"x": (3, 5, 3, 1)})
         badenc.var1.encoding["chunks"] = (3,)
-        with pytest.raises(
-            NotImplementedError, match=r"would overlap multiple dask chunks"
-        ):
+        with pytest.raises(ValueError, match=r"would overlap multiple dask chunks"):
             with self.roundtrip(badenc) as actual:
                 pass
 
@@ -2359,7 +2356,7 @@ class ZarrBase(CFEncodedBase):
         # TODO: remove this failure once synchronized overlapping writes are
         # supported by xarray
         ds_chunk4["var1"].encoding.update({"chunks": 5})
-        with pytest.raises(NotImplementedError, match=r"named 'var1' would overlap"):
+        with pytest.raises(ValueError, match=r"named 'var1' would overlap"):
             with self.roundtrip(ds_chunk4) as actual:
                 pass
         # override option
@@ -2468,6 +2465,27 @@ class ZarrBase(CFEncodedBase):
         ) as actual:
             assert_identical(original, actual)
 
+    def test_zarr_mode_w_overwrites_encoding(self) -> None:
+        import zarr
+
+        data = Dataset({"foo": ("x", [1.0, 1.0, 1.0])})
+        with self.create_zarr_target() as store:
+            data.to_zarr(
+                store, **self.version_kwargs, encoding={"foo": {"add_offset": 1}}
+            )
+            np.testing.assert_equal(
+                zarr.open_group(store, **self.version_kwargs)["foo"], data.foo.data - 1
+            )
+            data.to_zarr(
+                store,
+                **self.version_kwargs,
+                encoding={"foo": {"add_offset": 0}},
+                mode="w",
+            )
+            np.testing.assert_equal(
+                zarr.open_group(store, **self.version_kwargs)["foo"], data.foo.data
+            )
+
     def test_encoding_kwarg_fixed_width_string(self) -> None:
         # not relevant for zarr, since we don't use EncodedStringCoder
         pass
@@ -5733,3 +5751,80 @@ def test_zarr_region(tmp_path):
 
     # Write without region
     ds_transposed.to_zarr(tmp_path / "test.zarr", mode="r+")
+
+
+@requires_zarr
+@requires_dask
+def test_zarr_region_chunk_partial(tmp_path):
+    """
+    Check that writing to partial chunks with `region` fails, assuming `safe_chunks=False`.
+    """
+    ds = (
+        xr.DataArray(np.arange(120).reshape(4, 3, -1), dims=list("abc"))
+        .rename("var1")
+        .to_dataset()
+    )
+
+    ds.chunk(5).to_zarr(tmp_path / "foo.zarr", compute=False, mode="w")
+    with pytest.raises(ValueError):
+        for r in range(ds.sizes["a"]):
+            ds.chunk(3).isel(a=[r]).to_zarr(
+                tmp_path / "foo.zarr", region=dict(a=slice(r, r + 1))
+            )
+
+
+@requires_zarr
+@requires_dask
+def test_zarr_append_chunk_partial(tmp_path):
+    t_coords = np.array([np.datetime64("2020-01-01").astype("datetime64[ns]")])
+    data = np.ones((10, 10))
+
+    da = xr.DataArray(
+        data.reshape((-1, 10, 10)),
+        dims=["time", "x", "y"],
+        coords={"time": t_coords},
+        name="foo",
+    )
+    da.to_zarr(tmp_path / "foo.zarr", mode="w", encoding={"foo": {"chunks": (5, 5, 1)}})
+
+    new_time = np.array([np.datetime64("2021-01-01").astype("datetime64[ns]")])
+
+    da2 = xr.DataArray(
+        data.reshape((-1, 10, 10)),
+        dims=["time", "x", "y"],
+        coords={"time": new_time},
+        name="foo",
+    )
+    with pytest.raises(ValueError, match="encoding was provided"):
+        da2.to_zarr(
+            tmp_path / "foo.zarr",
+            append_dim="time",
+            mode="a",
+            encoding={"foo": {"chunks": (1, 1, 1)}},
+        )
+
+    # chunking with dask sidesteps the encoding check, so we need a different check
+    with pytest.raises(ValueError, match="Specified zarr chunks"):
+        da2.chunk({"x": 1, "y": 1, "time": 1}).to_zarr(
+            tmp_path / "foo.zarr", append_dim="time", mode="a"
+        )
+
+
+@requires_zarr
+@requires_dask
+def test_zarr_region_chunk_partial_offset(tmp_path):
+    # https://github.com/pydata/xarray/pull/8459#issuecomment-1819417545
+    store = tmp_path / "foo.zarr"
+    data = np.ones((30,))
+    da = xr.DataArray(data, dims=["x"], coords={"x": range(30)}, name="foo").chunk(x=10)
+    da.to_zarr(store, compute=False)
+
+    da.isel(x=slice(10)).chunk(x=(10,)).to_zarr(store, region="auto")
+
+    da.isel(x=slice(5, 25)).chunk(x=(10, 10)).to_zarr(
+        store, safe_chunks=False, region="auto"
+    )
+
+    # This write is unsafe, and should raise an error, but does not.
+    # with pytest.raises(ValueError):
+    #     da.isel(x=slice(5, 25)).chunk(x=(10, 10)).to_zarr(store, region="auto")
diff --git a/xarray/tests/test_coordinates.py b/xarray/tests/test_coordinates.py
index 40743194..f88e554d 100644
--- a/xarray/tests/test_coordinates.py
+++ b/xarray/tests/test_coordinates.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import numpy as np
 import pandas as pd
 import pytest
 
@@ -8,7 +9,7 @@ from xarray.core.coordinates import Coordinates
 from xarray.core.dataarray import DataArray
 from xarray.core.dataset import Dataset
 from xarray.core.indexes import PandasIndex, PandasMultiIndex
-from xarray.core.variable import IndexVariable
+from xarray.core.variable import IndexVariable, Variable
 from xarray.tests import assert_identical, source_ndarray
 
 
@@ -174,3 +175,10 @@ class TestCoordinates:
         left2, right2 = align(left, right, join="override")
         assert_identical(left2, left)
         assert_identical(left2, right2)
+
+    def test_dataset_from_coords_with_multidim_var_same_name(self):
+        # regression test for GH #8883
+        var = Variable(data=np.arange(6).reshape(2, 3), dims=["x", "y"])
+        coords = Coordinates(coords={"x": var}, indexes={})
+        ds = Dataset(coords=coords)
+        assert ds.coords["x"].dims == ("x", "y")
diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py
index 39c404d0..e2a64964 100644
--- a/xarray/tests/test_dataset.py
+++ b/xarray/tests/test_dataset.py
@@ -486,14 +486,6 @@ class TestDataset:
         actual = Dataset({"z": expected["z"]})
         assert_identical(expected, actual)
 
-    def test_constructor_invalid_dims(self) -> None:
-        # regression for GH1120
-        with pytest.raises(MergeError):
-            Dataset(
-                data_vars=dict(v=("y", [1, 2, 3, 4])),
-                coords=dict(y=DataArray([0.1, 0.2, 0.3, 0.4], dims="x")),
-            )
-
     def test_constructor_1d(self) -> None:
         expected = Dataset({"x": (["x"], 5.0 + np.arange(5))})
         actual = Dataset({"x": 5.0 + np.arange(5)})
diff --git a/xarray/tests/test_plot.py b/xarray/tests/test_plot.py
index 6f983a12..8da1bba6 100644
--- a/xarray/tests/test_plot.py
+++ b/xarray/tests/test_plot.py
@@ -5,7 +5,7 @@ import inspect
 import math
 from collections.abc import Hashable
 from copy import copy
-from datetime import datetime
+from datetime import date, datetime, timedelta
 from typing import Any, Callable, Literal
 
 import numpy as np
@@ -620,6 +620,18 @@ class TestPlot(PlotTestCase):
         ax = plt.gca()
         assert ax.has_data()
 
+    def test_date_dimension(self) -> None:
+        nrow = 3
+        ncol = 4
+        start = date(2000, 1, 1)
+        time = [start + timedelta(days=i) for i in range(nrow)]
+        a = DataArray(
+            easy_array((nrow, ncol)), coords=[("time", time), ("y", range(ncol))]
+        )
+        a.plot()
+        ax = plt.gca()
+        assert ax.has_data()
+
     @pytest.mark.slow
     @pytest.mark.filterwarnings("ignore:tight_layout cannot")
     def test_convenient_facetgrid(self) -> None: