Here is my first cut at a general purpose way of adding business days to a base date.
To help you understand the code a bit better...
The dates are 32 bit integers representing the number of days since Jan 1, 1 which is a Monday.
I use Sun=0....Sat=6 as my values for the 7 weekdays defined as %SUNDAY - %SATURDAY.
The function cmGregorianWeekday is just MOD 7 operation that also handles negative numbers so that the result is always in the range 0-6.
cmWeekDayAfter just calculates the next requested day of the week.
cmWeekDayBefore just calculates the first prior day of the week.
Code:
FUNCTION cmGregorianBusinessAddWeekdays (BYVAL dwStart AS LONG, _ BYVAL dwEnd AS LONG, _ BYVAL dwSign AS LONG, _ BYVAL dwSunday AS LONG, _ BYVAL dwMonday AS LONG, _ BYVAL dwTuesday AS LONG, _ BYVAL dwWednesday AS LONG, _ BYVAL dwThursday AS LONG, _ BYVAL dwFriday AS LONG, _ BYVAL dwSaturday AS LONG) AS LONG ' ' Add/Subtract days range to adjust for days of the week that are non business days ' ' dwSunday through dwSaturday, %TRUE=business day, %FALSE=non business day ' ' Returns fixed date representing the range extension either backwards from dsStart ' or forwards from dwEnd ' ' dwSign = 1 if range extension is forward from dwEnd, -1 if from dwStart ' DIM dwNonBusinessDays AS LOCAL LONG DIM dwLoop AS LOCAL LONG dwNonBusinessDays = -1 WHILE dwNonBusinessDays <> 0 dwNonBusinessDays = 0 IF dwSunday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%SUNDAY,dwStart,dwEnd) END IF IF dwMonday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%MONDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF IF dwTuesday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%TUESDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF IF dwWednesday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%WEDNESDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF IF dwThursday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%THURSDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF IF dwFriday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%FRIDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF IF dwSaturday = %FALSE THEN dwNonBusinessDays = cmGregorianWeekdayCount(%SATURDAY,dwStart,dwEnd) _ + dwNonBusinessDays END IF ' If there are some non business weekdays found, adjust the range for next pass SELECT CASE dwNonBusinessDays CASE > 0 ' Test for which end of the range to adjust, make the adjustment and reset the new range SELECT CASE dwSign CASE = 1 SELECT CASE dwEnd - dwStart + 1 CASE <> dwNonBusinessDays ' If all the days in the range are non-business dwStart = dwEnd + dwNonBusinessDays dwEnd = dwEnd + 1 CASE ELSE dwStart = dwEnd + 1 ' Loop through one day at a time until a business day is found dwEnd = dwStart END SELECT CASE ELSE SELECT CASE dwEnd - dwStart + 1 CASE <> dwNonBusinessDays dwEnd = dwStart - dwNonBusinessDays dwStart = dwStart - 1 CASE ELSE dwEnd = dwStart - 1 ' Loop through one day at a time until a business day is found dwStart = dwEnd END SELECT END SELECT ' Reverse to reset range SWAP dwStart, dwEnd END SELECT WEND FUNCTION = IIF(dwSign = 1,dwEnd,dwStart) ' Check which end of the range to return END FUNCTION FUNCTION cmGregorianWeekdayCount (BYVAL dwWeekday AS LONG, _ BYVAL dwStart AS LONG, _ BYVAL dwEnd AS LONG) AS LONG ' Calculate the number of ocurrences of dwWeekDay (%SUNDAY - %SATURDAY) in the ' date range provided DIM dwCount AS LOCAL LONG ' Start date must be less or equal to ending date SELECT CASE dwStart CASE > dwEnd SWAP dwStart, dwEnd END SELECT ' If Start date and ending date are equal, return 0 days SELECT CASE dwStart CASE = dwEnd dwCount = 0 CASE ELSE dwCount = INT(ABS(cmWeekdayAfter(dwWeekday,dwEnd) _ - cmWeekdayBefore(dwWeekday,dwStart)) / 7) _ - 1 END SELECT FUNCTION = dwCount END FUNCTION
Add 20 business days to April 1, 2009, Sat/Sun are non business weekdays
April 1, 2009 = 733498 + 20 = 733518 or April 21, 2009
Calling the function as:
cmGregorianBusinessAddWeekdays (733498, _
733518,
1, _
0, _ ' Sunday
1, _
1, _
1, _
1, _
1, _
0) ' Saturday
The first pass through the WHILE/WEND loop in cmGregorianBusinessAddWeekdays finds 3 Sundays and 3 Saturdays for a total of 6 non business days. The inspected date range is adjusted to 733519 (April 22, 2009) through 733524 (April 27, 2009).
The second pass through the WHILE/WEND loop finds 1 Sunday and 1 Saturday for a total of 2 non business days for the April 22-27, 2009 range. The inspected date range is adjusted to 733525 (April 28, 2009) through 733526 (April 29, 2009).
The thrid pass through the WHILE/WEND loops finds 0 Sunday and 0 Saturday for a total of 0 (zero). We are done and the answer is April 29, 2009.
Before this function is called, the range has to adjusted to be sure the lower part of the range is passed first and that at least one weekday is specified as a normal business day (or a loop race will result). One special case had to be handled in cmGregorianBusinessAddWeekdays to handle the situation where the entire initial range is composed of non-business days. In that case, we just revert to looping one day at time to find the next business work day.
I'm out of state for the coming week. Enjoy!
Rick
Leave a comment: