Skip to content

Commit cb5eef1

Browse files
spencerkclarkdcherian
authored andcommitted
Remove outdated code related to compatibility with netcdftime (#3450)
* Remove code leftover from the netcdftime -> cftime transition * Add a what's new note * black formatting * Add more detail to what's new note * More minor edits to what's new note
1 parent 74ca69a commit cb5eef1

8 files changed

+100
-199
lines changed

doc/whats-new.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Breaking changes
2222
~~~~~~~~~~~~~~~~
2323

2424
- Minimum cftime version is now 1.0.3. By `Deepak Cherian <https://github.com/dcherian>`_.
25+
- All leftover support for dates from non-standard calendars through netcdftime, the
26+
module included in versions of netCDF4 prior to 1.4 that eventually became the
27+
cftime package, has been removed in favor of relying solely on the standalone
28+
cftime package (:pull:`3450`). By `Spencer Clark
29+
<https://github.com/spencerkclark>`_.
2530

2631
New Features
2732
~~~~~~~~~~~~

xarray/coding/times.py

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -39,34 +39,6 @@
3939
)
4040

4141

42-
def _import_cftime():
43-
"""
44-
helper function handle the transition to netcdftime/cftime
45-
as a stand-alone package
46-
"""
47-
try:
48-
import cftime
49-
except ImportError:
50-
# in netCDF4 the num2date/date2num function are top-level api
51-
try:
52-
import netCDF4 as cftime
53-
except ImportError:
54-
raise ImportError("Failed to import cftime")
55-
return cftime
56-
57-
58-
def _require_standalone_cftime():
59-
"""Raises an ImportError if the standalone cftime is not found"""
60-
try:
61-
import cftime # noqa: F401
62-
except ImportError:
63-
raise ImportError(
64-
"Decoding times with non-standard calendars "
65-
"or outside the pandas.Timestamp-valid range "
66-
"requires the standalone cftime package."
67-
)
68-
69-
7042
def _netcdf_to_numpy_timeunit(units):
7143
units = units.lower()
7244
if not units.endswith("s"):
@@ -119,16 +91,11 @@ def _decode_cf_datetime_dtype(data, units, calendar, use_cftime):
11991

12092

12193
def _decode_datetime_with_cftime(num_dates, units, calendar):
122-
cftime = _import_cftime()
94+
import cftime
12395

124-
if cftime.__name__ == "cftime":
125-
return np.asarray(
126-
cftime.num2date(num_dates, units, calendar, only_use_cftime_datetimes=True)
127-
)
128-
else:
129-
# Must be using num2date from an old version of netCDF4 which
130-
# does not have the only_use_cftime_datetimes option.
131-
return np.asarray(cftime.num2date(num_dates, units, calendar))
96+
return np.asarray(
97+
cftime.num2date(num_dates, units, calendar, only_use_cftime_datetimes=True)
98+
)
13299

133100

