节假日和工作日计算及转换






4.86/5 (9投票s)
在日期之间添加和减去工作日。计算日期是否为节假日。
介绍
该项目的目的是帮助报告系统确定运行报告的有效日期。它具有确定日期是否为节假日的函数,以及允许报告系统跳过周末的函数,以满足诸如“我需要昨天、今天或明天的数据”之类的需求。
背景
我首先回顾了 2003 年的 ASP 文章“处理工作日、节假日和周末”,我将其翻译并进一步扩展到 VB.NET。 当然,每个人庆祝的节日可能不同,因此您需要修改代码以适应您的目的。 我添加了我认为有用的功能,或者是我正在开发的 ASPX Live Reporting 系统中需要的功能。
节假日有两种基本类型:一些是设置为特定日期的,例如圣诞节,属于浮动假日。 如果其中一个落在星期六,那么星期五就被认为是假日,星期日变成星期一,等等。 固定假日是每周的特定一天。 我没有涵盖复活节,因为它总是在星期天,而且本来就不是工作日。
由于这用于报告引擎,并且我的客户周末工作,但不希望将它们纳入报告的考虑范围。 该模块重新定义了“今天”的概念。 周六和周日,“今天”就是星期一。 因此,周六和周日,“明天”就是星期二。 反之,周日和周一,“昨天”就是星期五。 如果是星期六,并且节假日落在星期一,那么“明天”就是星期三,“今天”就是星期二。 你需要全神贯注才能理解这一点。
使用代码
由于这一切都基于节假日,因此 isHoliday
函数是一个很好的起点。
IsHoliday
如果找到节假日,则返回布尔值 True。 有一些全局属性可以通过 IsHolidayOK
样式名称(例如 IsNewYearsOK
)来关闭节假日。 它们都默认为 true,并且可以通过编程方式更改。
''' <summary>
''' Returns True if Date passed is a Holiday
''' </summary>
''' <param name="_Date">Date to Check</param>
''' <returns>Boolean True if Holiday</returns>
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 ' Jan
If IsNewYearsOK Then
If iDay = 1 Then ' New Years
If isNotWeekend(_Date) Then
ret = True
End If
ElseIf iDay = 2 Then 'Make Sure New Years isn't on a Sunday
If _Date.AddDays(-1).DayOfWeek = DayOfWeek.Sunday Then
ret = True
End If
End If
End If
If IsMartinLutherKingOK Then
'Is it Martin Luther King B-Day 3rd Monday of January
If _Date.DayOfWeek = DayOfWeek.Monday Then
' it might be
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
'Might be Presidents Day 3rd Moday
If _Date.Day > 14 AndAlso _Date.Day < 22 Then
ret = True
End If
End If
End If
Case 3 ' March
Case 4 'April (Easter is on Sunday)
Case 5
If IsMemorialDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
'Memorial Day Last Monday
If _Date.Day > 24 And _Date.Day < 32 Then
ret = True
End If
End If
End If
Case 6 ' June
Case 7 'July
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 'August
Case 9 'September
If IsLaborDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
'Labor Day
If _Date.Day < 7 Then
Return True
End If
End If
End If
Case 10 'October
If IsColumbusDayOK Then
If _Date.DayOfWeek = DayOfWeek.Monday Then
'Columbus Day, Second Monday of October
If _Date.Day > 7 AndAlso _Date.Day < 15 Then
ret = True
End If
End If
End If
Case 11 'November
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
'Thanksgiving is 4th Thursday
If _Date.DayOfWeek = DayOfWeek.Thursday Then
ret = True
End If
End If
End If
Case 12 'December
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
'New Years is on Saturday
ret = True
End If
End If
Case Else
End Select
Return ret
End Function
GetBusinessToday
GetBusinessToday
有两个重载:一个没有参数,使用 NOW
作为日期;另一个带有参数,可以用日期调用。 它首先检查我们是否在周末,然后确保它不会停在节假日。
''' <summary>
''' Returns the _Date or next valid business day
''' </summary>
''' <param name="_Date">Next Business Day after Internal GetBusinessToday</param>
''' <returns></returns>
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
也有两个重载,并从今天(GetBusinessToday
)或过去的日期获取第一个之前的日期。
Public Function GetBusinessYesterday(ByRef _Date As Date) As Date
Dim ret As Date = _Date
ret = HolidaySubtractDays(ret.AddDays(-1)) ' 5/28/2013 DVF moved part from else
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
返回下一个有效工作日,同样有两个重载。
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
在向日期添加天数时,会同时考虑周末和节假日。 它检查每个日期,看看是否是节假日,并使用回调函数从计数中跳过节假日。
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
是一个内部函数,用于查找日期之后的下一个非节假日。 它是递归的,尽管很可能永远不需要这样做。 我认为有人可能会认为感恩节后的第二天是节假日,并想尝试为此编写代码。
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
它与 HolidayAddDays
相同,但是朝着相反的方向移动,它们很可能应该合并。 它递归地从特定日期中删除天数,以寻找非节假日。
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
我添加这个功能只是为了以防万一我们想在工作日计算中忽略节假日。 它不会返回节假日,而是在计算天数时忽略它们。
LastBusinessDayOfMonth
LastBusinessDayOfMonth
返回作为参数传递的月份的最后一个工作日。
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
在该模块中,还有用于该月的最后一天的函数,以及因为我懒惰,该月的第一天和该月的第一个工作日。
结束
这几乎涵盖了我所做的一切。 如果我遗漏了任何东西,或者某些东西没有按预期工作,请告诉我。 当然,请随时改进它或教我代码!