Repository: pydata/xarray

Issue:
cannot use negative step to sel from zarr (without dask)
### What happened?

As per: https://github.com/pydata/xarray/pull/8246#discussion_r1340357405

Passing a negative step in a `slice` to select a non-chunked zarr-backed datasets raises an error.

### What did you expect to happen?

zarr should allow negative step (probably?)

### Minimal Complete Verifiable Example

```Python
import xarray as xr

# create a zarr dataset
air = xr.tutorial.open_dataset("air_temperature")
air.to_zarr("test.zarr")

ds = xr.open_dataset("test.zarr", engine="zarr")
ds.air[::-1, ].load()

# note that this works if the dataset is backed by dask
ds_dask = xr.open_dataset("test.zarr", engine="zarr", chunks="auto")
ds_dask.air[::-1, ].load()
```


### MVCE confirmation

- [ ] Minimal example — the example is as focused as reasonably possible to demonstrate the underlying issue in xarray.
- [ ] Complete example — the example is self-contained, including all data and the text of any traceback.
- [ ] 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.
- [ ] New issue — a search of GitHub Issues suggests this is not a duplicate.

### Relevant log output

```Python
File ~/code/xarray/xarray/core/parallelcompat.py:93, in guess_chunkmanager(manager)
     91 if isinstance(manager, str):
     92     if manager not in chunkmanagers:
---> 93         raise ValueError(
     94             f"unrecognized chunk manager {manager} - must be one of: {list(chunkmanagers)}"
     95         )
     97     return chunkmanagers[manager]
     98 elif isinstance(manager, ChunkManagerEntrypoint):
     99     # already a valid ChunkManager so just pass through

ValueError: unrecognized chunk manager dask - must be one of: []
```


### Anything else we need to know?

The error comes from https://github.com/zarr-developers/zarr-python/blob/6ec746ef1242dd9fec26b128cc0b3455d28ad6f0/zarr/indexing.py#L174 so it would need an upstream fix first.

cc @dcherian is this what you had in mind?

### Environment

<details>

INSTALLED VERSIONS
------------------
commit: f6d69a1f6d952dcd67609c97f3fb3069abdda586
python: 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:40:32) [GCC 12.3.0]
python-bits: 64
OS: Linux
OS-release: 6.2.0-33-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: ('en_US', 'UTF-8')
libhdf5: 1.14.2
libnetcdf: 4.9.2

xarray: 2023.9.1.dev8+gf6d69a1f
pandas: 2.1.1
numpy: 1.24.4
scipy: 1.11.3
netCDF4: 1.6.4
pydap: installed
h5netcdf: 1.2.0
h5py: 3.9.0
Nio: None
zarr: 2.16.1
cftime: 1.6.2
nc_time_axis: 1.4.1
PseudoNetCDF: 3.2.2
iris: 3.7.0
bottleneck: 1.3.7
dask: 2023.9.2
distributed: None
matplotlib: 3.8.0
cartopy: 0.22.0
seaborn: 0.12.2
numbagg: 0.2.2
fsspec: 2023.9.2
cupy: None
pint: 0.20.1
sparse: 0.14.0
flox: 0.7.2
numpy_groupies: 0.10.1
setuptools: 68.2.2
pip: 23.2.1
conda: None
pytest: 7.4.2
mypy: None
IPython: 8.15.0
sphinx: None


</details>


Discussion:

Patch:
diff --git a/doc/internals/extending-xarray.rst b/doc/internals/extending-xarray.rst
index cb1b23e7..0537ae85 100644
--- a/doc/internals/extending-xarray.rst
+++ b/doc/internals/extending-xarray.rst
@@ -103,7 +103,7 @@ The intent here is that libraries that extend xarray could add such an accessor
 to implement subclass specific functionality rather than using actual subclasses
 or patching in a large number of domain specific methods. For further reading
 on ways to write new accessors and the philosophy behind the approach, see
