diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 73618782460..82355a6bda4 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -22,6 +22,11 @@ Breaking changes ~~~~~~~~~~~~~~~~ - Minimum cftime version is now 1.0.3. By `Deepak Cherian `_. +- All leftover support for dates from non-standard calendars through netcdftime, the + module included in versions of netCDF4 prior to 1.4 that eventually became the + cftime package, has been removed in favor of relying solely on the standalone + cftime package (:pull:`3450`). By `Spencer Clark + `_. New Features ~~~~~~~~~~~~ diff --git a/xarray/coding/times.py b/xarray/coding/times.py index 0174088064b..965ddd8f043 100644 --- a/xarray/coding/times.py +++ b/xarray/coding/times.py @@ -39,34 +39,6 @@ ) -def _import_cftime(): - """ - helper function handle the transition to netcdftime/cftime - as a stand-alone package - """ - try: - import cftime - except ImportError: - # in netCDF4 the num2date/date2num function are top-level api - try: - import netCDF4 as cftime - except ImportError: - raise ImportError("Failed to import cftime") - return cftime - - -def _require_standalone_cftime(): - """Raises an ImportError if the standalone cftime is not found""" - try: - import cftime # noqa: F401 - except ImportError: - raise ImportError( - "Decoding times with non-standard calendars " - "or outside the pandas.Timestamp-valid range " - "requires the standalone cftime package." - ) - - def _netcdf_to_numpy_timeunit(units): units = units.lower() if not units.endswith("s"): @@ -119,16 +91,11 @@ def _decode_cf_datetime_dtype(data, units, calendar, use_cftime): def _decode_datetime_with_cftime(num_dates, units, calendar): - cftime = _import_cftime() + import cftime - if cftime.__name__ == "cftime": - return np.asarray( - cftime.num2date(num_dates, units, calendar, only_use_cftime_datetimes=True) - ) - else: - # Must be using num2date from an old version of netCDF4 which - # does not have the only_use_cftime_datetimes option. - return np.asarray(cftime.num2date(num_dates, units, calendar)) + return np.asarray( + cftime.num2date(num_dates, units, calendar, only_use_cftime_datetimes=True) + ) def _decode_datetime_with_pandas(flat_num_dates, units, calendar): @@ -354,7 +321,7 @@ def _encode_datetime_with_cftime(dates, units, calendar): This method is more flexible than xarray's parsing using datetime64[ns] arrays but also slower because it loops over each element. """ - cftime = _import_cftime() + import cftime if np.issubdtype(dates.dtype, np.datetime64): # numpy's broken datetime conversion only works for us precision diff --git a/xarray/tests/__init__.py b/xarray/tests/__init__.py index f85a33f7a3c..6592360cdf2 100644 --- a/xarray/tests/__init__.py +++ b/xarray/tests/__init__.py @@ -78,10 +78,6 @@ def LooseVersion(vstring): requires_scipy_or_netCDF4 = pytest.mark.skipif( not has_scipy_or_netCDF4, reason="requires scipy or netCDF4" ) -has_cftime_or_netCDF4 = has_cftime or has_netCDF4 -requires_cftime_or_netCDF4 = pytest.mark.skipif( - not has_cftime_or_netCDF4, reason="requires cftime or netCDF4" -) try: import_seaborn() has_seaborn = True diff --git a/xarray/tests/test_accessor_dt.py b/xarray/tests/test_accessor_dt.py index 0058747db71..5fe5b8c3f59 100644 --- a/xarray/tests/test_accessor_dt.py +++ b/xarray/tests/test_accessor_dt.py @@ -7,10 +7,8 @@ from . import ( assert_array_equal, assert_equal, - has_cftime, - has_cftime_or_netCDF4, - has_dask, raises_regex, + requires_cftime, requires_dask, ) @@ -199,7 +197,7 @@ def times_3d(times): ) -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize( "field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"] ) @@ -217,7 +215,7 @@ def test_field_access(data, field): assert_equal(result, expected) -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime def test_cftime_strftime_access(data): """ compare cftime formatting against datetime formatting """ date_format = "%Y%m%d%H" @@ -232,8 +230,8 @@ def test_cftime_strftime_access(data): assert_equal(result, expected) -@pytest.mark.skipif(not has_dask, reason="dask not installed") -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime +@requires_dask @pytest.mark.parametrize( "field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"] ) @@ -254,8 +252,8 @@ def test_dask_field_access_1d(data, field): assert_equal(result.compute(), expected) -@pytest.mark.skipif(not has_dask, reason="dask not installed") -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime +@requires_dask @pytest.mark.parametrize( "field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"] ) @@ -286,7 +284,7 @@ def cftime_date_type(calendar): return _all_cftime_date_types()[calendar] -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime def test_seasons(cftime_date_type): dates = np.array([cftime_date_type(2000, month, 15) for month in range(1, 13)]) dates = xr.DataArray(dates) @@ -307,15 +305,3 @@ def test_seasons(cftime_date_type): seasons = xr.DataArray(seasons) assert_array_equal(seasons.values, dates.dt.season.values) - - -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime or netCDF4 not installed") -def test_dt_accessor_error_netCDF4(cftime_date_type): - da = xr.DataArray( - [cftime_date_type(1, 1, 1), cftime_date_type(2, 1, 1)], dims=["time"] - ) - if not has_cftime: - with pytest.raises(TypeError): - da.dt.month - else: - da.dt.month diff --git a/xarray/tests/test_cftimeindex.py b/xarray/tests/test_cftimeindex.py index e49dc72abdd..a8ee3c97042 100644 --- a/xarray/tests/test_cftimeindex.py +++ b/xarray/tests/test_cftimeindex.py @@ -15,7 +15,7 @@ ) from xarray.tests import assert_array_equal, assert_identical -from . import has_cftime, has_cftime_or_netCDF4, raises_regex, requires_cftime +from . import raises_regex, requires_cftime from .test_coding_times import ( _ALL_CALENDARS, _NON_STANDARD_CALENDARS, @@ -653,7 +653,7 @@ def test_indexing_in_dataframe_iloc(df, index): assert result.equals(expected) -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime def test_concat_cftimeindex(date_type): da1 = xr.DataArray( [1.0, 2.0], coords=[[date_type(1, 1, 1), date_type(1, 2, 1)]], dims=["time"] @@ -663,11 +663,7 @@ def test_concat_cftimeindex(date_type): ) da = xr.concat([da1, da2], dim="time") - if has_cftime: - assert isinstance(da.indexes["time"], CFTimeIndex) - else: - assert isinstance(da.indexes["time"], pd.Index) - assert not isinstance(da.indexes["time"], CFTimeIndex) + assert isinstance(da.indexes["time"], CFTimeIndex) @requires_cftime diff --git a/xarray/tests/test_coding_times.py b/xarray/tests/test_coding_times.py index 021d76e2b11..d012fb36c35 100644 --- a/xarray/tests/test_coding_times.py +++ b/xarray/tests/test_coding_times.py @@ -8,7 +8,6 @@ from xarray import DataArray, Dataset, Variable, coding, decode_cf from xarray.coding.times import ( - _import_cftime, cftime_to_nptime, decode_cf_datetime, encode_cf_datetime, @@ -19,15 +18,7 @@ from xarray.core.common import contains_cftime_datetimes from xarray.testing import assert_equal -from . import ( - arm_xfail, - assert_array_equal, - has_cftime, - has_cftime_or_netCDF4, - has_dask, - requires_cftime, - requires_cftime_or_netCDF4, -) +from . import arm_xfail, assert_array_equal, has_cftime, requires_cftime, requires_dask _NON_STANDARD_CALENDARS_SET = { "noleap", @@ -79,10 +70,8 @@ def _all_cftime_date_types(): - try: - import cftime - except ImportError: - import netcdftime as cftime + import cftime + return { "noleap": cftime.DatetimeNoLeap, "365_day": cftime.DatetimeNoLeap, @@ -95,16 +84,14 @@ def _all_cftime_date_types(): } -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize(["num_dates", "units", "calendar"], _CF_DATETIME_TESTS) def test_cf_datetime(num_dates, units, calendar): - cftime = _import_cftime() - if cftime.__name__ == "cftime": - expected = cftime.num2date( - num_dates, units, calendar, only_use_cftime_datetimes=True - ) - else: - expected = cftime.num2date(num_dates, units, calendar) + import cftime + + expected = cftime.num2date( + num_dates, units, calendar, only_use_cftime_datetimes=True + ) min_y = np.ravel(np.atleast_1d(expected))[np.nanargmin(num_dates)].year max_y = np.ravel(np.atleast_1d(expected))[np.nanargmax(num_dates)].year if min_y >= 1678 and max_y < 2262: @@ -138,15 +125,12 @@ def test_cf_datetime(num_dates, units, calendar): assert_array_equal(num_dates, np.around(encoded, 1)) -@requires_cftime_or_netCDF4 +@requires_cftime def test_decode_cf_datetime_overflow(): # checks for # https://github.com/pydata/pandas/issues/14068 # https://github.com/pydata/xarray/issues/975 - try: - from cftime import DatetimeGregorian - except ImportError: - from netcdftime import DatetimeGregorian + from cftime import DatetimeGregorian datetime = DatetimeGregorian units = "days since 2000-01-01 00:00:00" @@ -171,7 +155,7 @@ def test_decode_cf_datetime_non_standard_units(): assert_array_equal(actual, expected) -@requires_cftime_or_netCDF4 +@requires_cftime def test_decode_cf_datetime_non_iso_strings(): # datetime strings that are _almost_ ISO compliant but not quite, # but which cftime.num2date can still parse correctly @@ -190,10 +174,10 @@ def test_decode_cf_datetime_non_iso_strings(): assert (abs_diff <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _STANDARD_CALENDARS) def test_decode_standard_calendar_inside_timestamp_range(calendar): - cftime = _import_cftime() + import cftime units = "days since 0001-01-01" times = pd.date_range("2001-04-01-00", end="2001-04-30-23", freq="H") @@ -210,21 +194,18 @@ def test_decode_standard_calendar_inside_timestamp_range(calendar): assert (abs_diff <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _NON_STANDARD_CALENDARS) def test_decode_non_standard_calendar_inside_timestamp_range(calendar): - cftime = _import_cftime() + import cftime + units = "days since 0001-01-01" times = pd.date_range("2001-04-01-00", end="2001-04-30-23", freq="H") non_standard_time = cftime.date2num(times.to_pydatetime(), units, calendar=calendar) - if cftime.__name__ == "cftime": - expected = cftime.num2date( - non_standard_time, units, calendar=calendar, only_use_cftime_datetimes=True - ) - else: - expected = cftime.num2date(non_standard_time, units, calendar=calendar) - + expected = cftime.num2date( + non_standard_time, units, calendar=calendar, only_use_cftime_datetimes=True + ) expected_dtype = np.dtype("O") actual = coding.times.decode_cf_datetime( @@ -238,24 +219,19 @@ def test_decode_non_standard_calendar_inside_timestamp_range(calendar): assert (abs_diff <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _ALL_CALENDARS) def test_decode_dates_outside_timestamp_range(calendar): + import cftime from datetime import datetime - cftime = _import_cftime() - units = "days since 0001-01-01" times = [datetime(1, 4, 1, h) for h in range(1, 5)] time = cftime.date2num(times, units, calendar=calendar) - if cftime.__name__ == "cftime": - expected = cftime.num2date( - time, units, calendar=calendar, only_use_cftime_datetimes=True - ) - else: - expected = cftime.num2date(time, units, calendar=calendar) - + expected = cftime.num2date( + time, units, calendar=calendar, only_use_cftime_datetimes=True + ) expected_date_type = type(expected[0]) with warnings.catch_warnings(): @@ -269,7 +245,7 @@ def test_decode_dates_outside_timestamp_range(calendar): assert (abs_diff <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _STANDARD_CALENDARS) def test_decode_standard_calendar_single_element_inside_timestamp_range(calendar): units = "days since 0001-01-01" @@ -280,7 +256,7 @@ def test_decode_standard_calendar_single_element_inside_timestamp_range(calendar assert actual.dtype == np.dtype("M8[ns]") -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _NON_STANDARD_CALENDARS) def test_decode_non_standard_calendar_single_element_inside_timestamp_range(calendar): units = "days since 0001-01-01" @@ -291,10 +267,11 @@ def test_decode_non_standard_calendar_single_element_inside_timestamp_range(cale assert actual.dtype == np.dtype("O") -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _NON_STANDARD_CALENDARS) def test_decode_single_element_outside_timestamp_range(calendar): - cftime = _import_cftime() + import cftime + units = "days since 0001-01-01" for days in [1, 1470376]: for num_time in [days, [days], [[days]]]: @@ -304,20 +281,16 @@ def test_decode_single_element_outside_timestamp_range(calendar): num_time, units, calendar=calendar ) - if cftime.__name__ == "cftime": - expected = cftime.num2date( - days, units, calendar, only_use_cftime_datetimes=True - ) - else: - expected = cftime.num2date(days, units, calendar) - + expected = cftime.num2date( + days, units, calendar, only_use_cftime_datetimes=True + ) assert isinstance(actual.item(), type(expected)) -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _STANDARD_CALENDARS) def test_decode_standard_calendar_multidim_time_inside_timestamp_range(calendar): - cftime = _import_cftime() + import cftime units = "days since 0001-01-01" times1 = pd.date_range("2001-04-01", end="2001-04-05", freq="D") @@ -343,10 +316,10 @@ def test_decode_standard_calendar_multidim_time_inside_timestamp_range(calendar) assert (abs_diff2 <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _NON_STANDARD_CALENDARS) def test_decode_nonstandard_calendar_multidim_time_inside_timestamp_range(calendar): - cftime = _import_cftime() + import cftime units = "days since 0001-01-01" times1 = pd.date_range("2001-04-01", end="2001-04-05", freq="D") @@ -382,13 +355,12 @@ def test_decode_nonstandard_calendar_multidim_time_inside_timestamp_range(calend assert (abs_diff2 <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", _ALL_CALENDARS) def test_decode_multidim_time_outside_timestamp_range(calendar): + import cftime from datetime import datetime - cftime = _import_cftime() - units = "days since 0001-01-01" times1 = [datetime(1, 4, day) for day in range(1, 6)] times2 = [datetime(1, 5, day) for day in range(1, 6)] @@ -398,16 +370,8 @@ def test_decode_multidim_time_outside_timestamp_range(calendar): mdim_time[:, 0] = time1 mdim_time[:, 1] = time2 - if cftime.__name__ == "cftime": - expected1 = cftime.num2date( - time1, units, calendar, only_use_cftime_datetimes=True - ) - expected2 = cftime.num2date( - time2, units, calendar, only_use_cftime_datetimes=True - ) - else: - expected1 = cftime.num2date(time1, units, calendar) - expected2 = cftime.num2date(time2, units, calendar) + expected1 = cftime.num2date(time1, units, calendar, only_use_cftime_datetimes=True) + expected2 = cftime.num2date(time2, units, calendar, only_use_cftime_datetimes=True) with warnings.catch_warnings(): warnings.filterwarnings("ignore", "Unable to decode time axis") @@ -424,46 +388,38 @@ def test_decode_multidim_time_outside_timestamp_range(calendar): assert (abs_diff2 <= np.timedelta64(1, "s")).all() -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("calendar", ["360_day", "all_leap", "366_day"]) def test_decode_non_standard_calendar_single_element(calendar): - cftime = _import_cftime() + import cftime + units = "days since 0001-01-01" - try: - dt = cftime.netcdftime.datetime(2001, 2, 29) - except AttributeError: - # Must be using the standalone cftime library - dt = cftime.datetime(2001, 2, 29) + dt = cftime.datetime(2001, 2, 29) num_time = cftime.date2num(dt, units, calendar) actual = coding.times.decode_cf_datetime(num_time, units, calendar=calendar) - if cftime.__name__ == "cftime": - expected = np.asarray( - cftime.num2date(num_time, units, calendar, only_use_cftime_datetimes=True) - ) - else: - expected = np.asarray(cftime.num2date(num_time, units, calendar)) + expected = np.asarray( + cftime.num2date(num_time, units, calendar, only_use_cftime_datetimes=True) + ) assert actual.dtype == np.dtype("O") assert expected == actual -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime def test_decode_360_day_calendar(): - cftime = _import_cftime() + import cftime + calendar = "360_day" # ensure leap year doesn't matter for year in [2010, 2011, 2012, 2013, 2014]: units = f"days since {year}-01-01" num_times = np.arange(100) - if cftime.__name__ == "cftime": - expected = cftime.num2date( - num_times, units, calendar, only_use_cftime_datetimes=True - ) - else: - expected = cftime.num2date(num_times, units, calendar) + expected = cftime.num2date( + num_times, units, calendar, only_use_cftime_datetimes=True + ) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") @@ -477,7 +433,7 @@ def test_decode_360_day_calendar(): @arm_xfail -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize( ["num_dates", "units", "expected_list"], [ @@ -499,7 +455,7 @@ def test_cf_datetime_nan(num_dates, units, expected_list): assert_array_equal(expected, actual) -@requires_cftime_or_netCDF4 +@requires_cftime def test_decoded_cf_datetime_array_2d(): # regression test for GH1229 variable = Variable( @@ -548,7 +504,7 @@ def test_infer_datetime_units(dates, expected): ] -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize( "calendar", _NON_STANDARD_CALENDARS + ["gregorian", "proleptic_gregorian"] ) @@ -622,7 +578,7 @@ def test_infer_timedelta_units(deltas, expected): assert expected == coding.times.infer_timedelta_units(deltas) -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize( ["date_args", "expected"], [ @@ -649,7 +605,7 @@ def test_decode_cf(calendar): ds[v].attrs["units"] = "days since 2001-01-01" ds[v].attrs["calendar"] = calendar - if not has_cftime_or_netCDF4 and calendar not in _STANDARD_CALENDARS: + if not has_cftime and calendar not in _STANDARD_CALENDARS: with pytest.raises(ValueError): ds = decode_cf(ds) else: @@ -703,7 +659,7 @@ def test_decode_cf_time_bounds(): _update_bounds_attributes(ds.variables) -@requires_cftime_or_netCDF4 +@requires_cftime def test_encode_time_bounds(): time = pd.date_range("2000-01-16", periods=1) @@ -749,7 +705,7 @@ def calendar(request): @pytest.fixture() def times(calendar): - cftime = _import_cftime() + import cftime return cftime.num2date( np.arange(4), @@ -779,24 +735,24 @@ def times_3d(times): ) -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime def test_contains_cftime_datetimes_1d(data): assert contains_cftime_datetimes(data.time) -@pytest.mark.skipif(not has_dask, reason="dask not installed") -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime +@requires_dask def test_contains_cftime_datetimes_dask_1d(data): assert contains_cftime_datetimes(data.time.chunk()) -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime def test_contains_cftime_datetimes_3d(times_3d): assert contains_cftime_datetimes(times_3d) -@pytest.mark.skipif(not has_dask, reason="dask not installed") -@pytest.mark.skipif(not has_cftime, reason="cftime not installed") +@requires_cftime +@requires_dask def test_contains_cftime_datetimes_dask_3d(times_3d): assert contains_cftime_datetimes(times_3d.chunk()) @@ -806,13 +762,13 @@ def test_contains_cftime_datetimes_non_cftimes(non_cftime_data): assert not contains_cftime_datetimes(non_cftime_data) -@pytest.mark.skipif(not has_dask, reason="dask not installed") +@requires_dask @pytest.mark.parametrize("non_cftime_data", [DataArray([]), DataArray([1, 2])]) def test_contains_cftime_datetimes_non_cftimes_dask(non_cftime_data): assert not contains_cftime_datetimes(non_cftime_data.chunk()) -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime @pytest.mark.parametrize("shape", [(24,), (8, 3), (2, 4, 3)]) def test_encode_cf_datetime_overflow(shape): # Test for fix to GH 2272 @@ -837,7 +793,7 @@ def test_encode_cf_datetime_pandas_min(): assert calendar == expected_calendar -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime def test_time_units_with_timezone_roundtrip(calendar): # Regression test for GH 2649 expected_units = "days since 2000-01-01T00:00:00-05:00" diff --git a/xarray/tests/test_conventions.py b/xarray/tests/test_conventions.py index 42b2a679347..09002e252b4 100644 --- a/xarray/tests/test_conventions.py +++ b/xarray/tests/test_conventions.py @@ -21,7 +21,7 @@ from . import ( assert_array_equal, raises_regex, - requires_cftime_or_netCDF4, + requires_cftime, requires_dask, requires_netCDF4, ) @@ -81,7 +81,7 @@ def test_decode_cf_with_conflicting_fill_missing_value(): assert_identical(actual, expected) -@requires_cftime_or_netCDF4 +@requires_cftime class TestEncodeCFVariable: def test_incompatible_attributes(self): invalid_vars = [ @@ -144,7 +144,7 @@ def test_string_object_warning(self): assert_identical(original, encoded) -@requires_cftime_or_netCDF4 +@requires_cftime class TestDecodeCF: def test_dataset(self): original = Dataset( @@ -226,7 +226,7 @@ def test_invalid_time_units_raises_eagerly(self): with raises_regex(ValueError, "unable to decode time"): decode_cf(ds) - @requires_cftime_or_netCDF4 + @requires_cftime def test_dataset_repr_with_netcdf4_datetimes(self): # regression test for #347 attrs = {"units": "days since 0001-01-01", "calendar": "noleap"} @@ -239,7 +239,7 @@ def test_dataset_repr_with_netcdf4_datetimes(self): ds = decode_cf(Dataset({"time": ("time", [0, 1], attrs)})) assert "(time) datetime64[ns]" in repr(ds) - @requires_cftime_or_netCDF4 + @requires_cftime def test_decode_cf_datetime_transition_to_invalid(self): # manually create dataset with not-decoded date from datetime import datetime diff --git a/xarray/tests/test_utils.py b/xarray/tests/test_utils.py index 5bb9deaf240..af87b94393d 100644 --- a/xarray/tests/test_utils.py +++ b/xarray/tests/test_utils.py @@ -9,7 +9,7 @@ from xarray.core import duck_array_ops, utils from xarray.core.utils import either_dict_or_kwargs -from . import assert_array_equal, has_cftime, has_cftime_or_netCDF4, requires_dask +from . import assert_array_equal, requires_cftime, requires_dask from .test_coding_times import _all_cftime_date_types @@ -39,17 +39,12 @@ def test_safe_cast_to_index(): assert expected.dtype == actual.dtype -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime def test_safe_cast_to_index_cftimeindex(): date_types = _all_cftime_date_types() for date_type in date_types.values(): dates = [date_type(1, 1, day) for day in range(1, 20)] - - if has_cftime: - expected = CFTimeIndex(dates) - else: - expected = pd.Index(dates) - + expected = CFTimeIndex(dates) actual = utils.safe_cast_to_index(np.array(dates)) assert_array_equal(expected, actual) assert expected.dtype == actual.dtype @@ -57,7 +52,7 @@ def test_safe_cast_to_index_cftimeindex(): # Test that datetime.datetime objects are never used in a CFTimeIndex -@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed") +@requires_cftime def test_safe_cast_to_index_datetime_datetime(): dates = [datetime(1, 1, day) for day in range(1, 20)]