VBA Workdays

kickstand07

New member
Local time
Today, 09:26
Joined
May 24, 2012
Messages
6
Hey guys,

This is my first post on access-programmers, so be gentle please!

My issue is this:

I am trying to calculate the number of minutes between the opening of a ticket and its close using Access 2010. Obviously this is simple with DateDiff, but i also need to exclude holidays, weekends and non-business hours(Officially our hours are 8am-5pm)

I have very little experience with VBA or Access for that matter, so i have been basically moving forward with google as my only aid. So far i have come up with the code to exclude holidays(referencing a holiday table in my database) and weekends, but the function is returning a whole day number which i cannot simply multiply x 24 x 60 because i need an accurate count down to the minute.

For example:
Given open_date 5/18/2012 12:02:36 AM close_date 5/24/2012 1:50:43 AM the workdays function would return 5 (it successfully removed the two weekend days)

Here is the VBA code for the Weekdays and Workdays function:

PHP:
Option Compare Database
Option Explicit

Public Function Weekdays(ByRef startDate As Date, _
    ByRef endDate As Date _
    ) As Integer
    ' Returns the number of weekdays in the period from startDate
    ' to endDate inclusive. Returns -1 if an error occurs.
    ' If your weekend days do not include Saturday and Sunday and
    ' do not total two per week in number, this function will
    ' require modification.
    On Error GoTo Weekdays_Error
    
    ' The number of weekend days per week.
    Const ncNumberOfWeekendDays As Integer = 2
    
    ' The number of days inclusive.
    Dim varDays As Variant
    
    ' The number of weekend days.
    Dim varWeekendDays As Variant
    
    ' Temporary storage for datetime.
    Dim dtmX As Date
    
    ' If the end date is earlier, swap the dates.
    If endDate < startDate Then
        dtmX = startDate
        startDate = endDate
        endDate = dtmX
    End If
    
    ' Calculate the number of days inclusive (+ 1 is to add back startDate).
    varDays = DateDiff(Interval:="d", _
        date1:=startDate, _
        date2:=endDate) + 1
    
    ' Calculate the number of weekend days.
    varWeekendDays = (DateDiff(Interval:="ww", _
        date1:=startDate, _
        date2:=endDate) _
        * ncNumberOfWeekendDays) _
        + IIf(DatePart(Interval:="w", _
        Date:=startDate) = vbSunday, 1, 0) _
        + IIf(DatePart(Interval:="w", _
        Date:=endDate) = vbSaturday, 1, 0)
    
    ' Calculate the number of weekdays.
    Weekdays = (varDays - varWeekendDays)
    
Weekdays_Exit:
    Exit Function
    
Weekdays_Error:
    Weekdays = -1
    MsgBox "Error " & Err.Number & ": " & Err.Description, _
        vbCritical, "Weekdays"
    Resume Weekdays_Exit
End Function

Public Function Workdays(ByRef startDate As Date, _
     ByRef endDate As Date, _
     Optional ByRef strHolidays As String = "Holidays" _
     ) As Integer
    ' Returns the number of workdays between startDate
    ' and endDate inclusive.  Workdays excludes weekends and
    ' holidays. Optionally, pass this function the name of a table
    ' or query as the third argument. If you don't the default
    ' is "Holidays".
    On Error GoTo Workdays_Error
    Dim nWeekdays As Integer
    Dim nHolidays As Integer
    Dim strWhere As String
    
    ' DateValue returns the date part only.
    startDate = DateValue(startDate)
    endDate = DateValue(endDate)
    
    nWeekdays = Weekdays(startDate, endDate)
    If nWeekdays = -1 Then
        Workdays = -1
        GoTo Workdays_Exit
    End If
    
    strWhere = "[Holiday] >= #" & startDate _
        & "# AND [Holiday] <= #" & endDate & "#"
    
    ' Count the number of holidays.
    nHolidays = DCount(Expr:="[Holiday]", _
        Domain:=strHolidays, _
        Criteria:=strWhere)
    
    Workdays = nWeekdays - nHolidays
    
Workdays_Exit:
    Exit Function
    
Workdays_Error:
    Workdays = -1
    MsgBox "Error " & Err.Number & ": " & Err.Description, _
        vbCritical, "Workdays"
    Resume Workdays_Exit
    
End Function

First of all i would like to make this code accurate to the minute. After that i believe i can move forward with my efforts to remove times outside of 8am-5pm

If at any point I have been unclear, I would be happy to elaborate.

Thanks in advance for any help, and i apologize for my ignorance :banghead:
 