-:issue:`1080`.
+https://github.com/pydata/xarray/issues/1080.
 
 To help users keep things straight, please `let us know
 <https://github.com/pydata/xarray/issues>`_ if you plan to write a new accessor
diff --git a/doc/whats-new.rst b/doc/whats-new.rst
index dc4fb7ae..d51b5da2 100644
--- a/doc/whats-new.rst
+++ b/doc/whats-new.rst
@@ -59,6 +59,8 @@ Bug fixes
   By `Tom Nicholas <https://github.com/TomNicholas>`_.
 - Ensure :py:meth:`DataArray.unstack` works when wrapping array API-compliant classes. (:issue:`8666`, :pull:`8668`)
   By `Tom Nicholas <https://github.com/TomNicholas>`_.
+- Fix negative slicing of Zarr arrays without dask installed. (:issue:`8252`)
+  By `Deepak Cherian <https://github.com/dcherian>`_.
 - Preserve chunks when writing time-like variables to zarr by enabling lazy CF
   encoding of time-like variables (:issue:`7132`, :issue:`8230`, :issue:`8432`,
   :pull:`8575`). By `Spencer Clark <https://github.com/spencerkclark>`_ and
@@ -82,6 +84,9 @@ Documentation
 Internal Changes
 ~~~~~~~~~~~~~~~~
 
+- ``DataArray.dt`` now raises an ``AttributeError`` rather than a ``TypeError``
+  when the data isn't datetime-like. (:issue:`8718`, :pull:`8724`)
+  By `Maximilian Roos <https://github.com/max-sixty>`_.
 
 .. _whats-new.2024.01.1:
 
diff --git a/pyproject.toml b/pyproject.toml
index 0dce98ff..90f92110 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -245,15 +245,8 @@ extend-exclude = [
   "doc",
   "_typed_ops.pyi",
 ]
-extend-safe-fixes = [
-  "TID252", # absolute imports
-]
 target-version = "py39"
 
-[tool.ruff.per-file-ignores]
-# don't enforce absolute imports
-"asv_bench/**" = ["TID252"]
-
 [tool.ruff.lint]
 # E402: module level import not at top of file
 # E501: line too long - let black worry about that
@@ -271,6 +264,13 @@ select = [
   "I", # isort
   "UP", # Pyupgrade
 ]
+extend-safe-fixes = [
+  "TID252", # absolute imports
+]
+
+[tool.ruff.lint.per-file-ignores]
+# don't enforce absolute imports
+"asv_bench/**" = ["TID252"]
 
 [tool.ruff.lint.isort]
 known-first-party = ["xarray"]
diff --git a/xarray/backends/zarr.py b/xarray/backends/zarr.py
index 469bbf4c..8f904047 100644
--- a/xarray/backends/zarr.py
+++ b/xarray/backends/zarr.py
@@ -86,19 +86,23 @@ class ZarrArrayWrapper(BackendArray):
     def _oindex(self, key):
         return self._array.oindex[key]
 
+    def _vindex(self, key):
+        return self._array.vindex[key]
+
+    def _getitem(self, key):
+        return self._array[key]
+
     def __getitem__(self, key):
         array = self._array
         if isinstance(key, indexing.BasicIndexer):
-            return array[key.tuple]
+            method = self._getitem
         elif isinstance(key, indexing.VectorizedIndexer):
-            return array.vindex[
-                indexing._arrayize_vectorized_indexer(key, self.shape).tuple
-            ]
-        else:
-            assert isinstance(key, indexing.OuterIndexer)
-            return indexing.explicit_indexing_adapter(
-                key, array.shape, indexing.IndexingSupport.VECTORIZED, self._oindex
-            )
+            method = self._vindex
+        elif isinstance(key, indexing.OuterIndexer):
+            method = self._oindex
+        return indexing.explicit_indexing_adapter(
+            key, array.shape, indexing.IndexingSupport.VECTORIZED, method
+        )
 
         # if self.ndim == 0:
         # could possibly have a work-around for 0d data here
