Skip to content

Commit 5baca43

Browse files
committed
Fix Issues with FY5253 and variation=nearest w/ year end in Dec
1 parent 6b868b1 commit 5baca43

File tree

2 files changed

+122
-35
lines changed

2 files changed

+122
-35
lines changed

pandas/tseries/offsets.py

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,7 @@ def onOffset(self, dt):
977977
modMonth = (dt.month - self.startingMonth) % 3
978978
return BMonthEnd().onOffset(dt) and modMonth == 0
979979

980+
980981
class FY5253(CacheableOffset, DateOffset):
981982
"""
982983
Describes 52-53 week fiscal year. This is also known as a 4-4-5 calendar.
@@ -1035,8 +1036,9 @@ def __init__(self, n=1, **kwds):
10351036
raise ValueError('%s is not a valid variation' % self.variation)
10361037

10371038
if self.variation == "nearest":
1038-
self._rd_forward = relativedelta(weekday=weekday(self.weekday))
1039-
self._rd_backward = relativedelta(weekday=weekday(self.weekday)(-1))
1039+
weekday_offset = weekday(self.weekday)
1040+
self._rd_forward = relativedelta(weekday=weekday_offset)
1041+
self._rd_backward = relativedelta(weekday=weekday_offset(-1))
10401042
else:
10411043
self._offset_lwom = LastWeekOfMonth(n=1, weekday=self.weekday)
10421044

@@ -1047,31 +1049,64 @@ def isAnchored(self):
10471049

10481050
def onOffset(self, dt):
10491051
year_end = self.get_year_end(dt)
1050-
return year_end == dt
1052+
1053+
if self.variation == "nearest":
1054+
# We have to check the year end of "this" cal year AND the previous
1055+
return year_end == dt or \
1056+
self.get_year_end(dt - relativedelta(months=1)) == dt
1057+
else:
1058+
return year_end == dt
10511059

10521060
def apply(self, other):
10531061
n = self.n
1062+
prev_year = self.get_year_end(
1063+
datetime(other.year - 1, self.startingMonth, 1))
1064+
cur_year = self.get_year_end(
1065+
datetime(other.year, self.startingMonth, 1))
1066+
next_year = self.get_year_end(
1067+
datetime(other.year + 1, self.startingMonth, 1))
1068+
10541069
if n > 0:
1055-
year_end = self.get_year_end(other)
1056-
if other < year_end:
1057-
other = year_end
1070+
if other == prev_year:
1071+
year = other.year - 1
1072+
elif other == cur_year:
1073+
year = other.year
1074+
elif other == next_year:
1075+
year = other.year + 1
1076+
elif other < prev_year:
1077+
year = other.year - 1
1078+
n -= 1
1079+
elif other < cur_year:
1080+
year = other.year
10581081
n -= 1
1059-
elif other > year_end:
1060-
other = self.get_year_end(as_datetime(other) + relativedelta(years=1))
1082+
elif other < next_year:
1083+
year = other.year + 1
10611084
n -= 1
1085+
else:
1086+
assert False
10621087

1063-
return self.get_year_end(as_datetime(other) + relativedelta(years=n))
1088+
return self.get_year_end(datetime(year + n, self.startingMonth, 1))
10641089
else:
10651090
n = -n
1066-
year_end = self.get_year_end(other)
1067-
if other > year_end:
1068-
other = year_end
1091+
if other == prev_year:
1092+
year = other.year - 1
1093+
elif other == cur_year:
1094+
year = other.year
1095+
elif other == next_year:
1096+
year = other.year + 1
1097+
elif other > next_year:
1098+
year = other.year + 1
1099+
n -= 1
1100+
elif other > cur_year:
1101+
year = other.year
10691102
n -= 1
1070-
elif other < year_end:
1071-
other = self.get_year_end(as_datetime(other) + relativedelta(years=-1))
1103+
elif other > prev_year:
1104+
year = other.year - 1
10721105
n -= 1
1106+
else:
1107+
assert False
10731108

1074-
return self.get_year_end(as_datetime(other) + relativedelta(years=-n))
1109+
return self.get_year_end(datetime(year - n, self.startingMonth, 1))
10751110

10761111
def get_year_end(self, dt):
10771112
if self.variation == "nearest":
@@ -1127,21 +1162,23 @@ def _parse_suffix(cls, varion_code, startingMonth_code, weekday_code):
11271162
elif varion_code == "L":
11281163
variation = "last"
11291164
else:
1130-
raise ValueError("Unable to parse varion_code: %s" % (varion_code,))
1165+
raise ValueError(
1166+
"Unable to parse varion_code: %s" % (varion_code,))
11311167

11321168
startingMonth = _month_to_int[startingMonth_code]
11331169
weekday = _weekday_to_int[weekday_code]
11341170

11351171
return {
1136-
"weekday":weekday,
1137-
"startingMonth":startingMonth,
1138-
"variation":variation,
1172+
"weekday": weekday,
1173+
"startingMonth": startingMonth,
1174+
"variation": variation,
11391175
}
11401176

11411177
@classmethod
11421178
def _from_name(cls, *args):
11431179
return cls(**cls._parse_suffix(*args))
11441180

1181+
11451182
class FY5253Quarter(CacheableOffset, DateOffset):
11461183
"""
11471184
DateOffset increments between business quarter dates
@@ -1251,7 +1288,7 @@ def get_weeks(self, dt):
12511288
year_has_extra_week = self.year_has_extra_week(dt)
12521289

