Module Error Handling Question (1 Viewer)

DeanFran

Registered User.
Local time
Today, 08:09
Joined
Jan 10, 2014
Messages
111
I've been learnig about dates and Access and was surprised to learn that there's no native function determine the number of weekdays between 2 dates as there is in Excel. I googled around and found a couple of functions that do that job, and have been noodling around with using one of the methods I found in various ways. Of course I quickly stumbled upon the fact if there's only one date of the required two, you get an error for a result. I imagine the real life application of something like this would have to work around this likely scenario. I'm just looking for a nudge in the right direction. Here's the function I've been experimenting with.

Code:
Public Function MyNetworkDays(startDate As Date, endDate As Date) As Long
    Dim D As Date
    MyNetworkDays = 0
    D = startDate
    While D < endDate
    If Weekday(D) > 1 And Weekday(D) < 7 Then MyNetworkDays = MyNetworkDays + 1
D = D + 1
    Wend
End Function
 

pbaldy

Wino Moderator
Staff member
Local time
Today, 05:09
Joined
Aug 30, 2003
Messages
36,126
I'm not clear on the question, as I don't get an error:

?mynetworkdays (#8/7/23#,#8/8/23#)
1
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 08:09
Joined
May 21, 2018
Messages
8,529
In access I usually make the function take variants because if you use in a query there is a good chance you are going to pass a null.
Code:
Public Function MyNetworkDays(startDate As variant, endDate As variant) As variant
  if Isdate(startDate) and Isdate(endDate)
     if StartDate <= EndDate
     ...
     end if
end if

This way if used in a query you will pass back a null if your inputs are bad or missing. If you pass back 0 then you will not see where start = end or bad data.
 

pbaldy

Wino Moderator
Staff member
Local time
Today, 05:09
Joined
Aug 30, 2003
Messages
36,126
In access I usually make the function take variants because if you use in a query there is a good chance you are going to pass a null.
Code:
Public Function MyNetworkDays(startDate As variant, endDate As variant) As variant
  if Isdate(startDate) and Isdate(endDate)
     if StartDate <= EndDate
     ...
     end if
end if

You and me both. ;)
 

MajP

You've got your good things, and you've got mine.
Local time
Today, 08:09
Joined
May 21, 2018
Messages
8,529
If you do not make these variants then in your query you have to do something like this, to ensure you do not get a runtime error locking up the query.

