44
44
import re
45
45
from datetime import datetime , timedelta
46
46
from functools import partial
47
- from typing import ClassVar
47
+ from typing import TYPE_CHECKING , ClassVar
48
48
49
49
import numpy as np
50
50
import pandas as pd
57
57
format_cftime_datetime ,
58
58
)
59
59
from xarray .core .common import _contains_datetime_like_objects , is_np_datetime_like
60
- from xarray .core .pdcompat import count_not_none
60
+ from xarray .core .pdcompat import NoDefault , count_not_none , no_default
61
+ from xarray .core .utils import emit_user_level_warning
61
62
62
63
try :
63
64
import cftime
64
65
except ImportError :
65
66
cftime = None
66
67
67
68
69
+ if TYPE_CHECKING :
70
+ from xarray .core .types import InclusiveOptions , SideOptions
71
+
72
+
68
73
def get_date_type (calendar , use_cftime = True ):
69
74
"""Return the cftime date type for a given calendar name."""
70
75
if cftime is None :
@@ -849,14 +854,49 @@ def _generate_range(start, end, periods, offset):
849
854
current = next_date
850
855
851
856
857
+ def _translate_closed_to_inclusive (closed ):
858
+ """Follows code added in pandas #43504."""
859
+ emit_user_level_warning (
860
+ "Following pandas, the `closed` parameter is deprecated in "
861
+ "favor of the `inclusive` parameter, and will be removed in "
862
+ "a future version of xarray." ,
863
+ FutureWarning ,
864
+ )
865
+ if closed is None :
866
+ inclusive = "both"
867
+ elif closed in ("left" , "right" ):
868
+ inclusive = closed
869
+ else :
870
+ raise ValueError (
871
+ f"Argument `closed` must be either 'left', 'right', or None. "
872
+ f"Got { closed !r} ."
873
+ )
874
+ return inclusive
875
+
876
+
877
+ def _infer_inclusive (closed , inclusive ):
878
+ """Follows code added in pandas #43504."""
879
+ if closed is not no_default and inclusive is not None :
880
+ raise ValueError (
881
+ "Following pandas, deprecated argument `closed` cannot be "
882
+ "passed if argument `inclusive` is not None."
883
+ )
884
+ if closed is not no_default :
885
+ inclusive = _translate_closed_to_inclusive (closed )
886
+ elif inclusive is None :
887
+ inclusive = "both"
888
+ return inclusive
889
+
890
+
852
891
def cftime_range (
853
892
start = None ,
854
893
end = None ,
855
894
periods = None ,
856
895
freq = "D" ,
857
896
normalize = False ,
858
897
name = None ,
859
- closed = None ,
898
+ closed : NoDefault | SideOptions = no_default ,
899
+ inclusive : None | InclusiveOptions = None ,
860
900
calendar = "standard" ,
861
901
):
862
902
"""Return a fixed frequency CFTimeIndex.
@@ -875,9 +915,20 @@ def cftime_range(
875
915
Normalize start/end dates to midnight before generating date range.
876
916
name : str, default: None
877
917
Name of the resulting index
878
- closed : {"left", "right"} or None , default: None
918
+ closed : {None, "left", "right"}, default: "NO_DEFAULT"
879
919
Make the interval closed with respect to the given frequency to the
880
920
"left", "right", or both sides (None).
921
+
922
+ .. deprecated:: 2023.02.0
923
+ Following pandas, the ``closed`` parameter is deprecated in favor
924
+ of the ``inclusive`` parameter, and will be removed in a future
925
+ version of xarray.
926
+
927
+ inclusive : {None, "both", "neither", "left", "right"}, default None
928
+ Include boundaries; whether to set each bound as closed or open.
929
+
930
+ .. versionadded:: 2023.02.0
931
+
881
932
calendar : str, default: "standard"
882
933
Calendar type for the datetimes.
883
934
@@ -1047,18 +1098,25 @@ def cftime_range(
1047
1098
offset = to_offset (freq )
1048
1099
dates = np .array (list (_generate_range (start , end , periods , offset )))
1049
1100
1050
- left_closed = False
1051
- right_closed = False
1101
+ inclusive = _infer_inclusive (closed , inclusive )
1052
1102
1053
- if closed is None :
1103
+ if inclusive == "neither" :
1104
+ left_closed = False
1105
+ right_closed = False
1106
+ elif inclusive == "left" :
1054
1107
left_closed = True
1108
+ right_closed = False
1109
+ elif inclusive == "right" :
1110
+ left_closed = False
1055
1111
right_closed = True
1056
- elif closed == "left " :
1112
+ elif inclusive == "both " :
1057
1113
left_closed = True
1058
- elif closed == "right" :
1059
1114
right_closed = True
1060
1115
else :
1061
- raise ValueError ("Closed must be either 'left', 'right' or None" )
1116
+ raise ValueError (
1117
+ f"Argument `inclusive` must be either 'both', 'neither', "
1118
+ f"'left', 'right', or None. Got { inclusive } ."
1119
+ )
1062
1120
1063
1121
if not left_closed and len (dates ) and start is not None and dates [0 ] == start :
1064
1122
dates = dates [1 :]
@@ -1076,7 +1134,8 @@ def date_range(
1076
1134
tz = None ,
1077
1135
normalize = False ,
1078
1136
name = None ,
1079
- closed = None ,
1137
+ closed : NoDefault | SideOptions = no_default ,
1138
+ inclusive : None | InclusiveOptions = None ,
1080
1139
calendar = "standard" ,
1081
1140
use_cftime = None ,
1082
1141
):
@@ -1103,9 +1162,20 @@ def date_range(
1103
1162
Normalize start/end dates to midnight before generating date range.
1104
1163
name : str, default: None
1105
1164
Name of the resulting index
1106
- closed : {"left", "right"} or None , default: None
1165
+ closed : {None, "left", "right"}, default: "NO_DEFAULT"
1107
1166
Make the interval closed with respect to the given frequency to the
1108
1167
"left", "right", or both sides (None).
1168
+
1169
+ .. deprecated:: 2023.02.0
1170
+ Following pandas, the `closed` parameter is deprecated in favor
1171
+ of the `inclusive` parameter, and will be removed in a future
1172
+ version of xarray.
1173
+
1174
+ inclusive : {None, "both", "neither", "left", "right"}, default: None
1175
+ Include boundaries; whether to set each bound as closed or open.
1176
+
1177
+ .. versionadded:: 2023.02.0
1178
+
1109
1179
calendar : str, default: "standard"
1110
1180
Calendar type for the datetimes.
1111
1181
use_cftime : boolean, optional
@@ -1129,6 +1199,8 @@ def date_range(
1129
1199
if tz is not None :
1130
1200
use_cftime = False
1131
1201
1202
+ inclusive = _infer_inclusive (closed , inclusive )
1203
+
1132
1204
if _is_standard_calendar (calendar ) and use_cftime is not True :
1133
1205
try :
1134
1206
return pd .date_range (
@@ -1139,7 +1211,7 @@ def date_range(
1139
1211
tz = tz ,
1140
1212
normalize = normalize ,
1141
1213
name = name ,
1142
- closed = closed ,
1214
+ inclusive = inclusive ,
1143
1215
)
1144
1216
except pd .errors .OutOfBoundsDatetime as err :
1145
1217
if use_cftime is False :
@@ -1158,7 +1230,7 @@ def date_range(
1158
1230
freq = freq ,
1159
1231
normalize = normalize ,
1160
1232
name = name ,
1161
- closed = closed ,
1233
+ inclusive = inclusive ,
1162
1234
calendar = calendar ,
1163
1235
)
1164
1236
0 commit comments