Nov 28 2022 12:01 PM
How do I tell my excel calendar that Lock Date ='s day of the month and Due Date ='s +15 business days from day of the month? My calendar cells are broken up into single squares in which below the day of the week are two cells titled "Lock Date" and "Due Date" and below that are important numbers relevant to my client. I would like to know if I can set the dates for 2023 and have these cells populate automatically using a formula for variable data or if anyone knows a better way to create such a calendar?
Example for Tuesday January 3 in which the Lock Date is the 3rd and the Due Date is +15 business days from Jan. 3rd (cannot include weekends or holidays) this variable data will repeat for each day of the week, each day of the month, for the entire year:
Tuesday |
Lock date | Due Date |
3 | 1/24 |
1274 - 1545 |
Dec 02 2022 07:56 AM
You do not identify which version of Excel you are using. I'm going to assume you use a version that supports the LET function.
Using formulas only? Technically ... perhaps, barely, using the LET function. But that would require such very lengthy formulas that I would not even try.
But using VBA code (stored in a macro-enabled workbook which, as always, involves some security risk) along with formulas, definitely yes. It can be accomplished with or without the LET function, but using LET in my sample workbook allows me to write the formulas more plainly.
I wrote the custom VBA function CalcBusinessDay to perform the calculation. It takes parameters of
Public Function CalcBusinessDay(ByVal FromDate As Date _
, ByVal NumOfBusDaysAhead As Integer _
, Optional ByVal HolidayRange As Range _
, Optional ByVal HolidayArray As Variant _
) As Variant
' This procedure calculates and returns the next business day
' (on* or) after the specified FromDate. NumOfBusDaysAhead
' can be a value up to 60.
' The HolidayRange parameter takes precedence over the
' HolidayArray parameter, if both are specified.
' * Note that NumOfBusDaysAhead can be zero. In that case,
' if FromDate is a weekend or a holiday, the next business
' day is returned; otherwise, FromDate is a business day and
' it is returned.
Dim dteReturnValue As Date
Dim vntCopy As Variant 'a copy of the holiday date values
Dim blnHasTwoDimensions As Boolean
Dim in4LastItem As Long 'index of the last holiday date
'
Dim dteWorkDate As Date 'This date starts at FromDate and _
is incremented until the answer is found.
Dim in2BusinessDayCount As Integer
Dim in2Iteration As Integer
Dim vntItem As Variant
Dim in4HolidayIdx As Long
'----
On Error GoTo DueDte_ErrHndlr
'---- Validate input.
If NumOfBusDaysAhead < 0 _
Or NumOfBusDaysAhead > 60 Then
CalcBusinessDay = CVErr(380)
Exit Function
End If
' --
If Not HolidayRange Is Nothing Then
vntCopy = HolidayRange
ElseIf TypeName(HolidayArray) = "Variant()" Then
vntCopy = HolidayArray
blnHasTwoDimensions = UBound(HolidayArray, 2)
'(That is a tricky way of verifying that the array has at least
'two dimensions. If it does not, that test will cause an exception.)
If UBound(HolidayArray, 2) > LBound(HolidayArray, 2) Then
'...You've passed in a range with multiple columns.
'Should I just ignore the extra data? No; the person
'writing that code is causing memory to be wasted.
CalcBusinessDay = CVErr(380)
Exit Function
End If
Else
CalcBusinessDay = CVErr(380)
Exit Function
End If
'---- To improve efficiency, identify the last entry among the
' values with a (presumed) date.
in4LastItem = UBound(vntCopy, 1)
Do
vntItem = vntCopy(in4LastItem, 1)
If VarType(vntItem) = vbDate _
Or VarType(vntItem) = vbDouble Then
Exit Do
End If
in4LastItem = in4LastItem - 1
Loop
'----
dteWorkDate = FromDate
' The first pass through checking has a date offset of
' zero days from FromDate.
Do
in2Iteration = in2Iteration + 1
' --
If Weekday(dteWorkDate) = vbSunday Then
GoTo NextIteration
ElseIf Weekday(dteWorkDate) = vbSaturday Then
' Skip Sunday as well.
dteWorkDate = dteWorkDate + 1
GoTo NextIteration
End If
' -- Check the holidays to see if Work Date is present.
' (This is performed by an inefficient method, if the
' holidays are in chronological order. But it's still fast.)
For in4HolidayIdx = 1 To in4LastItem
vntItem = vntCopy(in4HolidayIdx, 1)
If VarType(vntItem) = vbDate _
Or VarType(vntItem) = vbDouble Then
If dteWorkDate = vntItem Then
'...Work Date is a holiday; skip it.
GoTo NextIteration
End If
Else
'Ignore other data.
End If
Next in4HolidayIdx
' --
If in2BusinessDayCount = NumOfBusDaysAhead Then
dteReturnValue = dteWorkDate
Exit Do
End If
in2BusinessDayCount = in2BusinessDayCount + 1
NextIteration:
If in2Iteration > 125 Then
'...something is amiss!
CalcBusinessDay = CVErr(2001)
Exit Function
End If
dteWorkDate = dteWorkDate + 1
Loop
'----
CalcBusinessDay = dteReturnValue
Exit Function
DueDte_ErrHndlr:
Dim in4ErrorCode As Long
Dim strErrorDescr As String
' -- Capture info.
in4ErrorCode = Err.Number
strErrorDescr = Err.Description
' --
CalcBusinessDay = CVErr(in4ErrorCode)
Exit Function
Resume 'inaccessible, but retained for debugging
End Function
Put CalcBusinessDay into a standard code module. If you need an explanation of how to do that, just ask.
I started to structure some cells like your sample, but did not want to continue that, as it will take up so much space in my sample, making it harder to review the results. And yes, maybe you do not allow Lock Dates to occur on weekends or on holidays, but the code supports the calculation for those days also.
If the 3 shown as your Lock Date is actually a formatted date (showing only the day-of-month), you can use that cell as the first argument, no worries. But if the 3 is actually just a one- or two-digit number (or is text), you'll need a calculation to derive the actual date. I did that in three different ways on the attached workbook, for January, February, and March 2023.
Regarding the Holidays worksheet, the code does allow for blank cells embedded in the Dates column. A row does not affect the Due Date calculations unless its Date column contains a date. If you already have a worksheet/other workbook that contains holiday data, you can probably use it instead. Otherwise, you can add holidays for years further in the future (and the past), include your own company holidays, and if your company does not celebrate holidays like those included, those rows can be removed. In the Calendar worksheet formulas I wrote to use it, only the first ~333 rows are used, so someone will have to update those references ... in about 20 years. Feel free to change the $A$333 cell address before then.