@@ -225,6 +225,20 @@ def test_to_offset_offset_input(offset):
225
225
("2U" , Microsecond (n = 2 )),
226
226
("us" , Microsecond (n = 1 )),
227
227
("2us" , Microsecond (n = 2 )),
228
+ # negative
229
+ ("-2M" , MonthEnd (n = - 2 )),
230
+ ("-2ME" , MonthEnd (n = - 2 )),
231
+ ("-2MS" , MonthBegin (n = - 2 )),
232
+ ("-2D" , Day (n = - 2 )),
233
+ ("-2H" , Hour (n = - 2 )),
234
+ ("-2h" , Hour (n = - 2 )),
235
+ ("-2T" , Minute (n = - 2 )),
236
+ ("-2min" , Minute (n = - 2 )),
237
+ ("-2S" , Second (n = - 2 )),
238
+ ("-2L" , Millisecond (n = - 2 )),
239
+ ("-2ms" , Millisecond (n = - 2 )),
240
+ ("-2U" , Microsecond (n = - 2 )),
241
+ ("-2us" , Microsecond (n = - 2 )),
228
242
],
229
243
ids = _id_func ,
230
244
)
@@ -239,7 +253,7 @@ def test_to_offset_sub_annual(freq, expected):
239
253
@pytest .mark .parametrize (
240
254
("month_int" , "month_label" ), list (_MONTH_ABBREVIATIONS .items ()) + [(0 , "" )]
241
255
)
242
- @pytest .mark .parametrize ("multiple" , [None , 2 ])
256
+ @pytest .mark .parametrize ("multiple" , [None , 2 , - 1 ])
243
257
@pytest .mark .parametrize ("offset_str" , ["AS" , "A" , "YS" , "Y" ])
244
258
@pytest .mark .filterwarnings ("ignore::FutureWarning" ) # Deprecation of "A" etc.
245
259
def test_to_offset_annual (month_label , month_int , multiple , offset_str ):
@@ -268,7 +282,7 @@ def test_to_offset_annual(month_label, month_int, multiple, offset_str):
268
282
@pytest .mark .parametrize (
269
283
("month_int" , "month_label" ), list (_MONTH_ABBREVIATIONS .items ()) + [(0 , "" )]
270
284
)
271
- @pytest .mark .parametrize ("multiple" , [None , 2 ])
285
+ @pytest .mark .parametrize ("multiple" , [None , 2 , - 1 ])
272
286
@pytest .mark .parametrize ("offset_str" , ["QS" , "Q" , "QE" ])
273
287
@pytest .mark .filterwarnings ("ignore::FutureWarning" ) # Deprecation of "Q" etc.
274
288
def test_to_offset_quarter (month_label , month_int , multiple , offset_str ):
@@ -403,6 +417,7 @@ def test_eq(a, b):
403
417
404
418
_MUL_TESTS = [
405
419
(BaseCFTimeOffset (), 3 , BaseCFTimeOffset (n = 3 )),
420
+ (BaseCFTimeOffset (), - 3 , BaseCFTimeOffset (n = - 3 )),
406
421
(YearEnd (), 3 , YearEnd (n = 3 )),
407
422
(YearBegin (), 3 , YearBegin (n = 3 )),
408
423
(QuarterEnd (), 3 , QuarterEnd (n = 3 )),
@@ -418,6 +433,7 @@ def test_eq(a, b):
418
433
(Microsecond (), 3 , Microsecond (n = 3 )),
419
434
(Day (), 0.5 , Hour (n = 12 )),
420
435
(Hour (), 0.5 , Minute (n = 30 )),
436
+ (Hour (), - 0.5 , Minute (n = - 30 )),
421
437
(Minute (), 0.5 , Second (n = 30 )),
422
438
(Second (), 0.5 , Millisecond (n = 500 )),
423
439
(Millisecond (), 0.5 , Microsecond (n = 500 )),
@@ -1162,6 +1178,15 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg
1162
1178
False ,
1163
1179
[(10 , 1 , 1 ), (8 , 1 , 1 ), (6 , 1 , 1 ), (4 , 1 , 1 )],
1164
1180
),
1181
+ (
1182
+ "0010" ,
1183
+ None ,
1184
+ 4 ,
1185
+ "-2YS" ,
1186
+ "both" ,
1187
+ False ,
1188
+ [(10 , 1 , 1 ), (8 , 1 , 1 ), (6 , 1 , 1 ), (4 , 1 , 1 )],
1189
+ ),
1165
1190
(
1166
1191
"0001-01-01" ,
1167
1192
"0001-01-04" ,
@@ -1180,6 +1205,24 @@ def test_rollback(calendar, offset, initial_date_args, partial_expected_date_arg
1180
1205
False ,
1181
1206
[(1 , 6 , 1 ), (2 , 3 , 1 ), (2 , 12 , 1 ), (3 , 9 , 1 )],
1182
1207
),
1208
+ (
1209
+ "0001-06-01" ,
1210
+ None ,
1211
+ 4 ,
1212
+ "-1MS" ,
1213
+ "both" ,
1214
+ False ,
1215
+ [(1 , 6 , 1 ), (1 , 5 , 1 ), (1 , 4 , 1 ), (1 , 3 , 1 )],
1216
+ ),
1217
+ (
1218
+ "0001-01-30" ,
1219
+ None ,
1220
+ 4 ,
1221
+ "-1D" ,
1222
+ "both" ,
1223
+ False ,
1224
+ [(1 , 1 , 30 ), (1 , 1 , 29 ), (1 , 1 , 28 ), (1 , 1 , 27 )],
1225
+ ),
1183
1226
]
1184
1227
1185
1228
@@ -1263,32 +1306,52 @@ def test_invalid_cftime_arg() -> None:
1263
1306
1264
1307
1265
1308
_CALENDAR_SPECIFIC_MONTH_END_TESTS = [
1266
- ("2ME" , " noleap" , [(2 , 28 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1267
- ("2ME" , " all_leap" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1268
- ("2ME" , " 360_day" , [(2 , 30 ), (4 , 30 ), (6 , 30 ), (8 , 30 ), (10 , 30 ), (12 , 30 )]),
1269
- ("2ME" , " standard" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1270
- ("2ME" , " gregorian" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1271
- ("2ME" , " julian" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1309
+ ("noleap" , [(2 , 28 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1310
+ ("all_leap" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1311
+ ("360_day" , [(2 , 30 ), (4 , 30 ), (6 , 30 ), (8 , 30 ), (10 , 30 ), (12 , 30 )]),
1312
+ ("standard" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1313
+ ("gregorian" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1314
+ ("julian" , [(2 , 29 ), (4 , 30 ), (6 , 30 ), (8 , 31 ), (10 , 31 ), (12 , 31 )]),
1272
1315
]
1273
1316
1274
1317
1275
1318
@pytest .mark .parametrize (
1276
- ("freq" , " calendar" , "expected_month_day" ),
1319
+ ("calendar" , "expected_month_day" ),
1277
1320
_CALENDAR_SPECIFIC_MONTH_END_TESTS ,
1278
1321
ids = _id_func ,
1279
1322
)
1280
1323
def test_calendar_specific_month_end (
1281
- freq : str , calendar : str , expected_month_day : list [tuple [int , int ]]
1324
+ calendar : str , expected_month_day : list [tuple [int , int ]]
1282
1325
) -> None :
1283
1326
year = 2000 # Use a leap-year to highlight calendar differences
1284
1327
result = cftime_range (
1285
- start = "2000-02" , end = "2001" , freq = freq , calendar = calendar
1328
+ start = "2000-02" , end = "2001" , freq = "2ME" , calendar = calendar
1286
1329
).values
1287
1330
date_type = get_date_type (calendar )
1288
1331
expected = [date_type (year , * args ) for args in expected_month_day ]
1289
1332
np .testing .assert_equal (result , expected )
1290
1333
1291
1334
1335
+ @pytest .mark .parametrize (
1336
+ ("calendar" , "expected_month_day" ),
1337
+ _CALENDAR_SPECIFIC_MONTH_END_TESTS ,
1338
+ ids = _id_func ,
1339
+ )
1340
+ def test_calendar_specific_month_end_negative_freq (
1341
+ calendar : str , expected_month_day : list [tuple [int , int ]]
1342
+ ) -> None :
1343
+ year = 2000 # Use a leap-year to highlight calendar differences
1344
+ result = cftime_range (
1345
+ start = "2000-12" ,
1346
+ end = "2000" ,
1347
+ freq = "-2ME" ,
1348
+ calendar = calendar ,
1349
+ ).values
1350
+ date_type = get_date_type (calendar )
1351
+ expected = [date_type (year , * args ) for args in expected_month_day [::- 1 ]]
1352
+ np .testing .assert_equal (result , expected )
1353
+
1354
+
1292
1355
@pytest .mark .parametrize (
1293
1356
("calendar" , "start" , "end" , "expected_number_of_days" ),
1294
1357
[
@@ -1376,10 +1439,6 @@ def test_date_range_errors() -> None:
1376
1439
)
1377
1440
1378
1441
1379
- @requires_cftime
1380
- @pytest .mark .xfail (
1381
- reason = "https://github.com/pydata/xarray/pull/8636#issuecomment-1902775153"
1382
- )
1383
1442
@pytest .mark .parametrize (
1384
1443
"start,freq,cal_src,cal_tgt,use_cftime,exp0,exp_pd" ,
1385
1444
[
@@ -1388,9 +1447,11 @@ def test_date_range_errors() -> None:
1388
1447
("2020-02-01" , "QE-DEC" , "noleap" , "gregorian" , True , "2020-03-31" , True ),
1389
1448
("2020-02-01" , "YS-FEB" , "noleap" , "gregorian" , True , "2020-02-01" , True ),
1390
1449
("2020-02-01" , "YE-FEB" , "noleap" , "gregorian" , True , "2020-02-29" , True ),
1450
+ ("2020-02-01" , "-1YE-FEB" , "noleap" , "gregorian" , True , "2020-02-29" , True ),
1391
1451
("2020-02-28" , "3h" , "all_leap" , "gregorian" , False , "2020-02-28" , True ),
1392
1452
("2020-03-30" , "ME" , "360_day" , "gregorian" , False , "2020-03-31" , True ),
1393
1453
("2020-03-31" , "ME" , "gregorian" , "360_day" , None , "2020-03-30" , False ),
1454
+ ("2020-03-31" , "-1ME" , "gregorian" , "360_day" , None , "2020-03-30" , False ),
1394
1455
],
1395
1456
)
1396
1457
def test_date_range_like (start , freq , cal_src , cal_tgt , use_cftime , exp0 , exp_pd ):
@@ -1541,3 +1602,15 @@ def test_to_offset_deprecation_warning(freq):
1541
1602
# Test for deprecations outlined in GitHub issue #8394
1542
1603
with pytest .warns (FutureWarning , match = "is deprecated" ):
1543
1604
to_offset (freq )
1605
+
1606
+
1607
+ @pytest .mark .filterwarnings ("ignore:Converting a CFTimeIndex with:" )
1608
+ @pytest .mark .parametrize ("start" , ("2000" , "2001" ))
1609
+ @pytest .mark .parametrize ("end" , ("2000" , "2001" ))
1610
+ @pytest .mark .parametrize ("freq" , ("MS" , "-1MS" , "YS" , "-1YS" , "M" , "-1M" , "Y" , "-1Y" ))
1611
+ def test_cftime_range_same_as_pandas (start , end , freq ):
1612
+ result = date_range (start , end , freq = freq , calendar = "standard" , use_cftime = True )
1613
+ result = result .to_datetimeindex ()
1614
+ expected = date_range (start , end , freq = freq , use_cftime = False )
1615
+
1616
+ np .testing .assert_array_equal (result , expected )
0 commit comments