12531290
if year_has_extra_week:
1254-
ret[self.qtr_with_extra_week-1] = 14
1291+
ret[self.qtr_with_extra_week - 1] = 14
12551292

12561293
return ret
12571294

@@ -1263,7 +1300,7 @@ def year_has_extra_week(self, dt):
12631300
next_year_end = dt + self._offset
12641301
prev_year_end = dt - self._offset
12651302

1266-
week_in_year = (next_year_end - prev_year_end).days/7
1303+
week_in_year = (next_year_end - prev_year_end).days / 7
12671304

12681305
return week_in_year == 53
12691306

@@ -1285,11 +1322,14 @@ def onOffset(self, dt):
12851322
@property
12861323
def rule_code(self):
12871324
suffix = self._offset.get_rule_code_suffix()
1288-
return "%s-%s" %(self._prefix, "%s-%d" % (suffix, self.qtr_with_extra_week))
1325+
return "%s-%s" % (self._prefix,
1326+
"%s-%d" % (suffix, self.qtr_with_extra_week))
12891327

12901328
@classmethod
12911329
def _from_name(cls, *args):
1292-
return cls(**dict(FY5253._parse_suffix(*args[:-1]), qtr_with_extra_week=int(args[-1])))
1330+
return cls(**dict(FY5253._parse_suffix(*args[:-1]),
1331+
qtr_with_extra_week=int(args[-1])))
1332+
12931333

12941334
_int_to_month = {
12951335
1: 'JAN',

pandas/tseries/tests/test_offsets.py

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,10 +1304,25 @@ def test_get_year_end(self):
13041304
self.assertEqual(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.SUN).get_year_end(datetime(2013,1,1)), datetime(2013,9,1))
13051305
self.assertEqual(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.FRI).get_year_end(datetime(2013,1,1)), datetime(2013,8,30))
13061306

