3
$\begingroup$

I have the following code that determines when the second business day of each month is (given a start and end date). I have a few If statements I would like to replace with functional programming.

getAccrualDates[fromDate_List,toDate_List]:=
(
today = fromDate;
projectionDate =toDate;

(*If the projected date is before a payroll, we don't want to grab any business days from that month. This will prevent the case where we try an grab the second business day of the month (i.e. payroll day), but it not existing*)
If[dateBeforePayrollQ[projectionDate],projectionDate=DatePlus[{projectionDate[[1]],projectionDate[[2]],1},{-1,"Day"}],projectionDate];

(*Determine if today is before the payroll date of the current month. If it is, include all business days of the current month. Otherwise, create a list of business days starting with next month*)
If[dateBeforePayrollQ[fromDate],allBusinessDays =   Take[DateList[#],3]&/@DayRange[DateObject[firstDayOfThisMonth],DateObject[projectionDate],"BusinessDay"];,allBusinessDays = Take[DateList[#],3]&/@DayRange[DateObject[firstDayOfNextMonth],DateObject[projectionDate],"BusinessDay"];];

(*Partition data by month*)
groupedBusinessDays = Flatten[GatherBy[Flatten[GatherBy[allBusinessDays,First],1],#[[2]]&],0];

(*Find all first payrolls between today and projectionDate*)
payrollDays = #[[2]]&/@groupedBusinessDays
)

Helper function below:

(*Create a helper function that returns true if the given date is before payroll, false otherwise*)
dateBeforePayrollQ[theDate_List]:=
(
firstDayOfThisMonth = {theDate[[1]],theDate[[2]],1};
firstDayOfNextMonth = DatePlus[firstDayOfThisMonth,{1,"Month"}];
businessDaysOfThisMonth =   Take[DateList[#],3]&/@DayRange[DateObject[firstDayOfThisMonth],DateObject[DatePlus[firstDayOfNextMonth,{-1,"Day"}]],"BusinessDay"];
If[businessDaysOfThisMonth[[2]][[3]]<= theDate[[3]],False,True]
)

Does anyone have any suggestions/better ways to accomplish this?

getAccrualDates[{2016,9,1},{2017,1,4}]
(*{{2016,9,2},{2016,10,4},{2016,11,2},{2016,12,2},{2017,1,4}}*)
$\endgroup$
3
$\begingroup$

You may use the Date & Time guide functions to greatly simplify your code.

ClearAll[getAccrualDates];
getAccrualDates[start_DateObject, end_DateObject, frequency_, location_] :=
 DatePlus[#, location] & /@ 
   DateRange[DatePlus[start, {-1, frequency}], end, frequency] // 
  Select[Between[{start, end}]]

Then

getAccrualDates[
 DateObject@{2016, 9, 1}, DateObject@{2017, 1, 4}, "EndOfMonth", {2, "BusinessDay"}]
{{2016, 9, 2}, {2016, 10, 4}, {2016, 11, 2}, {2016, 12, 2}, {2017, 1, 4}}

DateRange gives the range of dates in frequency increments. start is adjusted backwards one unit of frequency to ensure no dates are missed. DatePlus is mapped over the frequency dates to adjust them to the required location. Finally, the dates that are Between start and end are Selected. DateObject is required for start and end in order for Between to know what to do with them.

Try others like the second Tuesday of each month.

getAccrualDates[
 DateObject@{2016, 9, 1}, DateObject@{2017, 1, 4}, "EndOfMonth", {2, Tuesday}]
{{2016, 9, 13}, {2016, 10, 11}, {2016, 11, 8}, {2016, 12, 13}}

DateObjects actually returned but you can easily convert with DateList.

Hope this helps.

$\endgroup$
4
$\begingroup$

Pretty cute problem. Let's generate the required dates first:

firstDays = NestList[DatePlus[#, {1, "Month"}] &, DateObject[{2016, 9, 1}], 4];

From there:

getAccrualDates[date_DateObject] := NestWhile[DatePlus[#, 1] &, date,
                ! (DayMatchQ[#1, "BusinessDay"] && DayMatchQ[#2, "BusinessDay"]) &, 2]

Take[DateList[getAccrualDates[#]], 3] & /@ firstDays
   {{2016, 9, 2}, {2016, 10, 4}, {2016, 11, 2}, {2016, 12, 2}, {2017, 1, 4}}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.