diff --git a/cf_xarray/helpers.py b/cf_xarray/helpers.py index bda89ee7..91bac713 100644 --- a/cf_xarray/helpers.py +++ b/cf_xarray/helpers.py @@ -23,6 +23,11 @@ def _guess_bounds_1d(da, dim): ADDED_INDEX = True diff = da.diff(dim) + # Here we would need some escape based on a check of whether we were + # looking at cftime or not. It's not clear to me whether the fix should be + # here or further upstream though (either the casting shouldn't happen or + # numpy should be able to handle operations with cftime or we have some odd + # use case and need a workaround here). lower = da - diff / 2 upper = da + diff / 2 bounds = xr.concat([lower, upper], dim="bounds") diff --git a/cf_xarray/tests/test_accessor.py b/cf_xarray/tests/test_accessor.py index b585b0c2..4329e8d3 100644 --- a/cf_xarray/tests/test_accessor.py +++ b/cf_xarray/tests/test_accessor.py @@ -878,6 +878,40 @@ def test_add_bounds_nd_variable() -> None: ds.cf.add_bounds("z").cf.add_bounds("x") +def test_add_bounds_cftime() -> None: + ds = airds.copy(deep=False) + # Switch to cftime objects + time, time_units, time_calendar = xr.coding.times.encode_cf_datetime(ds["time"]) + time_cftime = xr.coding.times.decode_cf_datetime( + time, + units=time_units, + calendar=time_calendar, + use_cftime=True, + ) + + ds["time"] = ("time", time_cftime) + + da_time = ds["time"] + # Resorting to loop as something casts things to numpy types which causes explosions + time_diffs = np.array( + [da_time.values[i + 1] - da_time.values[i] for i in range(len(da_time) - 1)] + ) + + # `1:` indexing to mimic xarray's diff behaviour which drops the first value + lower = da_time.values[1:] - time_diffs / 2 + lower = np.concatenate([[lower[0] - time_diffs[0]], lower]) + upper = da_time.values[1:] + time_diffs / 2 + upper = np.concatenate([[upper[0] - time_diffs[0]], upper]) + + lower = xr.DataArray(lower, dims=["time"], coords=da_time.coords) + upper = xr.DataArray(upper, dims=["time"], coords=da_time.coords) + expected = xr.concat([lower, upper], dim="bounds").transpose(..., "bounds") + + ds.cf.add_bounds("time") + actual = ds.cf.add_bounds("time").time_bounds.reset_coords(drop=True) + xr.testing.assert_identical(actual, expected) + + def test_bounds() -> None: ds = airds.copy(deep=False).cf.add_bounds("lat") @@ -947,7 +981,7 @@ def test_bounds_to_vertices() -> None: with pytest.raises(ValueError): dsv = dsb.cf.bounds_to_vertices("T") - # Words on datetime arrays to + # Works on datetime arrays too dsb = dsb.cf.add_bounds("time") dsv = dsb.cf.bounds_to_vertices() assert "time_bounds" in dsv diff --git a/doc/contributing.rst b/doc/contributing.rst index 045ec2cc..0e2ece28 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -11,7 +11,26 @@ Contributing ------------ -This section will be expanded later. For now it lists docstrings for a number of internal variables, classes and functions. +This section will be expanded later. For now it tells you how to get setup to +run the tests and lists docstrings for a number of internal variables, classes +and functions. + +Running the tests +~~~~~~~~~~~~~~~~~ + +The simplest way is using conda/mamba (same as in the CI). Create yourself a +conda/mamba environment (e.g. ``mamba create -f ci/environment.yml``). +Activate your environment. Next install the local version of ``cf-xarray``, +``python -m pip install --no-deps -e .``. Now you are ready to run the tests +with ``pytest``. + +Pre-commit +~~~~~~~~~~ + +If you want the pre-commit hook too, after installing your environment as +above, simply install pre-commit (``pip install pre-commit``) and then run +``pre-commit install``. Now each time you commit you'll get the pre-commit +checks for free. Variables ~~~~~~~~~