1307+
offset_n = FY5253(weekday=WeekDay.TUE, startingMonth=12,
1308+
variation="nearest")
1309+
self.assertEqual(offset_n.get_year_end(datetime(2012,1,1)), datetime(2013,1,1))
1310+
self.assertEqual(offset_n.get_year_end(datetime(2012,1,10)), datetime(2013,1,1))
1311+
1312+
self.assertEqual(offset_n.get_year_end(datetime(2013,1,1)), datetime(2013,12,31))
1313+
self.assertEqual(offset_n.get_year_end(datetime(2013,1,2)), datetime(2013,12,31))
1314+
self.assertEqual(offset_n.get_year_end(datetime(2013,1,3)), datetime(2013,12,31))
1315+
self.assertEqual(offset_n.get_year_end(datetime(2013,1,10)), datetime(2013,12,31))
1316+
1317+
JNJ = FY5253(n=1, startingMonth=12, weekday=6, variation="nearest")
1318+
self.assertEqual(JNJ.get_year_end(datetime(2006, 1, 1)), datetime(2006, 12, 31))
1319+
13071320
def test_onOffset(self):
13081321
offset_lom_aug_sat = makeFY5253NearestEndMonth(1, startingMonth=8, weekday=WeekDay.SAT)
13091322
offset_lom_aug_thu = makeFY5253NearestEndMonth(1, startingMonth=8, weekday=WeekDay.THU)
1310-
1323+
offset_n = FY5253(weekday=WeekDay.TUE, startingMonth=12,
1324+
variation="nearest")
1325+
13111326
tests = [
13121327
# From Uncyclopedia (see: http://en.wikipedia.org/wiki/4%E2%80%934%E2%80%935_calendar#Saturday_nearest_the_end_of_month)
13131328
# 2006-09-02 2006 September 2
@@ -1354,21 +1369,39 @@ def test_onOffset(self):
13541369
#From Micron, see: http://google.brand.edgar-online.com/?sym=MU&formtypeID=7
13551370
(offset_lom_aug_thu, datetime(2012, 8, 30), True),
13561371
(offset_lom_aug_thu, datetime(2011, 9, 1), True),
1357-
1372+
1373+
(offset_n, datetime(2012, 12, 31), False),
1374+
(offset_n, datetime(2013, 1, 1), True),
1375+
(offset_n, datetime(2013, 1, 2), False),
13581376
]
13591377

13601378
for offset, date, expected in tests:
13611379
assertOnOffset(offset, date, expected)
13621380

13631381
def test_apply(self):
1364-
date_seq_nem_8_sat = [datetime(2006, 9, 2), datetime(2007, 9, 1), datetime(2008, 8, 30), datetime(2009, 8, 29), datetime(2010, 8, 28), datetime(2011, 9, 3)]
1382+
date_seq_nem_8_sat = [datetime(2006, 9, 2), datetime(2007, 9, 1),
1383+
datetime(2008, 8, 30), datetime(2009, 8, 29),
1384+
datetime(2010, 8, 28), datetime(2011, 9, 3)]
1385+
1386+
JNJ = [datetime(2005, 1, 2), datetime(2006, 1, 1),
1387+
datetime(2006, 12, 31), datetime(2007, 12, 30),
1388+
datetime(2008, 12, 28), datetime(2010, 1, 3),
1389+
datetime(2011, 1, 2), datetime(2012, 1, 1),
1390+
datetime(2012, 12, 30)]
1391+
1392+
DEC_SAT = FY5253(n=-1, startingMonth=12, weekday=5, variation="nearest")
13651393

13661394
tests = [
1367-
(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.SAT), date_seq_nem_8_sat),
1368-
(makeFY5253NearestEndMonth(n=1, startingMonth=8, weekday=WeekDay.SAT), date_seq_nem_8_sat),
1369-
(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.SAT), [datetime(2006, 9, 1)] + date_seq_nem_8_sat),
1370-
(makeFY5253NearestEndMonth(n=1, startingMonth=8, weekday=WeekDay.SAT), [datetime(2006, 9, 3)] + date_seq_nem_8_sat[1:]),
1371-
(makeFY5253NearestEndMonth(n=-1, startingMonth=8, weekday=WeekDay.SAT), list(reversed(date_seq_nem_8_sat))),
1395+
(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.SAT), date_seq_nem_8_sat),
1396+
(makeFY5253NearestEndMonth(n=1, startingMonth=8, weekday=WeekDay.SAT), date_seq_nem_8_sat),
1397+
(makeFY5253NearestEndMonth(startingMonth=8, weekday=WeekDay.SAT), [datetime(2006, 9, 1)] + date_seq_nem_8_sat),
1398+
(makeFY5253NearestEndMonth(n=1, startingMonth=8, weekday=WeekDay.SAT), [datetime(2006, 9, 3)] + date_seq_nem_8_sat[1:]),
1399+
(makeFY5253NearestEndMonth(n=-1, startingMonth=8, weekday=WeekDay.SAT), list(reversed(date_seq_nem_8_sat))),
1400+
(makeFY5253NearestEndMonth(n=1, startingMonth=12, weekday=WeekDay.SUN), JNJ),
1401+
(makeFY5253NearestEndMonth(n=-1, startingMonth=12, weekday=WeekDay.SUN), list(reversed(JNJ))),
1402+
(makeFY5253NearestEndMonth(n=1, startingMonth=12, weekday=WeekDay.SUN), [datetime(2005,1,2), datetime(2006, 1, 1)]),
1403+
(makeFY5253NearestEndMonth(n=1, startingMonth=12, weekday=WeekDay.SUN), [datetime(2006,1,2), datetime(2006, 12, 31)]),
1404+
(DEC_SAT, [datetime(2013,1,15), datetime(2012,12,29)])
13721405
]
13731406
for test in tests:
13741407
offset, data = test
@@ -1512,16 +1545,22 @@ def test_year_has_extra_week(self):
15121545
self.assertTrue(makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=1).year_has_extra_week(datetime(1994, 4, 2)))
15131546