diff --git a/xarray/core/accessor_dt.py b/xarray/core/accessor_dt.py
index 0745a72a..219bca90 100644
--- a/xarray/core/accessor_dt.py
+++ b/xarray/core/accessor_dt.py
@@ -616,7 +616,11 @@ class CombinedDatetimelikeAccessor(
         # appropriate. Since we're checking the dtypes anyway, we'll just
         # do all the validation here.
         if not _contains_datetime_like_objects(obj.variable):
-            raise TypeError(
+            # We use an AttributeError here so that `obj.dt` raises an error that
+            # `getattr` expects; https://github.com/pydata/xarray/issues/8718. It's a
+            # bit unusual in a `__new__`, but that's the only case where we use this
+            # class.
+            raise AttributeError(
                 "'.dt' accessor only available for "
                 "DataArray with datetime64 timedelta64 dtype or "
                 "for arrays containing cftime datetime "


Test patch:
diff --git a/xarray/tests/test_accessor_dt.py b/xarray/tests/test_accessor_dt.py
index 387929d3..d751d91d 100644
--- a/xarray/tests/test_accessor_dt.py
+++ b/xarray/tests/test_accessor_dt.py
@@ -145,7 +145,7 @@ class TestDatetimeAccessor:
         nontime_data = self.data.copy()
         int_data = np.arange(len(self.data.time)).astype("int8")
         nontime_data = nontime_data.assign_coords(time=int_data)
-        with pytest.raises(TypeError, match=r"dt"):
+        with pytest.raises(AttributeError, match=r"dt"):
             nontime_data.time.dt
 
     @pytest.mark.filterwarnings("ignore:dt.weekofyear and dt.week have been deprecated")
@@ -310,7 +310,7 @@ class TestTimedeltaAccessor:
         nontime_data = self.data.copy()
         int_data = np.arange(len(self.data.time)).astype("int8")
         nontime_data = nontime_data.assign_coords(time=int_data)
-        with pytest.raises(TypeError, match=r"dt"):
+        with pytest.raises(AttributeError, match=r"dt"):
             nontime_data.time.dt
 
     @pytest.mark.parametrize(
diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py
index e5fbe761..d898d3a3 100644
--- a/xarray/tests/test_dataarray.py
+++ b/xarray/tests/test_dataarray.py
@@ -71,8 +71,6 @@ pytestmark = [
     pytest.mark.filterwarnings("error:All-NaN (slice|axis) encountered"),
 ]
 
-ON_WINDOWS = sys.platform == "win32"
-
 
 class TestDataArray:
     @pytest.fixture(autouse=True)
@@ -87,61 +85,34 @@ class TestDataArray:
         self.mindex = pd.MultiIndex.from_product(
             [["a", "b"], [1, 2]], names=("level_1", "level_2")
         )
-        self.mda = DataArray([0, 1, 2, 3], coords={"x": self.mindex}, dims="x")
+        self.mda = DataArray([0, 1, 2, 3], coords={"x": self.mindex}, dims="x").astype(
+            np.uint64
+        )
 
-    @pytest.mark.skipif(
-        ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
     def test_repr(self) -> None:
         v = Variable(["time", "x"], [[1, 2, 3], [4, 5, 6]], {"foo": "bar"})
-        coords = {"x": np.arange(3, dtype=np.int64), "other": np.int64(0)}
+        v = v.astype(np.uint64)
+        coords = {"x": np.arange(3, dtype=np.uint64), "other": np.uint64(0)}
         data_array = DataArray(v, coords, name="my_variable")
         expected = dedent(
             """\
             <xarray.DataArray 'my_variable' (time: 2, x: 3)> Size: 48B
             array([[1, 2, 3],
-                   [4, 5, 6]])
-            Coordinates:
-              * x        (x) int64 24B 0 1 2
-                other    int64 8B 0
-            Dimensions without coordinates: time
-            Attributes:
-                foo:      bar"""
-        )
-        assert expected == repr(data_array)
-
-    @pytest.mark.skipif(
-        not ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
-    def test_repr_windows(self) -> None:
-        v = Variable(["time", "x"], [[1, 2, 3], [4, 5, 6]], {"foo": "bar"})
-        coords = {"x": np.arange(3, dtype=np.int64), "other": np.int64(0)}
-        data_array = DataArray(v, coords, name="my_variable")
-        expected = dedent(
-            """\
-            <xarray.DataArray 'my_variable' (time: 2, x: 3)> Size: 24B
-            array([[1, 2, 3],
-                   [4, 5, 6]])
+                   [4, 5, 6]], dtype=uint64)
             Coordinates:
-              * x        (x) int64 24B 0 1 2
-                other    int64 8B 0
+              * x        (x) uint64 24B 0 1 2
+                other    uint64 8B 0
             Dimensions without coordinates: time
             Attributes:
                 foo:      bar"""
         )
         assert expected == repr(data_array)
 
-    @pytest.mark.skipif(
-        ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
     def test_repr_multiindex(self) -> None:
         expected = dedent(
             """\
             <xarray.DataArray (x: 4)> Size: 32B
-            array([0, 1, 2, 3])
+            array([0, 1, 2, 3], dtype=uint64)
             Coordinates:
               * x        (x) object 32B MultiIndex
               * level_1  (x) object 32B 'a' 'a' 'b' 'b'
@@ -149,59 +120,20 @@ class TestDataArray:
         )
         assert expected == repr(self.mda)
 
-    @pytest.mark.skipif(
-        not ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
-    def test_repr_multiindex_windows(self) -> None:
-        expected = dedent(
-            """\
-            <xarray.DataArray (x: 4)> Size: 16B
-            array([0, 1, 2, 3])
-            Coordinates:
-              * x        (x) object 32B MultiIndex
-              * level_1  (x) object 32B 'a' 'a' 'b' 'b'
-              * level_2  (x) int64 32B 1 2 1 2"""
-        )
-        assert expected == repr(self.mda)
-
-    @pytest.mark.skipif(
-        ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
     def test_repr_multiindex_long(self) -> None:
         mindex_long = pd.MultiIndex.from_product(
             [["a", "b", "c", "d"], [1, 2, 3, 4, 5, 6, 7, 8]],
             names=("level_1", "level_2"),
         )
-        mda_long = DataArray(list(range(32)), coords={"x": mindex_long}, dims="x")
+        mda_long = DataArray(
+            list(range(32)), coords={"x": mindex_long}, dims="x"
+        ).astype(np.uint64)
         expected = dedent(
             """\
             <xarray.DataArray (x: 32)> Size: 256B
             array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
-                   17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
-            Coordinates:
-              * x        (x) object 256B MultiIndex
-              * level_1  (x) object 256B 'a' 'a' 'a' 'a' 'a' 'a' ... 'd' 'd' 'd' 'd' 'd' 'd'
-              * level_2  (x) int64 256B 1 2 3 4 5 6 7 8 1 2 3 4 ... 5 6 7 8 1 2 3 4 5 6 7 8"""
-        )
-        assert expected == repr(mda_long)
-
-    @pytest.mark.skipif(
-        not ON_WINDOWS,
-        reason="Default numpy's dtypes vary according to OS",
-    )
-    def test_repr_multiindex_long_windows(self) -> None:
-        mindex_long = pd.MultiIndex.from_product(
-            [["a", "b", "c", "d"], [1, 2, 3, 4, 5, 6, 7, 8]],
-            names=("level_1", "level_2"),
-        )
-        mda_long = DataArray(list(range(32)), coords={"x": mindex_long}, dims="x")
-        expected = dedent(
-            """\
-            <xarray.DataArray (x: 32)> Size: 128B
-            array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
-                   17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
+                   17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
+                  dtype=uint64)
             Coordinates:
               * x        (x) object 256B MultiIndex
               * level_1  (x) object 256B 'a' 'a' 'a' 'a' 'a' 'a' ... 'd' 'd' 'd' 'd' 'd' 'd'
diff --git a/xarray/tests/test_formatting.py b/xarray/tests/test_formatting.py
index 1ee6c86d..6923d26b 100644
--- a/xarray/tests/test_formatting.py
+++ b/xarray/tests/test_formatting.py
@@ -466,26 +466,24 @@ class TestFormatting:
         assert actual == expected
 
     def test_array_repr(self) -> None:
-        ds = xr.Dataset(coords={"foo": [1, 2, 3], "bar": [1, 2, 3]})
-        ds[(1, 2)] = xr.DataArray([0], dims="test")
+        ds = xr.Dataset(
+            coords={
+                "foo": np.array([1, 2, 3], dtype=np.uint64),
+                "bar": np.array([1, 2, 3], dtype=np.uint64),
+            }
+        )
+        ds[(1, 2)] = xr.DataArray(np.array([0], dtype=np.uint64), dims="test")
         ds_12 = ds[(1, 2)]
 
         # Test repr function behaves correctly:
         actual = formatting.array_repr(ds_12)
-        if ON_WINDOWS:
-            expected = dedent(
-                """\
-            <xarray.DataArray (1, 2) (test: 1)> Size: 4B
-            array([0])
-            Dimensions without coordinates: test"""
-            )
-        else:
-            expected = dedent(
-                """\
-            <xarray.DataArray (1, 2) (test: 1)> Size: 8B
-            array([0])
-            Dimensions without coordinates: test"""
-            )
+
+        expected = dedent(
+            """\
+        <xarray.DataArray (1, 2) (test: 1)> Size: 8B
+        array([0], dtype=uint64)
+        Dimensions without coordinates: test"""
+        )
 
         assert actual == expected
 
@@ -499,21 +497,12 @@ class TestFormatting:
 
         with xr.set_options(display_expand_data=False):
             actual = formatting.array_repr(ds[(1, 2)])
-            if ON_WINDOWS:
-                expected = dedent(
-                    """\
-                <xarray.DataArray (1, 2) (test: 1)> Size: 4B
-                0
-                Dimensions without coordinates: test"""
-                )
-
-            else:
-                expected = dedent(
-                    """\
-                <xarray.DataArray (1, 2) (test: 1)> Size: 8B
-                0
-                Dimensions without coordinates: test"""
-                )
+            expected = dedent(
+                """\
+            <xarray.DataArray (1, 2) (test: 1)> Size: 8B
+            0
+            Dimensions without coordinates: test"""
+            )
 
             assert actual == expected
 
@@ -682,15 +671,16 @@ def test__mapping_repr(display_max_rows, n_vars, n_attr) -> None:
     b = defchararray.add("attr_", np.arange(0, n_attr).astype(str))
     c = defchararray.add("coord", np.arange(0, n_vars).astype(str))
     attrs = {k: 2 for k in b}
-    coords = {_c: np.array([0, 1]) for _c in c}
+    coords = {_c: np.array([0, 1], dtype=np.uint64) for _c in c}
     data_vars = dict()
     for v, _c in zip(a, coords.items()):
         data_vars[v] = xr.DataArray(
             name=v,
-            data=np.array([3, 4]),
+            data=np.array([3, 4], dtype=np.uint64),
             dims=[_c[0]],
             coords=dict([_c]),
         )
+
     ds = xr.Dataset(data_vars)
     ds.attrs = attrs
 
@@ -727,7 +717,7 @@ def test__mapping_repr(display_max_rows, n_vars, n_attr) -> None:
         dims_values = formatting.dim_summary_limited(
             ds, col_width=col_width + 1, max_rows=display_max_rows
         )
-        expected_size = "640B" if ON_WINDOWS else "1kB"
+        expected_size = "1kB"
         expected = f"""\
 <xarray.Dataset> Size: {expected_size}
 {dims_start}({dims_values})
@@ -872,3 +862,181 @@ Coordinates:
   * foo      (foo) int16 2kB 0 1 2 3 4 5 6 ... 1194 1195 1196 1197 1198 1199
 """.strip()
     assert actual == expected
+
+
+def test_array_repr_dtypes():
+
+    # These dtypes are expected to be represented similarly
+    # on Ubuntu, macOS and Windows environments of the CI.
+    # Unsigned integer could be used as easy replacements
+    # for tests where the data-type does not matter,
+    # but the repr does, including the size
+    # (size of a int == size of an uint)
+
+    # Signed integer dtypes
+
+    ds = xr.DataArray(np.array([0], dtype="int8"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 1B
+array([0], dtype=int8)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="int16"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 2B
+array([0], dtype=int16)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    # Unsigned integer dtypes
+
+    ds = xr.DataArray(np.array([0], dtype="uint8"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 1B
+array([0], dtype=uint8)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="uint16"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 2B
+array([0], dtype=uint16)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="uint32"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 4B
+array([0], dtype=uint32)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="uint64"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0], dtype=uint64)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    # Float dtypes
+
+    ds = xr.DataArray(np.array([0.0]), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0.])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="float16"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 2B
+array([0.], dtype=float16)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="float32"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 4B
+array([0.], dtype=float32)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="float64"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0.])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+
+@pytest.mark.skipif(
+    ON_WINDOWS,
+    reason="Default numpy's dtypes vary according to OS",
+)
+def test_array_repr_dtypes_unix() -> None:
+
+    # Signed integer dtypes
+
+    ds = xr.DataArray(np.array([0]), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="int32"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 4B
+array([0], dtype=int32)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="int64"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+
+@pytest.mark.skipif(
+    not ON_WINDOWS,
+    reason="Default numpy's dtypes vary according to OS",
+)
+def test_array_repr_dtypes_on_windows() -> None:
+
+    # Integer dtypes
+
+    ds = xr.DataArray(np.array([0]), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 4B
+array([0])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="int32"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 4B
+array([0])
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
+
+    ds = xr.DataArray(np.array([0], dtype="int64"), dims="x")
+    actual = repr(ds)
+    expected = """
+<xarray.DataArray (x: 1)> Size: 8B
+array([0], dtype=int64)
+Dimensions without coordinates: x
+        """.strip()
+    assert actual == expected
diff --git a/xarray/tests/test_variable.py b/xarray/tests/test_variable.py
index 0c094635..eb71df0d 100644
--- a/xarray/tests/test_variable.py
+++ b/xarray/tests/test_variable.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import sys
 import warnings
 from abc import ABC
 from copy import copy, deepcopy
@@ -59,8 +58,6 @@ _PAD_XR_NP_ARGS = [
     [{"x": (3, 1), "z": 2}, ((3, 1), (0, 0), (2, 2))],
 ]
 
-ON_WINDOWS = sys.platform == "win32"
-
 
 @pytest.fixture
 def var():
@@ -1231,26 +1228,16 @@ class TestVariable(VariableSubclassobjects):
 
     def test_repr(self):
         v = Variable(["time", "x"], [[1, 2, 3], [4, 5, 6]], {"foo": "bar"})
-        if ON_WINDOWS:
-            expected = dedent(
-                """
-            <xarray.Variable (time: 2, x: 3)> Size: 24B
-            array([[1, 2, 3],
-                   [4, 5, 6]])
-            Attributes:
-                foo:      bar
-            """
-            ).strip()
-        else:
-            expected = dedent(
-                """
-            <xarray.Variable (time: 2, x: 3)> Size: 48B
-            array([[1, 2, 3],
-                   [4, 5, 6]])
-            Attributes:
-                foo:      bar
+        v = v.astype(np.uint64)
+        expected = dedent(
             """
-            ).strip()
+        <xarray.Variable (time: 2, x: 3)> Size: 48B
+        array([[1, 2, 3],
+               [4, 5, 6]], dtype=uint64)
+        Attributes:
+            foo:      bar
+        """
+        ).strip()
         assert expected == repr(v)
 
     def test_repr_lazy_data(self):