After reading you post and thinking about what is involved, I don't think your issue is a simple as you have indicated. I think it can get a little more complicated due to the fact that the calculation is not just a matter of using the DateDiff function to do the calculation.

This is, however, an interesting concept.

If you only want to count minutes between the hours of 8:00 AM and 5:00 PM each working day that is not a holiday then the only way to accomplish this is to crate a user defined function that will return the number of minutes that elapsed between the start date and time and the ending date and time. I am planning to look into this further but time constraints have kept me from spending much time on it today.

If I get some time I will look at it more and get back to you.
 
Awesome, thanks for the reply. I'll continue hacking away at it myself for now, but i look forward to your feedback!

I seem to be making some headway by changing some dim as integer to "dim as variant" (in order to keep the TimeValue of the Dates) and changing the return interval to "n" instead of "d". I realize other values will need to be tweaked since I'm working with minutes now, so i'm trial and error'ing my way through it.

I have not even began to address the 8am to 5pm time frame yet, but i'm thinking that as i start to understand the parts of the workdays function more completely, maybe i will be able to apply that to work hours, as it serves a very similar purpose.
 
Welcome to the forum!

If we break this down a little, it will help. Let's ignore the start and end dates themselves. So using the workdays() function, we have 5 working days; if we ignore the start and end dates for now, that leaves 3 working days (5-2) for which each has 9 hours/day or 540 minutes/day (working time from 8am-5pm).

In other words 3*540 or 1620 minutes elapsed working minutes for the 3 full days of the 5 working days.

Now we just need to deal with the times involved in the start date and end dates relative to the 8AM-5PM time slot and add those minutes to the 1620 minutes from above. You said that you could handle that, but it is actually probably the most difficult portion.

Some questions first:

1. Can the start or end dates occur on a weekend or holiday?
2. Based on your example, the times on the start and end dates can occur outside of the 8AM-5PM time slot. Is that correct?
 
One additional question. Can the start and end dates occur on the same date?
 
The more i look into this, the more work i realize is behind what i want to accomplish. Thanks so much for your time. As for your questions:

1. Yes, tickets can and do start on a weekend or a holiday.
2. Yes, tickets can and do occur outside 8-5.
3. Yes, tickets are often closed on the same day they are opened(this is the most common case)

Let me know if you need anything else
 
Well, I played with it, and I think I got it. All of my checks seem to work out; I'll let you verify. I created a query (qryChecks) that uses the custom function I created. The DB is attached.

The code may not be the most efficient, but it is functional. Others may have a more streamlined approach.

Here is the actual code for the function



Code:
Public Function ElaspsedWorkTime(dteBegin As Date, dteFinish As Date) As Long

Dim FullWorkdayMinutes As Long
Dim MinutesFromStartDate As Long
Dim MinutesFromEndDate As Long
Dim HoldTotalMinutes As Long

Dim holdstartdate As Date
Dim holdenddate As Date
Dim startdateisoffday As Boolean
Dim enddateisoffday As Boolean

holdstartdate = DateValue(dteBegin)
holdenddate = DateValue(dteFinish)

If Workdays(holdstartdate, holdenddate) > 2 Then
    FullWorkdayMinutes = (Workdays(holdstartdate, holdenddate) - 2) * 540
End If


'check to see if start date is a weekend day or holiday; if it is set boolean to true otherwise false
If Weekday(holdstartdate) = 1 Or Weekday(holdstartdate) = 7 Or DCount("*", "holidays", "holiday=#" & holdstartdate & "#") > 0 Then
    startdateisoffday = True
Else
    startdateisoffday = False
End If

'check to see if end date is a weekend day or holiday; if it is set boolean to true otherwise false
If Weekday(holdenddate) = 1 Or Weekday(holdenddate) = 7 Or DCount("*", "holidays", "holiday=#" & holdenddate & "#") > 0 Then
    enddateisoffday = True
Else
    enddateisoffday = False
End If


'Test to see if the time on the start date occurs before 8am; if it does reset the time to 8am
If TimeValue(dteBegin) < #8:00:00 AM# Then
    dteBegin = DateValue(dteBegin) + #8:00:00 AM#
End If


'Test to see if the time on the end date occurs after 5PM; if it does reset the time to 5PM
If TimeValue(dteFinish) > #5:00:00 PM# Then
    dteFinish = DateValue(dteFinish) + #5:00:00 PM#
End If

Debug.Print dteBegin; dteFinish; FullWorkdayMinutes


'test to see if start and end dates are the same and that neither is a weekend day or holiday; if so just find the diffence in minutes of the times;

