Skip to content
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

deprecate cftime_range() in favor of date_range(use_cftime=True) #10024

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
fc954de
Deprecate cftime_range() and some refactoring
Feb 4, 2025
7affcc6
updated whats-new.rst
Feb 4, 2025
87f0cde
Add deprecation warning to cftime_range()
Feb 4, 2025
635aa2c
Merge branch 'main' into cftime_deprecation
Maddogghoek Feb 4, 2025
392c092
- Update cftime_range example to expect deprecation warning.
Feb 7, 2025
f6677eb
Merge branch 'cftime_deprecation' of https://github.com/maddogghoek/x…
Feb 7, 2025
5b162fc
Merge branch 'main' into cftime_deprecation
Maddogghoek Feb 7, 2025
dec7df5
Update whats-new.rst
Maddogghoek Feb 7, 2025
003edc8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 7, 2025
a072f1b
always import core.types.InclusionOptions
Feb 7, 2025
e0cfdaa
Merge branch 'cftime_deprecation' of https://github.com/maddogghoek/x…
Feb 7, 2025
9bf05e2
fix check of InclusiveOptions Literal check
Feb 7, 2025
49a249f
remove dead code
Feb 7, 2025
62df181
- Replace calls to cftime_range in core and unit tests
Feb 7, 2025
7938bb7
ignore deprecation warnings in test_cftime_offsets.py
Feb 7, 2025
7fbff45
- Update cftime_range doctest to ignore deprecation warning.
Feb 7, 2025
7324f25
- Fix test_cftime_or_date_range_invalid_inclusive_value for cases where
Feb 7, 2025
da4d9f5
update weather-climate.rst to use date_range rather than deprecated c…
Feb 7, 2025
d500e79
only check inclusive argument when not already type checked to avoid …
Feb 7, 2025
9b45bc8
skip invalid inclusion value if type checking is turned on
Feb 8, 2025
f68277e
remove unneeded ignore arg type comments
Feb 8, 2025
2fa10f6
s/Illegal/Invalid/ in error message for cftime_range
Maddogghoek Feb 11, 2025
7c4f77b
Merge branch 'main' into cftime_deprecation
Maddogghoek Feb 11, 2025
0e5ee06
Update doc/whats-new.rst
Maddogghoek Feb 14, 2025
7409c1b
remove accidentally included documenation artifact
Feb 14, 2025
882c53e
Merge branch 'main' into cftime_deprecation
Maddogghoek Feb 14, 2025
7765d91
update to use data_range instead of cftime_range
Maddogghoek Feb 18, 2025
eb87222
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 18, 2025
899f123
remove "bug fix" for #9886
Maddogghoek Feb 18, 2025
12a5c65
fix documentation typo
Maddogghoek Feb 18, 2025
6e7444a
update backwards compatibility section example to use xarray.core.uti…
Feb 18, 2025
3adf065
update cftime_range deprecation warning to use emit_user_level_warnin…
Feb 18, 2025
40222b8
add "linear" to internal generate_date_range* function names per code…
Feb 18, 2025
16ca397
clean up cftime_range docum
Maddogghoek Feb 18, 2025
0004584
replace deprecated cftime_range with date_range(use_cftime=True) for …
Feb 18, 2025
1076dab
Merge branch 'cftime_deprecation' of https://github.com/maddogghoek/x…
Feb 18, 2025
fec6a99
copy and update cftime_range() documentation notes to date_range() (#…
Feb 18, 2025
d611fbc
remove erroneous "stacklevel" argument from emit_user_level_warning c…
Feb 18, 2025
acfcaf7
fix test_invalid_date_range_cftime_inputs to catch expected exception…
Feb 19, 2025
a0acfd3
Merge branch 'main' into cftime_deprecation
Maddogghoek Feb 19, 2025
8bf8160
improve invalid input error message for date_range()
Maddogghoek Feb 21, 2025
773a7b2
reformat deprecation note
Maddogghoek Feb 21, 2025
3971ce0
Minor documentation improvement for date_range()
Maddogghoek Feb 21, 2025
8e43215
Fix date_range() documentation
Maddogghoek Feb 21, 2025
5043c85
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 21, 2025
4d75b40
remove long url from date_range() documentation
Maddogghoek Feb 21, 2025
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
5 changes: 4 additions & 1 deletion doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Breaking changes

Deprecations
~~~~~~~~~~~~

xarray.cftime_range deprecated in favor of xarray.data_range(use_cftime=true)

Bug fixes
~~~~~~~~~
Expand All @@ -42,6 +42,9 @@ Bug fixes
- Use mean of min/max years as offset in calculation of datetime64 mean
(:issue:`10019`, :pull:`10035`).
By `Kai Mühlbauer <https://github.com/kmuehlbauer>`_.
- Deprecate xr.cftime_range() in favor of xr.date_range(use_cftime=True)
(:issue:`9886`, :pull:`10024`).
By `Josh Kihm <https://github.com/maddogghoek>`_.


Documentation
Expand Down
142 changes: 99 additions & 43 deletions xarray/coding/cftime_offsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,15 +855,21 @@ def normalize_date(date):
return date.replace(hour=0, minute=0, second=0, microsecond=0)


def _maybe_normalize_date(date, normalize):
"""Round datetime down to midnight if normalize is True."""
if normalize:
return normalize_date(date)
else:
def _get_normalized_cfdate(date, calendar, normalize):
"""convert to cf datetime and round down to midnight if normalize."""
if date is None:
return date

cf_date = to_cftime_datetime(date, calendar)

return normalize_date(cf_date) if normalize else cf_date


def _generate_date_range():
pass

def _generate_linear_range(start, end, periods):

def _generate_date_range(start, end, periods):
"""Generate an equally-spaced sequence of cftime.datetime objects between
and including two dates (whose length equals the number of periods)."""
if TYPE_CHECKING:
Expand All @@ -880,9 +886,9 @@ def _generate_linear_range(start, end, periods):
)