134101
def _decode_datetime_with_pandas(flat_num_dates, units, calendar):
@@ -354,7 +321,7 @@ def _encode_datetime_with_cftime(dates, units, calendar):
354321
This method is more flexible than xarray's parsing using datetime64[ns]
355322
arrays but also slower because it loops over each element.
356323
"""
357-
cftime = _import_cftime()
324+
import cftime
358325

359326
if np.issubdtype(dates.dtype, np.datetime64):
360327
# numpy's broken datetime conversion only works for us precision

xarray/tests/__init__.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ def LooseVersion(vstring):
7878
requires_scipy_or_netCDF4 = pytest.mark.skipif(
7979
not has_scipy_or_netCDF4, reason="requires scipy or netCDF4"
8080
)
81-
has_cftime_or_netCDF4 = has_cftime or has_netCDF4
82-
requires_cftime_or_netCDF4 = pytest.mark.skipif(
83-
not has_cftime_or_netCDF4, reason="requires cftime or netCDF4"
84-
)
8581
try:
8682
import_seaborn()
8783
has_seaborn = True

xarray/tests/test_accessor_dt.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
from . import (
88
assert_array_equal,
99
assert_equal,
10-
has_cftime,
11-
has_cftime_or_netCDF4,
12-
has_dask,
1310
raises_regex,
11+
requires_cftime,
1412
requires_dask,
1513
)
1614

@@ -199,7 +197,7 @@ def times_3d(times):
199197
)
200198

201199

202-
@pytest.mark.skipif(not has_cftime, reason="cftime not installed")
200+
@requires_cftime
203201
@pytest.mark.parametrize(
204202
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
205203
)
@@ -217,7 +215,7 @@ def test_field_access(data, field):
217215
assert_equal(result, expected)
218216

219217

220-
@pytest.mark.skipif(not has_cftime, reason="cftime not installed")
218+
@requires_cftime
221219
def test_cftime_strftime_access(data):
222220
""" compare cftime formatting against datetime formatting """
223221
date_format = "%Y%m%d%H"
@@ -232,8 +230,8 @@ def test_cftime_strftime_access(data):
232230
assert_equal(result, expected)
233231

234232

235-
@pytest.mark.skipif(not has_dask, reason="dask not installed")
236-
@pytest.mark.skipif(not has_cftime, reason="cftime not installed")
233+
@requires_cftime
234+
@requires_dask
237235
@pytest.mark.parametrize(
238236
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
239237
)
@@ -254,8 +252,8 @@ def test_dask_field_access_1d(data, field):
254252
assert_equal(result.compute(), expected)
255253

256254

257-
@pytest.mark.skipif(not has_dask, reason="dask not installed")
258-
@pytest.mark.skipif(not has_cftime, reason="cftime not installed")
255+
@requires_cftime
256+
@requires_dask
259257
@pytest.mark.parametrize(
260258
"field", ["year", "month", "day", "hour", "dayofyear", "dayofweek"]
261259
)
@@ -286,7 +284,7 @@ def cftime_date_type(calendar):
286284
return _all_cftime_date_types()[calendar]
287285

288286

289-
@pytest.mark.skipif(not has_cftime, reason="cftime not installed")
287+
@requires_cftime
290288
def test_seasons(cftime_date_type):
291289
dates = np.array([cftime_date_type(2000, month, 15) for month in range(1, 13)])
292290
dates = xr.DataArray(dates)
@@ -307,15 +305,3 @@ def test_seasons(cftime_date_type):
307305
seasons = xr.DataArray(seasons)
308306

309307
assert_array_equal(seasons.values, dates.dt.season.values)
310-
311-
312-
@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime or netCDF4 not installed")
313-
def test_dt_accessor_error_netCDF4(cftime_date_type):
314-
da = xr.DataArray(
315-
[cftime_date_type(1, 1, 1), cftime_date_type(2, 1, 1)], dims=["time"]
316-
)
317-
if not has_cftime:
318-
with pytest.raises(TypeError):
319-
da.dt.month
320-
else:
321-
da.dt.month

xarray/tests/test_cftimeindex.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
)
1616
from xarray.tests import assert_array_equal, assert_identical
1717

18-
from . import has_cftime, has_cftime_or_netCDF4, raises_regex, requires_cftime
18+
from . import raises_regex, requires_cftime
1919
from .test_coding_times import (
2020
_ALL_CALENDARS,
2121
_NON_STANDARD_CALENDARS,
@@ -653,7 +653,7 @@ def test_indexing_in_dataframe_iloc(df, index):
653653
assert result.equals(expected)
654654

655655

656-
@pytest.mark.skipif(not has_cftime_or_netCDF4, reason="cftime not installed")
656+
@requires_cftime
657657
def test_concat_cftimeindex(date_type):
658658
da1 = xr.DataArray(
659659
[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):
663663
)
664664
da = xr.concat([da1, da2], dim="time")
665665

666-
if has_cftime:
667-
assert isinstance(da.indexes["time"], CFTimeIndex)
668-
else:
669-
assert isinstance(da.indexes["time"], pd.Index)
670-
assert not isinstance(da.indexes["time"], CFTimeIndex)
666+
assert isinstance(da.indexes["time"], CFTimeIndex)
671667

672668

673669
@requires_cftime

0 commit comments

Comments
 (0)