If DateValue(dteBegin) = DateValue(dteFinish) And startdateisoffday = False And enddateisoffday = False Then
    HoldTotalMinutes = DateDiff("n", dteBegin, dteFinish)
Else
'the dates are not the same
    'now test the start date to see if it is a weekend day or holiday; if not, do another check, otherwise set to zero
    If startdateisoffday = False Then
        'check to see if the start time is after 5pm; if so set minutesfromstartdate to zero otherwise
        'determine the minutes relative  to 5pm for the time of the start date
        If TimeValue(dteBegin) > #5:00:00 PM# Then
            MinutesFromStartDate = 0
        Else
            MinutesFromStartDate = DateDiff("n", dteBegin, DateValue(dteBegin) + #5:00:00 PM#)
        End If
    Else
        'startdate is a weekend day or holiday, so no minutes applicable
        MinutesFromStartDate = 0
    End If
    
    'now test the end date to see if is a weekend day or holiday, if not, do another check, otherwise set to zero
    If enddateisoffday = False Then
        'if endtime occurs before 8am set minutesfromEnddate to zero; otherwise calculate
        If TimeValue(dteFinish) < #8:00:00 AM# Then
            MinutesFromEndDate = 0
        Else
            'determine the minutes relative to 8AM for the time of the end date
            MinutesFromEndDate = DateDiff("n", DateValue(dteFinish) + #8:00:00 AM#, dteFinish)
        End If
    Else
        'enddate is a weekend day or holiday, so no minutes applicable
        MinutesFromEndDate = 0
    End If
    'now add start and end day minute values and fullworkdayminutes value to holdtotalminutes
    
    HoldTotalMinutes = FullWorkdayMinutes + MinutesFromStartDate + MinutesFromEndDate
End If

'pass the total minutes to the function

ElaspsedWorkTime = HoldTotalMinutes



End Function
 

Attachments

it isn't hard to know what to do

the two dates are just numbers (a date is stored as real number)

so say - the "open" number is 28246.12
and the "close" number is 284262.58

access just knows how to present a number to you. in a date format.

the difference of 16.46 between these dates represents

a) 16 days between the two
b) 0.46 of a day between the two

so multiply the whole number by 24 and by 60 to turn this into minutes

the datediff function is capable of doing this and a whole lot more - but this is what it does.

the HARD part is using this minutes in the way that you want. there is certainly no easy way of extracting form this the weekends, bank holidays, and non-working hours. it is easy enough to write a function that does it - now if you want to run this function just once, say in a single form, and store the elapsed minutes as a calculated field - this is probably doable

if you want to use it as a generic function to process a whole set of data, it may take too long to be practical.
 