def _generate_range(start, end, periods, offset):
def _generate_date_range_with_freq(start, end, periods, freq):
"""Generate a regular range of cftime.datetime objects with a
given time offset.
given frequency.

Adapted from pandas.tseries.offsets.generate_range (now at
pandas.core.arrays.datetimes._generate_range).
Expand All @@ -895,13 +901,15 @@ def _generate_range(start, end, periods, offset):
End of range
periods : int, or None
Number of elements in the sequence
offset : BaseCFTimeOffset
An offset class designed for working with cftime.datetime objects
freq: str
Step size bewtween cftime.datetime objects. Not None.

Returns
-------
A generator object
A generator object of cftime.datetime objects
"""
offset = to_offset(freq)

if start:
# From pandas GH 56147 / 56832 to account for negative direction and
# range bounds
Expand Down Expand Up @@ -949,7 +957,9 @@ def cftime_range(
inclusive: InclusiveOptions = "both",
calendar="standard",
) -> CFTimeIndex:
"""Return a fixed frequency CFTimeIndex.
"""DEPRECATED: use date_range(use_cftime=True) instead.

Return a fixed frequency CFTimeIndex.

Parameters
----------
Expand Down Expand Up @@ -1103,6 +1113,9 @@ def cftime_range(
objects associated with the specified calendar type, e.g.

>>> xr.cftime_range(start="2000", periods=6, freq="2MS", calendar="noleap")
Traceback (most recent call last):
...
DeprecationWarning: cftime_range() is deprecated, please use date_range(use_cftime=True) instead.
CFTimeIndex([2000-01-01 00:00:00, 2000-03-01 00:00:00, 2000-05-01 00:00:00,
2000-07-01 00:00:00, 2000-09-01 00:00:00, 2000-11-01 00:00:00],
dtype='object', length=6, calendar='noleap', freq='2MS')
Expand All @@ -1118,52 +1131,95 @@ def cftime_range(
--------
pandas.date_range
"""
warnings.warn(
"cftime_range() is deprecated, please use date_range(use_cftime=True) instead.",
DeprecationWarning,
stacklevel=2,
)

return date_range(
start=start,
end=end,
periods=periods,
freq=freq,
normalize=normalize,
name=name,
inclusive=inclusive,
calendar=calendar,
use_cftime=True,
)


def _cftime_range(
start=None,
end=None,
periods=None,
freq=None,
normalize=False,
name=None,
inclusive: InclusiveOptions = "both",
calendar="standard",
) -> CFTimeIndex:
"""Return a fixed frequency CFTimeIndex.

Parameters
----------
start : str or cftime.datetime, optional
Left bound for generating dates.
end : str or cftime.datetime, optional
Right bound for generating dates.
periods : int, optional
Number of periods to generate.
freq : str or None, default: "D"
Frequency strings can have multiples, e.g. "5h" and negative values, e.g. "-1D".
normalize : bool, default: False
Normalize start/end dates to midnight before generating date range.
name : str, default: None
Name of the resulting index
inclusive : {"both", "neither", "left", "right"}, default "both"
Include boundaries; whether to set each bound as closed or open.
calendar : str, default: "standard"
Calendar type for the datetimes.

Returns
-------
CFTimeIndex

Notes
-----
see cftime_range
"""
if freq is None and any(arg is None for arg in [periods, start, end]):
freq = "D"

# Adapted from pandas.core.indexes.datetimes._generate_range.
if count_not_none(start, end, periods, freq) != 3:
raise ValueError(
"Of the arguments 'start', 'end', 'periods', and 'freq', three "
"must be specified at a time."
"Illegal combination of the arguments 'start', 'end', 'periods', "
"and 'freq' specified."
)

if start is not None:
start = to_cftime_datetime(start, calendar)
start = _maybe_normalize_date(start, normalize)
if end is not None:
end = to_cftime_datetime(end, calendar)
end = _maybe_normalize_date(end, normalize)
start = _get_normalized_cfdate(start, calendar, normalize)
end = _get_normalized_cfdate(start, calendar, normalize)

if freq is None:
dates = _generate_linear_range(start, end, periods)
else:
offset = to_offset(freq)
dates = np.array(list(_generate_range(start, end, periods, offset)))

if inclusive == "neither":
left_closed = False
right_closed = False
elif inclusive == "left":
left_closed = True
right_closed = False
elif inclusive == "right":
left_closed = False
right_closed = True
elif inclusive == "both":
left_closed = True
right_closed = True
dates = _generate_date_range(start, end, periods)
else:
dates = np.array(
list(_generate_date_range_with_freq(start, end, periods, freq))
)

if inclusive not in InclusiveOptions:
raise ValueError(
f"Argument `inclusive` must be either 'both', 'neither', "
f"'left', 'right', or None. Got {inclusive}."
f"'left', or 'right'. Got {inclusive}."
)

if not left_closed and len(dates) and start is not None and dates[0] == start:
dates = dates[1:]
if not right_closed and len(dates) and end is not None and dates[-1] == end:
dates = dates[:-1]
if len(dates) and inclusive != "both":
if inclusive != "left" and dates[0] == start:
dates = dates[1:]
if inclusive != "right" and dates[-1] == end:
dates = dates[:-1]

return CFTimeIndex(dates, name=name)

Expand Down Expand Up @@ -1259,7 +1315,7 @@ def date_range(
f"Invalid calendar {calendar} for pandas DatetimeIndex, try using `use_cftime=True`."
)

return cftime_range(
return _cftime_range(
start=start,
end=end,
periods=periods,
Expand Down
18 changes: 12 additions & 6 deletions xarray/coding/cftimeindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class CFTimeIndex(pd.Index):

See Also
--------
cftime_range
date_range
"""

_data: np.ndarray
Expand Down Expand Up @@ -463,7 +463,7 @@ def shift( # type: ignore[override] # freq is typed Any, we are more precise
) -> Self:
"""Shift the CFTimeIndex a multiple of the given frequency.

See the documentation for :py:func:`~xarray.cftime_range` for a
See the documentation for :py:func:`~xarray.date_range` for a
complete listing of valid frequency strings.

Parameters
Expand All @@ -483,7 +483,7 @@ def shift( # type: ignore[override] # freq is typed Any, we are more precise

Examples
--------
>>> index = xr.cftime_range("2000", periods=1, freq="ME")
>>> index = xr.date_range("2000", periods=1, freq="ME", use_cftime=True)
>>> index
CFTimeIndex([2000-01-31 00:00:00],
dtype='object', length=1, calendar='standard', freq=None)
Expand Down Expand Up @@ -586,7 +586,9 @@ def to_datetimeindex(

Examples
--------
>>> times = xr.cftime_range("2000", periods=2, calendar="gregorian")
>>> times = xr.date_range(
... "2000", periods=2, calendar="gregorian", use_cftime=True
... )
>>> times
CFTimeIndex([2000-01-01 00:00:00, 2000-01-02 00:00:00],
dtype='object', length=2, calendar='standard', freq=None)
Expand Down Expand Up @@ -652,8 +654,12 @@ def strftime(self, date_format):

Examples
--------
>>> rng = xr.cftime_range(
... start="2000", periods=5, freq="2MS", calendar="noleap"
>>> rng = xr.date_range(
... start="2000",
... periods=5,
... freq="2MS",
... calendar="noleap",
... use_cftime=True,
... )
>>> rng.strftime("%B %d, %Y, %r")
Index(['January 01, 2000, 12:00:00 AM', 'March 01, 2000, 12:00:00 AM',
Expand Down
2 changes: 1 addition & 1 deletion xarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -5618,7 +5618,7 @@ def map_blocks(
... clim = gb.mean(dim="time")
... return gb - clim
...
>>> time = xr.cftime_range("1990-01", "1992-01", freq="ME")
>>> time = xr.date_range("1990-01", "1992-01", freq="ME", use_cftime=True)
>>> month = xr.DataArray(time.month, coords={"time": time}, dims=["time"])
>>> np.random.seed(123)
>>> array = xr.DataArray(
Expand Down
2 changes: 1 addition & 1 deletion xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -9024,7 +9024,7 @@ def map_blocks(
... clim = gb.mean(dim="time")
... return gb - clim
...
>>> time = xr.cftime_range("1990-01", "1992-01", freq="ME")
>>> time = xr.date_range("1990-01", "1992-01", freq="ME", use_cftime=True)
>>> month = xr.DataArray(time.month, coords={"time": time}, dims=["time"])
>>> np.random.seed(123)
>>> array = xr.DataArray(
Expand Down
2 changes: 1 addition & 1 deletion xarray/core/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def map_blocks(
... clim = gb.mean(dim="time")
... return gb - clim
...
>>> time = xr.cftime_range("1990-01", "1992-01", freq="ME")
>>> time = xr.date_range("1990-01", "1992-01", freq="ME", use_cftime=True)
>>> month = xr.DataArray(time.month, coords={"time": time}, dims=["time"])
>>> np.random.seed(123)
>>> array = xr.DataArray(
Expand Down
Loading