From f74cc3a8d73c29090ee1f97f6b16da7539356241 Mon Sep 17 00:00:00 2001 From: Spencer Clark Date: Sat, 28 Dec 2024 16:42:08 -0500 Subject: [PATCH] Add hypothesis strategy for cftime objects --- properties/test_encode_decode.py | 13 ++++++++---- xarray/testing/strategies.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/properties/test_encode_decode.py b/properties/test_encode_decode.py index 211c33ff6b5..374c6b33b93 100644 --- a/properties/test_encode_decode.py +++ b/properties/test_encode_decode.py @@ -5,6 +5,8 @@ """ +import warnings + import pytest pytest.importorskip("hypothesis") @@ -17,7 +19,8 @@ import xarray as xr from xarray.coding.times import _parse_iso8601_without_reso -from xarray.testing.strategies import variables +from xarray.testing.strategies import CFTimeStrategyISO8601, variables +from xarray.tests import requires_cftime @pytest.mark.slow @@ -47,8 +50,10 @@ def test_CFScaleOffset_coder_roundtrip(original) -> None: xr.testing.assert_identical(original, roundtripped) -# TODO: add cftime.datetime -@given(dt=st.datetimes()) +@requires_cftime +@given(dt=st.datetimes() | CFTimeStrategyISO8601()) def test_iso8601_decode(dt): iso = dt.isoformat() - assert dt == _parse_iso8601_without_reso(type(dt), iso) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*date/calendar/year zero.*") + assert dt == _parse_iso8601_without_reso(type(dt), iso) diff --git a/xarray/testing/strategies.py b/xarray/testing/strategies.py index cfa226d991c..340988f8036 100644 --- a/xarray/testing/strategies.py +++ b/xarray/testing/strategies.py @@ -1,3 +1,5 @@ +import datetime +import warnings from collections.abc import Hashable, Iterable, Mapping, Sequence from typing import TYPE_CHECKING, Any, Protocol, overload @@ -8,6 +10,7 @@ import xarray as xr from xarray.core.types import T_DuckArray from xarray.core.utils import attempt_import +from xarray.tests.test_coding_times import _all_cftime_date_types if TYPE_CHECKING: from xarray.core.types import _DTypeLikeNested, _ShapeLike @@ -473,3 +476,34 @@ def unique_subset_of( return ( {k: objs[k] for k in subset_keys} if isinstance(objs, Mapping) else subset_keys ) + + +class CFTimeStategy(st.SearchStrategy): + def __init__(self, min_value, max_value): + self.min_value = min_value + self.max_value = max_value + + def do_draw(self, data): + unit_microsecond = datetime.timedelta(microseconds=1) + timespan_microseconds = (self.max_value - self.min_value) // unit_microsecond + result = data.draw_integer(0, timespan_microseconds) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*date/calendar/year zero.*") + return self.min_value + datetime.timedelta(microseconds=result) + + +class CFTimeStrategyISO8601(st.SearchStrategy): + def __init__(self): + self.date_types = _all_cftime_date_types() + self.calendars = list(self.date_types) + + def do_draw(self, data): + calendar = data.draw(st.sampled_from(self.calendars)) + date_type = self.date_types[calendar] + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=".*date/calendar/year zero.*") + daysinmonth = date_type(99999, 12, 1).daysinmonth + min_value = date_type(-99999, 1, 1) + max_value = date_type(99999, 12, daysinmonth, 23, 59, 59, 999999) + strategy = CFTimeStategy(min_value, max_value) + return strategy.do_draw(data)