Code:
NetDays:NetworkDays(nz([startDate],0), nz([endDate],0)
However if you do something like that you will get values that could cause downstream problems.
If endate and startdate are empty you return 0
if startdate is fillled and enddate is empty then you get 0
if startdate is empty and enddate is filled then you get the number of workdays since dec 31, 1899

so maybe a better one is
Code:
NetDays:NetworkDays(nz([startDate],0), nz([endDate],nz([startDate],0))
Then if then if the enddate is blank it equals the startdate, and if both blank it equals 0 (Dec 31,1899)

I prefer to do these checks in the function, so I do not forget.
 

jdraw

Super Moderator
Staff member
Local time
Today, 08:09
Joined
Jan 23, 2006
Messages
15,379
DeanFran,
Just a cautionary comment for consideration.
If you must account for Holidays within your date range, then appropriate logic and a holiday table is required.

I note in your comment in #1 you talk of weekdays, but your function shows netWORKdays.
 
Last edited:

Noson5434

New member
Local time
Today, 08:09
Joined
Jan 25, 2023
Messages
26
I've been learnig about dates and Access and was surprised to learn that there's no native function determine the number of weekdays between 2 dates as there is in Excel. I googled around and found a couple of functions that do that job, and have been noodling around with using one of the methods I found in various ways. Of course I quickly stumbled upon the fact if there's only one date of the required two, you get an error for a result. I imagine the real life application of something like this would have to work around this likely scenario. I'm just looking for a nudge in the right direction. Here's the function I've been experimenting with.

Code:
Public Function MyNetworkDays(startDate As Date, endDate As Date) As Long
    Dim D As Date
    MyNetworkDays = 0
    D = startDate
    While D < endDate
    If Weekday(D) > 1 And Weekday(D) < 7 Then MyNetworkDays = MyNetworkDays + 1
D = D + 1
    Wend
End Function

This should work.

Code:
Public Function CalculateWeekdays(ByVal startDate As Variant, ByVal endDate As Variant) As Variant
    On Error GoTo Err_Handler

    ' Check if the end date is before the start date.
    If endDate < startDate Then
        MsgBox "End date cannot be before the start date.", vbExclamation + vbOKOnly, "Weekday Calculation"
        Exit Function
    End If
    
    Dim currentDate As Date
    Dim weekdays As Long
    
    ' Initialize the weekdays counter to 0. This will be incremented for each weekday encountered.
    weekdays = 0
    ' Set our iterating date to the start date.
    currentDate = startDate
    
    ' Iterate through each date between start and end dates, excluding the end date.
    Do While currentDate < endDate
        ' The Weekday function returns values from 1 (Sunday) to 7 (Saturday).
        ' Therefore, to check if the current date is a weekday (Monday to Friday),
        ' we ensure its value is between 2 and 6 (inclusive).
        If Weekday(currentDate) >= 2 And Weekday(currentDate) <= 6 Then
            weekdays = weekdays + 1
        End If
        
        ' Move to the next date.
        currentDate = currentDate + 1
    Loop
    
    ' Return the count of weekdays.
    CalculateWeekdays = weekdays

Exit_Err_Handler:
    Exit Function

Err_Handler:
    MsgBox "Error Number: " & Err.Number & vbCrLf & "Error Description: " & Err.Description & vbCrLf & "Procedure: CalculateWeekdays", vbCritical + vbOKOnly, "CalculateWeekdays - Error"
    Resume Exit_Err_Handler
End Function
 
Last edited:

Pat Hartman

Super Moderator
Staff member
Local time
Today, 08:09
Joined
Feb 19, 2002
Messages
43,293
Unless a function includes holidays, it can't possibly return the number of working days between two dates which is NOT the same as the number of week days.

Here is a database with a number of useful date functions, including a holiday date table which you will probably have to populate with this year's holidays.

 

moke123

AWF VIP
Local time
Today, 08:09
Joined
Jan 11, 2013
Messages
3,920
In my calendars I use a procedure which gets the major U.S. holidays without using a table.
I use a dictionary with the date as key and the holiday name as the item.
Makes it very simple to use - dictHol.Exists( . . .) when iterating through dates.

It will also offset a holiday that falls on a weekend but is observed on a monday or friday.
 

Attachments

  • holidays.accdb
    512 KB · Views: 54

moke123

AWF VIP
Local time
Today, 08:09
Joined
Jan 11, 2013
Messages
3,920
Neat.
Holloween should be Halloween though, I think moke? :)
I think I spelled Independence day wrong too. At least in some versions I corrected it. Not sure which version I grabbed for the demo.
Oops.

Edit: Yup an old version. Re-Uploaded with spelling corrections.
 

HalloweenWeed

Member
Local time
Today, 08:09
Joined
Apr 8, 2020
Messages
213
Hello, just noticed this. I have code for a function in a general module, to be used in vba, SQL (Access), or code on controls:
Code:
Public Function WorkDayNext(SearchDate As Date, NumDays As Integer) As Date
'....................................................................
' Name: WorkDayNext
' Inputs: SearchDate As Date
'   NumDays As Integer, cannot be over 400 or negative. Null will be converted to zero.
' Returns: Date
' Author: Johnnie Alderson
' Date: 10/30/2018
' Returns the date: x number of working days past a date. Date can be a Holiday or Weekend.
' Note that this function accounts for holidays using:
' Requires a table named tblHolidays with a field named HolidayDate.
' This tblHolidays table must be updated annually with Holidays, in advance.
' If date is a weekend or Holiday, the date will be advanced (corrected) to the next work day before adding work days.
'....................................................................
On Error GoTo Err_WorkDayNext

Dim DB As DAO.Database
Dim rst As DAO.Recordset
Dim HolidayDate As DAO.Field

Set DB = CurrentDb
Set rst = DB.OpenRecordset("SELECT [HolidayDate] FROM tblHolidays", dbOpenSnapshot)

If IsNull(NumDays) Then NumDays = 0 ' convert null number of days to zero

' Error checking input variables:

If (Not IsDate(SearchDate)) Then
    MsgBox ("ERROR: WorkDayNext.SearchDate must be a date! Null date returned.")
    GoTo Exit_WorkDayNext
End If

If NumDays < 0 Then
    MsgBox ("ERROR: WorkDayNext.NumDays cannot be negative! Null date returned.")
    GoTo Exit_WorkDayNext
End If

If NumDays > 400 Then
    MsgBox ("ERROR: WorkDayNext.NumDays cannot >400! Null date returned.")
    GoTo Exit_WorkDayNext
End If

' Input error checking ends here

' First advance the date to a workday, if not already
' Check for weekend date
If Weekday(SearchDate) = 7 Then
    SearchDate = SearchDate + 2 ' add 2 if Saturday
End If

If Weekday(SearchDate) = 1 Then
    SearchDate = SearchDate + 1 ' add 1 if Sunday
End If

' Check for Holiday, and advance initially to workday if so
Do While DCount("*", "tblHolidays", "HolidayDate = #" & SearchDate & "#") > 0
    SearchDate = SearchDate + 1 ' increment if Holiday
  
    ' Check if day after Holiday is weekend
    If Weekday(SearchDate) = 7 Then
        SearchDate = SearchDate + 2 ' add 2 if Saturday
    End If
  
    If Weekday(SearchDate) = 1 Then
        SearchDate = SearchDate + 1 ' add 1 if Sunday
    End If
  
    Loop   ' past this point date is guaranteed to be workday if Holidays table is correct

Do While NumDays > 0
    SearchDate = SearchDate + 1 ' increment day and begin testing for weekends & Holidays
    NumDays = NumDays - 1     ' Decrement days to go (count) in loop

    ' Check if day is weekend
    If Weekday(SearchDate) = 7 Then
        SearchDate = SearchDate + 2 ' add 2 if Saturday
    End If
  
    If Weekday(SearchDate) = 1 Then
        SearchDate = SearchDate + 1 ' add 1 if Sunday
    End If
  
  
    Do While DCount("*", "tblHolidays", "HolidayDate = #" & SearchDate & "#") > 0
        SearchDate = SearchDate + 1 ' increment if Holiday
      
        ' Check if day is weekend
        If Weekday(SearchDate) = 7 Then
            SearchDate = SearchDate + 2 ' add 2 if Saturday
        End If
      
        If Weekday(SearchDate) = 1 Then
            SearchDate = SearchDate + 1 ' add 1 if Sunday
        End If
  
    Loop   ' past this point date is guaranteed to be workday if Holidays table is correct

Loop   ' Work is done

    ' MsgBox ("Past first loops, date is: " & SearchDate & "  NumDays = " & NumDays)

' Below is needed in case final day is Holiday, then recheck for weekend again
Do While DCount("*", "tblHolidays", "HolidayDate = #" & SearchDate & "#") > 0
    SearchDate = SearchDate + 1 ' increment if Holiday
  
    ' Check if day is weekend
    If Weekday(SearchDate) = 7 Then
        SearchDate = SearchDate + 2 ' add 2 if Saturday
    End If
  
    If Weekday(SearchDate) = 1 Then
        SearchDate = SearchDate + 1 ' add 1 if Sunday
    End If

Loop   ' past this point date is guaranteed to be workday if Holidays table is correct
  
WorkDayNext = SearchDate   ' return the work day date

Exit_WorkDayNext:
Exit Function

Err_WorkDayNext:
    Select Case Err

        Case Else
        MsgBox Err.Description
        Resume Exit_WorkDayNext
    End Select

End Function

I have permission from the Author to post this.
This has been in use for 4.5 years.
 
Last edited:

Noson5434

New member
Local time
Today, 08:09
Joined
Jan 25, 2023
Messages
26
To enhance your solution, consider utilizing the following method.
You will need to include the following module: VBA JSON


Code:
Public Function CalculateWeekdays(ByVal startDate As Variant, ByVal endDate As Variant, Optional ByVal includeHolidays As Boolean = False) As Variant
    On Error GoTo Err_Handler

    ' Check if the end date is before the start date.
    If endDate < startDate Then
        MsgBox "End date cannot be before the start date.", vbExclamation + vbOKOnly, "Weekday Calculation"
        Exit Function
    End If
   
    Dim currentDate As Date
    Dim weekdays As Long
    Dim holidays As Collection
   
    ' If includeHolidays is True, fetch the holidays.
    If includeHolidays Then
        Dim currentYear As Integer
        Set holidays = New Collection
        For currentYear = year(startDate) To year(endDate)
            Dim yearHolidays As Collection
            Set yearHolidays = GetUSHolidays(currentYear)
            Dim h As Variant
            For Each h In yearHolidays
                ' Only add the holiday to the collection if it's within the startDate and endDate range.
                If h >= startDate And h <= endDate And Not ExistsInCollection(holidays, h) Then
                    holidays.Add h
                End If
            Next h
        Next currentYear
    End If
   
    ' Initialize the weekdays counter to 0.
    weekdays = 0
    ' Set our iterating date to the start date.
    currentDate = startDate
   
    ' Iterate through each date between start and end dates, excluding the end date.
    Do While currentDate < endDate
        ' Check if the current date is a weekday (Monday to Friday).
        If Weekday(currentDate) >= 2 And Weekday(currentDate) <= 6 Then
            ' If we're including holidays, check if the date is a holiday.
            If includeHolidays Then
                Dim isHoliday As Boolean
                isHoliday = False
                Dim hd As Variant
                For Each hd In holidays
                    If hd = currentDate Then
                        isHoliday = True
                        Exit For
                    End If
                Next hd
                ' If it's not a holiday, increment the weekdays count.
                If Not isHoliday Then
                    weekdays = weekdays + 1
                End If
            Else
                ' If we're not including holidays, just increment the weekdays count.
                weekdays = weekdays + 1
            End If
        End If
       
        ' Move to the next date.
        currentDate = currentDate + 1
    Loop
   
    ' Return the count of weekdays.
    CalculateWeekdays = weekdays

Exit_Err_Handler:
    Exit Function

Err_Handler:
    MsgBox "Error Number: " & Err.Number & vbCrLf & "Error Description: " & Err.Description & vbCrLf & "Procedure: CalculateWeekdays", vbCritical + vbOKOnly, "CalculateWeekdays - Error"
    Resume Exit_Err_Handler
End Function

Public Function GetUSHolidays(ByVal targetYear As Integer) As Collection
    On Error GoTo Err_Handler
   
    ' Initialize an XMLHTTP object to fetch data from Nager.Date
    Dim xmlHttpRequest As Object
    Set xmlHttpRequest = CreateObject("Microsoft.XMLHTTP")
   
    ' Construct the URL for fetching U.S. holidays for the specified year.
    Dim apiUrl As String
    apiUrl = "https://date.nager.at/api/v3/publicholidays/" & targetYear & "/US"
   
    ' Set up and execute the HTTP request.
    With xmlHttpRequest
        .Open "GET", apiUrl, False
        .setRequestHeader "Content-Type", "text/xml"
        .send
    End With
   
    ' Parse the response JSON to extract the list of holidays.
    Dim parsedJson As Object
    Set parsedJson = JsonConverter.ParseJson(xmlHttpRequest.responseText)
   
    ' Initialize a collection to store the dates of the holidays.
    Dim holidaysCollection As Collection
    Set holidaysCollection = New Collection

    ' Loop through each holiday in the parsed JSON and add its date to the collection.
    Dim holidayData As Variant
    For Each holidayData In parsedJson
        holidaysCollection.Add CDate(holidayData("date"))
    Next holidayData

    ' Assign the populated collection to the function's return value.
    Set GetUSHolidays = holidaysCollection

Exit_Err_Handler:
    ' Clean up
    Set xmlHttpRequest = Nothing
    Set holidaysCollection = Nothing
    Exit Function

Err_Handler:
    MsgBox "Error Number: " & Err.Number & vbCrLf & "Error Description: " & Err.Description & vbCrLf & "Procedure: GetUSHolidays", vbCritical + vbOKOnly, "GetUSHolidays - Error"
    Resume Exit_Err_Handler
End Function

Private Function ExistsInCollection(ByVal col As Collection, ByVal value As Variant) As Boolean
    On Error Resume Next
    Dim temp As Variant
    temp = col(value)
    ExistsInCollection = (Err.Number = 0)
    Err.Clear
End Function
 

Pat Hartman

Super Moderator
Staff member
Local time
Today, 08:09
Joined
Feb 19, 2002
Messages
43,293
In my calendars I use a procedure which gets the major U.S. holidays without using a table.
I actually thought about doing that in my sample database but there are states that have their own holidays and even companies. For example, Massachusetts celebrates Patriots Day and that holiday doesn't affect any of the rest of the country, except in the years where it interferes with Tax day and we might end up with an extra couple of days to file our Federal returns if we live in a part of the country serviced by the facility in Massachusetts;) Companies also declare their own holidays that apply, obviously, only to their own employees. My high school even has one. They celebrate Nutting day in the fall in remembrance of our early farming communities. As far as the students were concerned, it was just a day off from school:)

Probably, the best alternative, is to write the code to generate the standard holidays but store the results in a table and let the calculation routine use the table. That takes care of the general cases and also the odd cases.
 

spaLOGICng

Member
Local time
Today, 05:09
Joined
Jul 27, 2012
Messages
127
I've been learnig about dates and Access and was surprised to learn that there's no native function determine the number of weekdays between 2 dates as there is in Excel. I googled around and found a couple of functions that do that job, and have been noodling around with using one of the methods I found in various ways. Of course I quickly stumbled upon the fact if there's only one date of the required two, you get an error for a result. I imagine the real life application of something like this would have to work around this likely scenario. I'm just looking for a nudge in the right direction. Here's the function I've been experimenting with.

Code:
Public Function MyNetworkDays(startDate As Date, endDate As Date) As Long
    Dim D As Date
    MyNetworkDays = 0
    D = startDate
    While D < endDate
    If Weekday(D) > 1 And Weekday(D) < 7 Then MyNetworkDays = MyNetworkDays + 1
D = D + 1
    Wend
End Function
While there is not a native function, instead of all the looping, you could simply use two of the native date functions, some simple IIF or SELECT CASE statements, to arrive at your answer.

The DateDiff will give you the number of days between two dates.

The only other issue apart from that is determinining whether a date transcends a weekend in either direction. In this case, with would need to get the Weekday Number for both the start and end dates.

If the datediff is 6 or 7, then that is a given 5.

If the weekday number begins or ends with a 1 or 7, then you know that you need to subtract one day.

If the datediff is less than five and the difference between the weekday numbers returns 0 or less then you know a weekend occurred. For instance, using weekday numbers, where 1 is Sunday and 7 is Saturday, 5 - 2 = 3 (no weekend). Or, 5 - 6 = -1 (has weekend). Or, 5 - 5 = 0 (has one weekend day).

If your DateDiff returns anything more than 7, then you simply remove the weekends.

Do you follow me? It's early Saturday morning and my brain is still fuzzy.
 

ebs17

Well-known member
Local time
Today, 14:09
Joined
Feb 7, 2020
Messages
1,948
Using functions seems to be very popular.
I mean, in a database, it's native and beneficial to work with queries based on tables.
So I quickly have a calendar table with a sequential date as the primary key. The weekday number for the day is stored in a second column. So you can easily filter for days in a period that are not on a weekend.
There is an extra table for public holidays. The public holidays can be deducted from the calendar days by query.
In the same way, one could manage company holidays, school holidays, division of the year into seasons, etc. and incorporate them into a query.

Setting up such tables for the first time may seem like a lot of work to some people. However, once such tables have been understood and created in a planned manner, they can be used very often and in many different ways.

In addition, I assume that determining working days is not just a one-off task for a form entry, but is also frequently applied to entire table contents. There query solutions are better and more convincing as well as more performant with proper design.
 

Users who are viewing this thread

Top Bottom