Skip to content

Commit b55c790

Browse files
committed
Merge pull request #5004 from cancan101/retail
ENH: Support for "52–53-week fiscal year" / "4–4–5 calendar" and LastWeekOfMonth DateOffset
2 parents d25aaff + de05636 commit b55c790

11 files changed

+945
-55
lines changed

doc/source/release.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ New features
6262
- Auto-detect field widths in read_fwf when unspecified (:issue:`4488`)
6363
- ``to_csv()`` now outputs datetime objects according to a specified format string
6464
via the ``date_format`` keyword (:issue:`4313`)
65-
65+
- Added ``LastWeekOfMonth`` DateOffset (:issue:`4637`)
66+
- Added ``FY5253``, and ``FY5253Quarter`` DateOffsets (:issue:`4511`)
6667

6768
Experimental Features
6869
~~~~~~~~~~~~~~~~~~~~~

doc/source/timeseries.rst

+3
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ frequency increment. Specific offset logic like "month", "business day", or
420420
CDay, "custom business day (experimental)"
421421
Week, "one week, optionally anchored on a day of the week"
422422
WeekOfMonth, "the x-th day of the y-th week of each month"
423+
LastWeekOfMonth, "the x-th day of the last week of each month"
423424
MonthEnd, "calendar month end"
424425
MonthBegin, "calendar month begin"
425426
BMonthEnd, "business month end"
@@ -428,10 +429,12 @@ frequency increment. Specific offset logic like "month", "business day", or
428429
QuarterBegin, "calendar quarter begin"
429430
BQuarterEnd, "business quarter end"
430431
BQuarterBegin, "business quarter begin"
432+
FY5253Quarter, "retail (aka 52-53 week) quarter"
431433
YearEnd, "calendar year end"
432434
YearBegin, "calendar year begin"
433435
BYearEnd, "business year end"
434436
BYearBegin, "business year begin"
437+
FY5253, "retail (aka 52-53 week) year"
435438
Hour, "one hour"
436439
Minute, "one minute"
437440
Second, "one second"

doc/source/v0.13.0.txt

+2
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,8 @@ Enhancements
511511
- Python csv parser now supports usecols (:issue:`4335`)
512512

513513
- DataFrame has a new ``interpolate`` method, similar to Series (:issue:`4434`, :issue:`1892`)
514+
- Added ``LastWeekOfMonth`` DateOffset (:issue:`4637`)
515+
- Added ``FY5253``, and ``FY5253Quarter`` DateOffsets (:issue:`4511`)
514516

515517

516518
.. ipython:: python

pandas/tseries/frequencies.py

+27-19
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def get_freq_code(freqstr):
9595
code = _period_str_to_code(freqstr[0])
9696
stride = freqstr[1]
9797
except:
98+
if com.is_integer(freqstr[1]):
99+
raise
98100
code = _period_str_to_code(freqstr[1])
99101
stride = freqstr[0]
100102
return code, stride
@@ -227,10 +229,10 @@ def get_period_alias(offset_str):
227229
'us': 'U'
228230
}
229231

232+
#TODO: Can this be killed?
230233
for _i, _weekday in enumerate(['MON', 'TUE', 'WED', 'THU', 'FRI']):
231234
for _iweek in range(4):
232235
_name = 'WOM-%d%s' % (_iweek + 1, _weekday)
233-
_offset_map[_name] = offsets.WeekOfMonth(week=_iweek, weekday=_i)
234236
_rule_aliases[_name.replace('-', '@')] = _name
235237

236238
# Note that _rule_aliases is not 1:1 (d[BA]==d[A@DEC]), and so traversal
@@ -301,7 +303,7 @@ def to_offset(freqstr):
301303

302304

303305
# hack to handle WOM-1MON
304-
opattern = re.compile(r'([\-]?\d*)\s*([A-Za-z]+([\-@]\d*[A-Za-z]+)?)')
306+
opattern = re.compile(r'([\-]?\d*)\s*([A-Za-z]+([\-@][\dA-Za-z\-]+)?)')
305307

306308

307309
def _base_and_stride(freqstr):
@@ -356,16 +358,16 @@ def get_offset(name):
356358
else:
357359
if name in _rule_aliases:
358360
name = _rule_aliases[name]
359-
try:
360-
if name not in _offset_map:
361+
362+
if name not in _offset_map:
363+
try:
361364
# generate and cache offset
362365
offset = _make_offset(name)
363-
_offset_map[name] = offset
364-
return _offset_map[name]
365-
except (ValueError, TypeError, KeyError):
366-
# bad prefix or suffix
367-
pass
368-
raise ValueError('Bad rule name requested: %s.' % name)
366+
except (ValueError, TypeError, KeyError):
367+
# bad prefix or suffix
368+
raise ValueError('Bad rule name requested: %s.' % name)
369+
_offset_map[name] = offset
370+
return _offset_map[name]
369371

370372

371373
getOffset = get_offset
@@ -401,9 +403,6 @@ def get_legacy_offset_name(offset):
401403
name = offset.name
402404
return _legacy_reverse_map.get(name, name)
403405

404-
get_offset_name = get_offset_name
405-
406-
407406
def get_standard_freq(freq):
408407
"""
409408
Return the standardized frequency string
@@ -621,8 +620,12 @@ def _period_str_to_code(freqstr):
621620
try:
622621
freqstr = freqstr.upper()
623622
return _period_code_map[freqstr]
624-
except:
625-
alias = _period_alias_dict[freqstr]
623+
except KeyError:
624+
try:
625+
alias = _period_alias_dict[freqstr]
626+
except KeyError:
627+
raise ValueError("Unknown freqstr: %s" % freqstr)
628+
626629
return _period_code_map[alias]
627630

628631

@@ -839,16 +842,21 @@ def _get_monthly_rule(self):
839842
'ce': 'M', 'be': 'BM'}.get(pos_check)
840843

841844
def _get_wom_rule(self):
842-
wdiffs = unique(np.diff(self.index.week))
843-
if not lib.ismember(wdiffs, set([4, 5])).all():
844-
return None
845+
# wdiffs = unique(np.diff(self.index.week))
846+
#We also need -47, -49, -48 to catch index spanning year boundary
847+
# if not lib.ismember(wdiffs, set([4, 5, -47, -49, -48])).all():
848+
# return None
845849

846850
weekdays = unique(self.index.weekday)
847851
if len(weekdays) > 1:
848852
return None
853+
854+
week_of_months = unique((self.index.day - 1) // 7)
855+
if len(week_of_months) > 1:
856+
return None
849857

850858
# get which week
851-
week = (self.index[0].day - 1) // 7 + 1
859+
week = week_of_months[0] + 1
852860
wd = _weekday_rule_aliases[weekdays[0]]
853861

854862
return 'WOM-%d%s' % (week, wd)

0 commit comments

Comments
 (0)