Skip to content

Improve warning message and tests for timedelta decoding #10508

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions xarray/coding/times.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,20 +1517,20 @@ def decode(self, variable: Variable, name: T_Name = None) -> Variable:
time_unit = self.time_unit
else:
if self._emit_decode_timedelta_future_warning:
var_string = f"the variable {name!r}" if name else ""
emit_user_level_warning(
"In a future version, xarray will not decode "
"timedelta values based on the presence of a "
"timedelta-like units attribute by default. Instead "
"it will rely on the presence of a timedelta64 dtype "
"attribute, which is now xarray's default way of "
"encoding timedelta64 values. To continue decoding "
"timedeltas based on the presence of a timedelta-like "
"units attribute, users will need to explicitly "
"opt-in by passing True or "
"CFTimedeltaCoder(decode_via_units=True) to "
"decode_timedelta. To silence this warning, set "
"decode_timedelta to True, False, or a "
"'CFTimedeltaCoder' instance.",
f"{var_string} into a timedelta64 dtype based on the "
"presence of a timedelta-like 'units' attribute by "
"default. Instead it will rely on the presence of a "
"timedelta64 'dtype' attribute, which is now xarray's "
"default way of encoding timedelta64 values.\n"
"To continue decoding into a timedelta64 dtype, either "
"set `decode_timedelta=True` when opening this "
"dataset, or add the attribute "
"`dtype='timedelta64[ns]'` to this variable on disk.\n"
"To opt-in to future behavior, set "
"`decode_timedelta=False`.",
FutureWarning,
)
if self.time_unit is None:
Expand Down
30 changes: 22 additions & 8 deletions xarray/tests/test_coding_times.py
Original file line number Diff line number Diff line change
Expand Up @@ -1867,7 +1867,10 @@ def test_decode_timedelta_via_units(
var = Variable(["time"], timedeltas, encoding=attrs)
encoded = Variable(["time"], np.array([0, 1, 2]), attrs=attrs)
if warns:
with pytest.warns(FutureWarning, match="decode_timedelta"):
with pytest.warns(
FutureWarning,
match="xarray will not decode the variable 'foo' into a timedelta64 dtype",
):
decoded = conventions.decode_cf_variable(
"foo",
encoded,
Expand All @@ -1886,45 +1889,56 @@ def test_decode_timedelta_via_units(


_DECODE_TIMEDELTA_VIA_DTYPE_TESTS = {
"default": (True, None, np.dtype("timedelta64[ns]")),
"decode_timedelta=False": (True, False, np.dtype("int64")),
"decode_timedelta=True": (True, True, np.dtype("timedelta64[ns]")),
"default": (True, None, "ns", np.dtype("timedelta64[ns]")),
"decode_timedelta=False": (True, False, "ns", np.dtype("int64")),
"decode_timedelta=True": (True, True, "ns", np.dtype("timedelta64[ns]")),
"use-original-units": (True, True, "s", np.dtype("timedelta64[s]")),
"inherit-time_unit-from-decode_times": (
CFDatetimeCoder(time_unit="s"),
None,
"ns",
np.dtype("timedelta64[s]"),
),
"set-time_unit-via-CFTimedeltaCoder-decode_times=True": (
True,
CFTimedeltaCoder(time_unit="s"),
"ns",
np.dtype("timedelta64[s]"),
),
"set-time_unit-via-CFTimedeltaCoder-decode_times=False": (
False,
CFTimedeltaCoder(time_unit="s"),
"ns",
np.dtype("timedelta64[s]"),
),
"override-time_unit-from-decode_times": (
CFDatetimeCoder(time_unit="ns"),
CFTimedeltaCoder(time_unit="s"),
"ns",
np.dtype("timedelta64[s]"),
),
"decode-different-units": (
True,
CFTimedeltaCoder(time_unit="us"),
"s",
np.dtype("timedelta64[us]"),
),
}


@pytest.mark.parametrize(
("decode_times", "decode_timedelta", "expected_dtype"),
("decode_times", "decode_timedelta", "original_unit", "expected_dtype"),
list(_DECODE_TIMEDELTA_VIA_DTYPE_TESTS.values()),
ids=list(_DECODE_TIMEDELTA_VIA_DTYPE_TESTS.keys()),
)
def test_decode_timedelta_via_dtype(
decode_times, decode_timedelta, expected_dtype
decode_times, decode_timedelta, original_unit, expected_dtype
) -> None:
timedeltas = pd.timedelta_range(0, freq="D", periods=3)
timedeltas = pd.timedelta_range(0, freq="D", periods=3, unit=original_unit)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not fully understand why mypy complains here, but we have worked around it this way in the past:

Suggested change
timedeltas = pd.timedelta_range(0, freq="D", periods=3, unit=original_unit)
timedeltas = pd.timedelta_range(0, freq="D", periods=3, unit=original_unit) # type: ignore[call-arg]

encoding = {"units": "days"}
var = Variable(["time"], timedeltas, encoding=encoding)
encoded = conventions.encode_cf_variable(var)
assert encoded.attrs["dtype"] == "timedelta64[ns]"
assert encoded.attrs["dtype"] == f"timedelta64[{original_unit}]"
assert encoded.attrs["units"] == encoding["units"]
decoded = conventions.decode_cf_variable(
"foo", encoded, decode_times=decode_times, decode_timedelta=decode_timedelta
Expand Down
Loading