@@ -751,7 +751,7 @@ def _emit_freq_deprecation_warning(deprecated_freq):
751751 emit_user_level_warning (message , FutureWarning )
752752
753753
754- def to_offset (freq ):
754+ def to_offset (freq , warn = True ):
755755 """Convert a frequency string to the appropriate subclass of
756756 BaseCFTimeOffset."""
757757 if isinstance (freq , BaseCFTimeOffset ):
@@ -763,7 +763,7 @@ def to_offset(freq):
763763 raise ValueError ("Invalid frequency string provided" )
764764
765765 freq = freq_data ["freq" ]
766- if freq in _DEPRECATED_FREQUENICES :
766+ if warn and freq in _DEPRECATED_FREQUENICES :
767767 _emit_freq_deprecation_warning (freq )
768768 multiples = freq_data ["multiple" ]
769769 multiples = 1 if multiples is None else int (multiples )
@@ -1229,7 +1229,8 @@ def date_range(
12291229 start = start ,
12301230 end = end ,
12311231 periods = periods ,
1232- freq = freq ,
1232+ # TODO remove translation once requiring pandas >= 2.2
1233+ freq = _new_to_legacy_freq (freq ),
12331234 tz = tz ,
12341235 normalize = normalize ,
12351236 name = name ,
@@ -1257,6 +1258,96 @@ def date_range(
12571258 )
12581259
12591260
1261+ def _new_to_legacy_freq (freq ):
1262+ # xarray will now always return "ME" and "QE" for MonthEnd and QuarterEnd
1263+ # frequencies, but older versions of pandas do not support these as
1264+ # frequency strings. Until xarray's minimum pandas version is 2.2 or above,
1265+ # we add logic to continue using the deprecated "M" and "Q" frequency
1266+ # strings in these circumstances.
1267+
1268+ # NOTE: other conversions ("h" -> "H", ..., "ns" -> "N") not required
1269+
1270+ # TODO: remove once requiring pandas >= 2.2
1271+ if not freq or Version (pd .__version__ ) >= Version ("2.2" ):
1272+ return freq
1273+
1274+ try :
1275+ freq_as_offset = to_offset (freq )
1276+ except ValueError :
1277+ # freq may be valid in pandas but not in xarray
1278+ return freq
1279+
1280+ if isinstance (freq_as_offset , MonthEnd ) and "ME" in freq :
1281+ freq = freq .replace ("ME" , "M" )
1282+ elif isinstance (freq_as_offset , QuarterEnd ) and "QE" in freq :
1283+ freq = freq .replace ("QE" , "Q" )
1284+ elif isinstance (freq_as_offset , YearBegin ) and "YS" in freq :
1285+ freq = freq .replace ("YS" , "AS" )
1286+ elif isinstance (freq_as_offset , YearEnd ):
1287+ # testing for "Y" is required as this was valid in xarray 2023.11 - 2024.01
1288+ if "Y-" in freq :
1289+ # Check for and replace "Y-" instead of just "Y" to prevent
1290+ # corrupting anchored offsets that contain "Y" in the month
1291+ # abbreviation, e.g. "Y-MAY" -> "A-MAY".
1292+ freq = freq .replace ("Y-" , "A-" )
1293+ elif "YE-" in freq :
1294+ freq = freq .replace ("YE-" , "A-" )
1295+ elif "A-" not in freq and freq .endswith ("Y" ):
1296+ freq = freq .replace ("Y" , "A" )
1297+ elif freq .endswith ("YE" ):
1298+ freq = freq .replace ("YE" , "A" )
1299+
1300+ return freq
1301+
1302+
1303+ def _legacy_to_new_freq (freq ):
1304+ # to avoid internal deprecation warnings when freq is determined using pandas < 2.2
1305+
1306+ # TODO: remove once requiring pandas >= 2.2
1307+
1308+ if not freq or Version (pd .__version__ ) >= Version ("2.2" ):
1309+ return freq
1310+
1311+ try :
1312+ freq_as_offset = to_offset (freq , warn = False )
1313+ except ValueError :
1314+ # freq may be valid in pandas but not in xarray
1315+ return freq
1316+
1317+ if isinstance (freq_as_offset , MonthEnd ) and "ME" not in freq :
1318+ freq = freq .replace ("M" , "ME" )
1319+ elif isinstance (freq_as_offset , QuarterEnd ) and "QE" not in freq :
1320+ freq = freq .replace ("Q" , "QE" )
1321+ elif isinstance (freq_as_offset , YearBegin ) and "YS" not in freq :
1322+ freq = freq .replace ("AS" , "YS" )
1323+ elif isinstance (freq_as_offset , YearEnd ):
1324+ if "A-" in freq :
1325+ # Check for and replace "A-" instead of just "A" to prevent
1326+ # corrupting anchored offsets that contain "Y" in the month
1327+ # abbreviation, e.g. "A-MAY" -> "YE-MAY".
1328+ freq = freq .replace ("A-" , "YE-" )
1329+ elif "Y-" in freq :
1330+ freq = freq .replace ("Y-" , "YE-" )
1331+ elif freq .endswith ("A" ):
1332+ # the "A-MAY" case is already handled above
1333+ freq = freq .replace ("A" , "YE" )
1334+ elif "YE" not in freq and freq .endswith ("Y" ):
1335+ # the "Y-MAY" case is already handled above
1336+ freq = freq .replace ("Y" , "YE" )
1337+ elif isinstance (freq_as_offset , Hour ):
1338+ freq = freq .replace ("H" , "h" )
1339+ elif isinstance (freq_as_offset , Minute ):
1340+ freq = freq .replace ("T" , "min" )
1341+ elif isinstance (freq_as_offset , Second ):
1342+ freq = freq .replace ("S" , "s" )
1343+ elif isinstance (freq_as_offset , Millisecond ):
1344+ freq = freq .replace ("L" , "ms" )
1345+ elif isinstance (freq_as_offset , Microsecond ):
1346+ freq = freq .replace ("U" , "us" )
1347+
1348+ return freq
1349+
1350+
12601351def date_range_like (source , calendar , use_cftime = None ):
12611352 """Generate a datetime array with the same frequency, start and end as
12621353 another one, but in a different calendar.
@@ -1301,21 +1392,8 @@ def date_range_like(source, calendar, use_cftime=None):
13011392 "`date_range_like` was unable to generate a range as the source frequency was not inferable."
13021393 )
13031394
1304- # xarray will now always return "ME" and "QE" for MonthEnd and QuarterEnd
1305- # frequencies, but older versions of pandas do not support these as
1306- # frequency strings. Until xarray's minimum pandas version is 2.2 or above,
1307- # we add logic to continue using the deprecated "M" and "Q" frequency
1308- # strings in these circumstances.
1309- if Version (pd .__version__ ) < Version ("2.2" ):
1310- freq_as_offset = to_offset (freq )
1311- if isinstance (freq_as_offset , MonthEnd ) and "ME" in freq :
1312- freq = freq .replace ("ME" , "M" )
1313- elif isinstance (freq_as_offset , QuarterEnd ) and "QE" in freq :
1314- freq = freq .replace ("QE" , "Q" )
1315- elif isinstance (freq_as_offset , YearBegin ) and "YS" in freq :
1316- freq = freq .replace ("YS" , "AS" )
1317- elif isinstance (freq_as_offset , YearEnd ) and "YE" in freq :
1318- freq = freq .replace ("YE" , "A" )
1395+ # TODO remove once requiring pandas >= 2.2
1396+ freq = _legacy_to_new_freq (freq )
13191397
13201398 use_cftime = _should_cftime_be_used (source , calendar , use_cftime )
13211399
0 commit comments