15141547
def test_get_weeks(self):
1515-
self.assertEqual(makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=1).get_weeks(datetime(2011, 4, 2)), [14, 13, 13, 13])
1516-
self.assertEqual(makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=4).get_weeks(datetime(2011, 4, 2)), [13, 13, 13, 14])
1517-
self.assertEqual(makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=1).get_weeks(datetime(2010, 12, 25)), [13, 13, 13, 13])
1548+
sat_dec_1 = makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=1)
1549+
sat_dec_4 = makeFY5253LastOfMonthQuarter(1, startingMonth=12, weekday=WeekDay.SAT, qtr_with_extra_week=4)
1550+
1551+
self.assertEqual(sat_dec_1.get_weeks(datetime(2011, 4, 2)), [14, 13, 13, 13])
1552+
self.assertEqual(sat_dec_4.get_weeks(datetime(2011, 4, 2)), [13, 13, 13, 14])
1553+
self.assertEqual(sat_dec_1.get_weeks(datetime(2010, 12, 25)), [13, 13, 13, 13])
15181554

15191555
class TestFY5253NearestEndMonthQuarter(TestBase):
15201556

15211557
def test_onOffset(self):
15221558

15231559
offset_nem_sat_aug_4 = makeFY5253NearestEndMonthQuarter(1, startingMonth=8, weekday=WeekDay.SAT, qtr_with_extra_week=4)
15241560
offset_nem_thu_aug_4 = makeFY5253NearestEndMonthQuarter(1, startingMonth=8, weekday=WeekDay.THU, qtr_with_extra_week=4)
1561+
offset_n = FY5253(weekday=WeekDay.TUE, startingMonth=12,
1562+
variation="nearest", qtr_with_extra_week=4)
1563+
15251564
tests = [
15261565
#From Uncyclopedia
15271566
(offset_nem_sat_aug_4, datetime(2006, 9, 2), True),
@@ -1563,6 +1602,9 @@ def test_onOffset(self):
15631602
(offset_nem_thu_aug_4, datetime(2007, 3, 1), True),
15641603
(offset_nem_thu_aug_4, datetime(1994, 3, 3), True),
15651604

1605+
(offset_n, datetime(2012, 12, 31), False),
1606+
(offset_n, datetime(2013, 1, 1), True),
1607+
(offset_n, datetime(2013, 1, 2), False)
15661608
]
15671609

15681610
for offset, date, expected in tests:
@@ -1580,7 +1622,12 @@ def test_offset(self):
15801622

15811623
assertEq(offset, datetime(2012, 5, 31), datetime(2012, 8, 30))
15821624
assertEq(offset, datetime(2012, 5, 30), datetime(2012, 5, 31))
1583-
1625+
1626+
offset2 = FY5253Quarter(weekday=5, startingMonth=12,
1627+
variation="last", qtr_with_extra_week=4)
1628+
1629+
assertEq(offset2, datetime(2013,1,15), datetime(2013, 3, 30))
1630+
15841631
class TestQuarterBegin(TestBase):
15851632

15861633
def test_repr(self):

0 commit comments

Comments
 (0)