Introduction
The project is intended to help a reporting system determine valid dates to run reports. It has functions for determining if a date is a holiday, as well as functions to allow the reporting system to skip over weekends when you have requirements like I want this data by yesterday, today, or tomorrow.
Background
I started by reviewing a 2003 ASP article “Working with Business Dates Business Holidays Week”, I translated that and more into VB.NET. Of course everyone might celebrate different holidays, so you’ll have to change the code to fit your purpose. I added features I thought were useful, or were needed in the ASPX Live Reporting system that I’m working on.
There are two basic types of holidays: some are set to a particular day, like Christmas which are floating holidays. If one of these falls on a Saturday, then Friday is considered the holiday, Sunday turns into Monday, etc. Fixed holidays are on a specific day of the week. I didn’t cover Easter as it’s always on Sunday and already not a business day.
Since this is used for a reporting engine, and my clients work weekends, but don’t want them considered for the reports. The module redefines what the concept of Today is. Today is Monday on Saturday and Sunday. Thus Tomorrow is Tuesday on Saturday and Sunday. Conversely Yesterday is Friday on Sunday and Monday. If it’s Saturday and a Holiday falls on Monday then Tomorrow is Wednesday and Today is Tuesday. You have to be the ball to follow that one.
Using the code
Since this is all based on holidays, the isHoliday
function is a good starting point.
IsHoliday
Returns Boolean True if holiday is found. There are global properties to turn off holidays with IsHolidayOK
style names (IsNewYearsOK
for example). They are all defaulted to true and programmatically changeable.
Public Function isHoliday(_Date As Date) As Boolean
Dim ret As Boolean = False
Dim iDay As Integer
Dim iTmpDay As Integer
Dim i As Integer
iDay = Day(_Date)
Select Case _Date.Month
Case 1
If IsNewYearsOK Then
If iDay = 1 Then
If isNotWeekend(_Date) Then
ret = True
End If
ElseIf iDay = 2 Then
If _Date.AddDays(-1).DayOfWeek = DayOfWeek.Sunday Then
ret = True
End If
End If
End If
If IsMartinLutherKingOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
If _Date.Day > 14 AndAlso _Date.Day < 22 Then
ret = True
End If
End If
End If
Case 2
If IsPresidentsDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
If _Date.Day > 14 AndAlso _Date.Day < 22 Then
ret = True
End If
End If
End If
Case 3
Case 4
Case 5
If IsMemorialDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
If _Date.Day > 24 And _Date.Day < 32 Then
ret = True
End If
End If
End If
Case 6
Case 7
If Is4thOfJulyOk Then
Select Case _Date.Day
Case 3
If _Date.DayOfWeek = DayOfWeek.Friday Then
ret = True
End If
Case 4
If isNotWeekend(_Date) Then
ret = True
End If
Case 5
If _Date.DayOfWeek = DayOfWeek.Monday Then
ret = True
End If
Case Else
End Select
End If
Case 8
Case 9
If IsLaborDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
If _Date.Day < 7 Then
Return True
End If
End If
End If
Case 10
If IsColumbusDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
If _Date.Day > 7 AndAlso _Date.Day < 15 Then
ret = True
End If
End If
End If
Case 11
If IsVeteransDayOK Then
If _Date.Day >= 10 AndAlso _Date.Day <= 12 Then
Select Case _Date.Day
Case 10
If _Date.DayOfWeek = DayOfWeek.Friday Then
ret = True
End If
Case 11
If isNotWeekend(_Date) Then
ret = True
End If
Case 12
If _Date.DayOfWeek = DayOfWeek.Monday Then
ret = True
End If
Case Else
End Select
End If
End If
If IsThanksgivingOK Then
If _Date.Day > 20 AndAlso _Date.Day < 28 Then
If _Date.DayOfWeek = DayOfWeek.Thursday Then
ret = True
End If
End If
End If
Case 12
If IsChristmasOK Then
If _Date.Day >= 24 AndAlso _Date.Day <= 26 Then
Select Case _Date.Day
Case 24
If _Date.DayOfWeek = DayOfWeek.Friday Then
ret = True
End If
Case 25
If isNotWeekend(_Date) Then
ret = True
End If
Case 26
If _Date.DayOfWeek = DayOfWeek.Monday Then
ret = True
End If
Case Else
End Select
End If
End If
If IsNewYearsOK Then
If _Date.Day = 31 AndAlso _Date.DayOfWeek = DayOfWeek.Friday Then
ret = True
End If
End If
Case Else
End Select
Return ret
End Function
GetBusinessToday
GetBusinessToday
has two overloads: one with no parameters to use NOW
as the date, and one with parameters which can be called with a date. It first checks if we are on a weekend, then makes sure it doesn’t stop on a holiday.
Public Function GetBusinessToday(ByRef _Date As Date) As Date
Dim ret As Date = _Date
If ret.DayOfWeek = DayOfWeek.Sunday Then
ret = ret.AddDays(+1)
ElseIf ret.DayOfWeek = DayOfWeek.Saturday Then
ret = ret.AddDays(+2)
End If
ret = HolidayAddDays(ret)
Return ret
End Function
GetBusinessYesterday
GetBusinessYesterday
also has two overloads and gets the first prior date from either Today (GetBusinessToday
) or a past date.
Public Function GetBusinessYesterday(ByRef _Date As Date) As Date
Dim ret As Date = _Date
ret = HolidaySubtractDays(ret.AddDays(-1))
If ret.DayOfWeek = DayOfWeek.Monday Then
ret = ret.AddDays(-3)
ElseIf ret.DayOfWeek = DayOfWeek.Sunday Then
ret = ret.AddDays(-2)
Else
End If
Return ret
End Function
GetBusinessTomorrow
GetBusinessTomorrow
returns the next valid business day, again with two overloads.
Public Function GetBusinessTomorrow() As Date
Dim ret As Date = GetBusinessToday()
If ret.DayOfWeek = DayOfWeek.Friday Then
ret = ret.AddDays(+3)
ElseIf ret.DayOfWeek = DayOfWeek.Saturday Then
ret = ret.AddDays(+2)
Else
ret = ret.AddDays(+1)
End If
ret = HolidayAddDays(ret)
Return ret
End Function
AddBusinessDays
AddBusinessDays
takes both weekends and holidays into consideration when adding days to a date. It checks each date to see if it’s a holiday and skips the holiday from the count using a callback function.
Public Function AddBusinessDays(ByVal StartDate As Date, ByVal WorkDays As Integer) As Date
Dim ret As Date = StartDate
Dim direction As Integer = 0
If WorkDays > 0 Then
direction = 1
ElseIf WorkDays < 0 Then
direction = -1
End If
If WorkDays <> 0 Then
For i = 1 To Math.Abs(WorkDays)
If direction > 0 Then
ret = HolidayAddDays(ret.AddDays(direction))
Else
ret = HolidaySubtractDays(ret.AddDays(direction))
End If
While (ret.DayOfWeek = _
DayOfWeek.Saturday OrElse ret.DayOfWeek = DayOfWeek.Sunday)
ret = ret.AddDays(direction)
End While
Next
End If
Return ret
End Function
HolidayAddDays
HolidayAddDays
is an internal function to find the next non-holiday day from a date. It is recursive, although the odds are high it would never need to do that. I thought someone might consider the day after Thanksgiving a holiday and wanted to try to code for that.
Private Function HolidayAddDays(ByVal _Date As Date) As Date
Dim ret As Date = _Date
If isHoliday(_Date) Then
ret = _Date.AddDays(1)
HolidayAddDays(ret)
End If
Return ret
End Function
HolidaySubtractDays
It’s the same as HolidayAddDays
, but moving the other direction, they likely should have been combined. It recursively removes days from a particular date looking for a non-holiday.
Private Function HolidaySubtractDays(ByVal _Date As Date) As Date
Dim ret As Date = _Date
If isHoliday(_Date) Then
ret = _Date.AddDays(-1)
HolidaySubtractDays(ret)
End If
Return ret
End Function
AddBusinessDaysIgnoreHoliday
I added this just in case we wanted to ignore holidays in the Business day calculation. It will not return a holiday, it does ignore them while counting through the days.
LastBusinessDayOfMonth
LastBusinessDayOfMonth
returns the last business day of the month passed as a parameter.
Public Function LastBusinessDayOfMonth(ByVal _Date As Date) As Date
Dim ret As Date = _Date
ret = GetBusinessYesterday(New DateTime(_Date.Year, _Date.Month + 1, 1))
Return ret
End Function
In the Module there are also functions for the last day of the month, and just because I’m lazy, first day of month and first business day of month.
The End
That pretty much covers what I did. If I missed anything or something doesn’t work the way it should, please let me know. Of course feel free to make it better or teach me code!