Thanks for taking a shot at it jzwp22! Looks to be on the right track. However, when i pulled the function into my database i noticed a problem immediately. On dates opened and closed on the same day( negative values are returning. For example, try
StartDate "5/18/2012 12:00:56 AM" and EndDate "5/18/2012 12:13:28 AM". This returns -467 in both my database and yours.

Another issue i noticed, Given:
startdate "11/17/2011 12:36:03 AM" and enddate "11/25/2011 3:36:55 PM" ElapsedWorkTime is returning "2160"
however, it should return "2700" given that 11/24 and 11/25 are holidays on my holiday table and there is a weekend 11/19 and 11/20. 5x540=2700

Thanks again for the help!
 
Last edited:
kickstand07,

I have provided two methods for you to try my code.

You can copy the code below and paste it into a new module. Save the module. I saved mine as "modCalcElapsedMinutes", (You can use any name you choose as long as you do not name it the same as the Public Function name of "ElapsedMinutes") or you can simply import the attached .bas file to create the new module.

In this code there is a Public Sub named "CalcElapsedMinutes" that you can use to test the user defined function named "ElapsedMinutes", and the actual user defined function named "ElapsedMinutes". The "ElapsedMinutes" function requires two parameters to be passed to it, a Starting Date and Time and an Ending Date and time. You can see the format of these dates in the testing public sub.

Try this out and let me know what you think. I have tested it using the public sub and changing the parameters and by using the "ElapsedMinutes" function in a query and it appears to work as designed but I could have missed something. The function can be modified as required if needed.

Code:
Option Compare Database
Option Explicit

Public Sub CalcElapsedMinutes()
'DEVELOPER"S NOTE:
'  This sub is to be used for testing only

'the variable below is used to hold the returned number of elapsed minutes
Dim lngMinutesUsed As Long
'the two variables below are used here to allow the assignment of the
'starting date and time and the ending date and time that will be
'passed to the "ElpasedMinutes" user defined function

Dim dtStartDateTime As Date
Dim dtEndDateTime As Date

'DEVELOPER'S NOTE:
'   For testing, just change the two dates below as desired
'the line below simply assigsn a starting date and time to the variable
dtStartDateTime = "5/18/2012 08:00:00"
'the line below simply assigsn a ending date and time to the variable
dtEndDateTime = "5/21/2012 9:15:00"

'the line below provides the two variable parameters and calls
'the "ElapsedMinutes" function and assign the value returned by
'this function to the "lngMinutesUsed" variable

'use the line below in the actual code where you want the
'calculated elpased minutes to be available
lngMinutesUsed = ElapsedMinutes(dtStartDateTime, dtEndDateTime)

'DEVELOPER'S NOTE:
'  if you want to use the "ElapsedMinutes" function in a query, just
'  call the function providing the starting and ending dates.  See line below:
'  UsedMinutes: ElapsedMinutes([tblName]![StartDateFieldName], [tblName]![EndDateFieldName])

'the line below prints the returned number of minutes to the Immediate window
Debug.Print lngMinutesUsed
End Sub


Public Function ElapsedMinutes(dtStartDateTime As Date, dtEndDateTime As Date) As Long
'*****************************************************************************
'*  This function was writting by Byron Polk and is designed to return the   *
'*  the number of elasped minute between two dates.  It assumes that there   *
'*  area only 9 working hours in eadh day (8:00 AM to 5:00 PM and is         *
'*  designed to measure only hours worked on workdays (no Saturdays or       *
'*  sundays.  Is also excludes all holidays as defined in the "tblHolidays"  *
'*  table. (If your holidays table is named diferently, change this table    *
'*  name in the code below.)                                                 *
'*****************************************************************************

'multiple variables are declared and are used to hold information
'as the calculation of the elapsed minutes is being completed
Dim dtWkDayStartDateTime As Date
Dim dtWkDayEndDateTime As Date
Dim dtDateToValidate As Date

'the variable below is only used to hold the calculated Hours and
'minutes string.  If the string values are not used then this
'variable can be commented out
Dim strHrsAndMinutes As String

Dim lngDayCnt As Long
Dim lngDayHrs As Long
Dim lngUsedHrs As Long
Dim lngMinsDay1 As Long
Dim lngMinsDay2 As Long
Dim lngUsedMin As Long
Dim lngMinsLeft As Long

Dim tmpDateTime, cntr, varDayVal, varRecCnt

'This function assumes that a Starting Date and Time and and
'       Ending Date and Time are being passed to it
'It also assumes a workday starting time of 8:00 AM and
'       no minutes should be counted prior to 8:00 AM
'It also assumes a workday ending time of 5:00 PM and
'       no minutes should be counted after 5:00 PM
'get the number of days between the start and ending dates
lngDayCnt = DateDiff("d", dtStartDateTime, dtEndDateTime)
'the "varDayCnt" variable will hold a zero if the start
'and end dates are in the same day.
If lngDayCnt = 0 Then
    'because only one day is involved between starting and ending
    'declare the actual start date and time
    tmpDateTime = Format(dtStartDateTime, "mm/dd/yyyy") & " " & "08:00:00"
    dtWkDayStartDateTime = tmpDateTime
    If dtStartDateTime < dtWkDayStartDateTime Then
        'set the start time to be 8:00 AM
        dtStartDateTime = dtWkDayStartDateTime
    End If
    'declare the ending time
    tmpDateTime = Format(dtEndDateTime, "mm/dd/yyyy") & " " & "17:00:00"
    dtWkDayEndDateTime = tmpDateTime
    If dtEndDateTime > dtWkDayEndDateTime Then
        'set the end time to be 5:00 PM
        dtEndDateTime = dtWkDayEndDateTime
    End If
    lngUsedMin = DateDiff("n", dtStartDateTime, dtEndDateTime)
Else
    'because there are at least two days involved, a starting date
    'and time and an ending date and time
    'we will, first, just calculate the minutes used in the first day
    'use the start date and time as passed to the function
    'declare the ending time as 5:00 PM
    tmpDateTime = Format(dtStartDateTime, "mm/dd/yyyy") & " " & "17:00:00"
    dtWkDayEndDateTime = tmpDateTime
    'get the minutes between the starting time and the end of the day
    lngMinsDay1 = DateDiff("n", dtStartDateTime, dtWkDayEndDateTime)
    
    'Next we will calculate the minutes used during the ending day
    'declare the starting time as 8:00 AM
    tmpDateTime = Format(dtEndDateTime, "mm/dd/yyyy") & " " & "08:00:00"
    dtWkDayStartDateTime = tmpDateTime
    'get the minutes between the starting time and the end of the day
    'and add those minutes to the minutes used the first day
    'Dim lngEndDayMinutes As Long
    'lngEndDayMinutes = DateDiff("n", dtWkDayStartDateTime, dtEndDateTime)
    lngMinsDay2 = DateDiff("n", dtWkDayStartDateTime, dtEndDateTime)
    
    'get the total minutes used in the start day and the end day
    lngUsedMin = lngMinsDay1 + lngMinsDay2
    'using the day count we will need to check to see if any of the days between
    'the starting and ending days are weekend days or holiday days
    'Note: the Holiday days are defined in the tblHolidays table
    If lngDayCnt > 1 Then   'this will be done only if there is more than 1 day
                            'between the starting and ending date
        For cntr = 1 To lngDayCnt - 1
            'calculate the date of the next day
            dtDateToValidate = Format(DateAdd("d", cntr, dtStartDateTime), "mm/dd/yyyy")
            'check to see if the calculated "Date to Validate" is a holiday
            'DEVELOPERS'NOTE:
            '   if the table that holds the definition of your holidays is named
            '   different or your field names are different, change the values
            '   in the line below as appropriate.
            varRecCnt = DCount("HolidayID", "tblHolidays", "HolidayDate = #" & dtDateToValidate & "#")
            If varRecCnt = 0 Then   'if the calculated date is not a holiday
                'check to see if the calculated "Date to Validate" is a weekend day
                varDayVal = Weekday(dtDateToValidate)
                'if the day is a weekday declare 8 hours used for that day
                If varDayVal > 1 And varDayVal < 7 Then
                    lngDayHrs = lngDayHrs + 9
                End If
            End If
        Next cntr
        
    End If
    'add the 8 hours per elapsed day to the used start day and end day minutes
    lngUsedMin = lngUsedMin + (lngDayHrs * 60)
End If
'return the calsulated used minutes to the function
ElapsedMinutes = lngUsedMin

'DEVELOPER'S NOTE:
'  The code below here is designed to calculate the Hours and remaining
'  minutes and then concatenate a string that is then printed to the
'  immediate window to show the hours and minutes used.
'  If the string value is not needed or the calculation of the hours
'  is not needed then the code below can simply be commented out

'get the difference in the start and end dates in minutes for
'the time used in the last day
lngUsedHrs = (lngUsedMin / 60)
'calculate the remaining miuntes
lngMinsLeft = lngUsedMin - (lngUsedHrs * 60)
'create the string to be returned based on
'wheather more than 2 hours were used
If lngUsedHrs < 2 Then
    strHrsAndMinutes = "It took " & lngUsedHrs & " hour"
Else
    strHrsAndMinutes = "It took " & lngUsedHrs & " hours"
End If
If lngMinsLeft > 0 Then
    strHrsAndMinutes = strHrsAndMinutes & " and " & lngMinsLeft & " minutes."
Else
    strHrsAndMinutes = strHrsAndMinutes & "."
End If
Debug.Print "Total elapsed minutes was " & lngUsedMin & " miunues."
Debug.Print strHrsAndMinutes
End Function

If you have any questions please post back here.
 
Mr. B thanks a lot for looking at this for me. In order to get your function to work in a query i had to change the holiday table name, and holiday field name to match mine. Otherwise i ran it as is.
In a query of 11/17/2011 12:36:03 AM 11/29/2011 3:36:55 PM the function returned 4140 where it should be returning 3696. (there are 6 off days including two weekends and 2 holidays)

It does not appear to be working as intended, so far. Please let me know if i made an error in setting this up, and thank you so much for your help
 
I tweaked the code a little in the attached. Let me know how it works for you.
 

Attachments

Jzwp22,
As far as i can tell, that function is working beautifully! I am still going through it to understand the logic a bit better, and ill continue spot checking the function.

Really appreciate you helping me here, this community has been great to me already!
 
You're welcome. Let us know if you encounter any other issues with the function.
 
so with this elapsed minutes DB can it used for a gnatt like chart? or do you have any ideas of how to begin creating a gnatt style chart. because im trying to create one. i want the months and days to go across the top the individuals names down the left and the "bar graph" to adjust with a table that has start and end dates
 
It sounds like you will need to create a cross tab query to make something that looks like a Gantt Chart; unfortunately, I am not very good with cross tab queries.
 

Users who are viewing this thread

Back
Top Bottom