Time difference testing

After reading some of the comments on Calculate Working Hours in VBA, I went back and reworked the TimeDiff function. Below is the new and improved WorkingHoursDiff function!

The only requirement is that the start date and end date have to be actual working days. In other words, don't pass weekends or holidays to the function as start or end dates. The function returns -999 if an error occurs, for example if the start date is later than the end date.

It seems the original function returns incorrect working hours. For example, if you have a start date of "2/17/09 12:00PM" and an end date of "2/25/09 4:00PM" and given an 8 hour working day (9AM to 5PM), the number of working hours between those dates and times is 52. WorkingHoursDiff correctly returns 52, while TimeDiff returns 44.

timediff

The new function has the added benefit of using correctly typed variables.

Function WorkingHoursDiff(StartDateAndTime As Date, _
           EndDateAndTime As Date) As Long

' returns -999 if any errors occur

Const HoursPerDay As Integer = 24

' 24-hour military time, edit as needed
' if you want to set different working hours
Const ComeIn As Date = #9:00:00 AM#
Const Leave As Date = #5:00:00 PM#

' these are US Federal Holidays, in mm/dd/yyyy format
' taken from http://www.opm.gov/Operating_Status_Schedules/fedhol/2009.asp
Dim HolidaysList As Variant
HolidaysList = Array("1/1/2009", "1/19/2009", "2/16/2009", _
                          "5/25/2009", "7/3/2009", "9/7/2009", _
                        "10/12/2009", "11/11/2009", "11/26/2009", "12/25/2009")

' get date portion
Dim StartDate As Date
Dim EndDate As Date
StartDate = Int(StartDateAndTime)
EndDate = Int(EndDateAndTime)

' make sure end date is later than start date
If (StartDate > EndDate) Then
  WorkingHoursDiff = -999
  Exit Function
End If

' make sure we aren't starting or ending on a weekend or holiday
Dim ArrMember As Variant

If (Weekday(StartDate, vbMonday) > 6) Or _
    (Weekday(EndDate, vbMonday) > 6) Then
  WorkingHoursDiff = -999
  Exit Function
End If

' loop through holidays and check if startdate or enddate is one of them
For Each ArrMember In HolidaysList
  If (CStr(ArrMember) = CStr(StartDate)) Or _
       (CStr(ArrMember) = CStr(EndDate)) Then
    WorkingHoursDiff = -999
    Exit Function
  End If
Next ArrMember

' get time portion
' http://www.bygsoftware.com/issues/modbug.html
Dim ModStartTime As Date
Dim ModEndTime As Date
ModStartTime = StartDateAndTime - 1 * Int(StartDateAndTime / 1)
ModEndTime = EndDateAndTime - 1 * Int(EndDateAndTime / 1)

' if same day, make sure start time is earlier than end time
If (StartDate = EndDate) Then
  If (ModStartTime > ModEndTime) Then
    WorkingHoursDiff = -999
    Exit Function
  End If
End If

' it's the same day, just calculate simple hours diff
If (EndDate - StartDate) < 1 Then
  WorkingHoursDiff = HoursPerDay * (ModEndTime - ModStartTime)
  Exit Function
End If

' loop through days and sum only business days
Dim DaysCount As Long
Dim bWasFound As Boolean
Dim lCounter As Date
For lCounter = StartDate To EndDate
  If Weekday(lCounter, vbMonday) < 6 Then ' it's a weekday
    For Each ArrMember In HolidaysList
      ' reset to false
      bWasFound = False
      If CStr(ArrMember) = CStr(lCounter) Then
        bWasFound = True
        Exit For
      End If
    Next ArrMember

    If Not bWasFound Then
      DaysCount = DaysCount + 1
    End If
  End If
Next lCounter

' check for no business days in date range
If (DaysCount = 0) Then
  WorkingHoursDiff = -999
  Exit Function
End If

' number of hours per day, based on constants defined above
Dim WorkingHoursPerDay As Long
WorkingHoursPerDay = HoursPerDay * (Leave - ComeIn)

' number of hours on the first and last days
Dim BeginningHours As Long
BeginningHours = WorkingHoursPerDay - (HoursPerDay * (ModStartTime - ComeIn))
Dim EndingHours As Long
EndingHours = WorkingHoursPerDay - (HoursPerDay * (Leave - ModEndTime))

If DaysCount = 2 Then ' one day difference
  WorkingHoursDiff = (BeginningHours + EndingHours)
  Exit Function
End If

' subtract start and end dates from count (2 days), multiply by working hours
' to get number of full working hours between start and end dates, plus the
' amount of time worked on the first and last days
WorkingHoursDiff = _
        ((DaysCount - 2) * WorkingHoursPerDay) + (EndingHours) + (BeginningHours)

End Function

You might notice a slightly different algorithm for this function: no more ATP VBA functions. It's just straight up math this time. The inline comments should explain how it works, but let me know in the comments below if you need clarification.

Related Articles:

About JP

I'm just an average guy who writes VBA code for a living. This is my personal blog. Excel and Outlook are my thing, with a sprinkle of Access and Word here and there. Follow this space to learn more about VBA. Keep Reading »

Share This Article:

Share and bookmark this articledelicious buttonfacebook buttonlinkedin buttonstumbleupon buttontwitter button

comment bubble 8 Comments:

  1. Martin writes:

    Hi JP!
    This is working super great! You're the best!
    I've changed the Function WorkingHoursDiff(ByVal StartDateAndTime As Date, ByVal EndDateAndTime As Date) declaration from Long to Variant to get fractional results. I needed to get minutes as well.
    Thank you so much for all the help! Keep up the excellent work!

  2. JP writes:

    Glad to hear it Martin, good luck!

  3. Cynthia writes:

    Hi JP,

    I've tried this new code – copied from the source and didn't change a thing. In my workbook, my values are:

    H5 = 17/10/2008 5:07:40 PM
    L5 = 24/10/2008 5:11:51 PM
    M5 = 24/10/2008 7:11:25 PM

    If I run WorkingHoursDiff to calculate TAT, I get the following results:

    TAT1: WorkingHoursDiff(H5,L5) = 40.00 (good)
    TAT2: WorkingHoursDiff(L5,M5) = 2.00 (bad)
    TAT3: WorkingHoursDiff(H5,M5) = 42.00 (bad)

    Logic says that TAT2 should equal 0.00, because both times are after 5:00PM, but your same day calculation doesn't appear to take this into account. Also, TAT3 should equal 40.00, since M5 is 2 hours after the 5:00PM EndTime.

    Also, is there a way that I can work around the Business Days only rule, since I often have a Start Time that begins on a weekend, but I don't start counting for my TATs until 900AM on Mondays.

    Am I misunderstanding the intent of this function? Or is there something that I am missing?

    Thanks – Cynthia

  4. JP writes:

    Cynthia – the start and end times have to be within working hours. Try it with a start and end time in between 9:00AM and 5:00PM and it should work.

  5. Kelly writes:

    Hi,

    I'm using this code and it works exactly as needed. However, I want to calculate something else using the same code and was wondering if anyone could help with this.

    What I need is to have a date and time returned. For example, I have a column for "Hours". In one of these cells, I type 4. I need the date and time returned which would be 4 hours back in working hours.

    Could anyone help me with this?

  6. Kelly writes:

    Hi,

    Thanks :) I tried looking at the workdays function. The problem is that I need the exact time returned as well. Basically, I need a date returned "MM/DD/YY hh:mm", so I can use the frequency function to count anything older than that exact date and time. (It's to calculate how many items are outside of a selected turnaround time)

  7. Pri writes:

    hello JP..I am kind of new to vb and my question might seem lame to you guys but could you please help me clear a few doubts?

    1.Dim ModStartTime As Date
    Dim ModEndTime As Date
    ModStartTime = StartDateAndTime – 1 * Int(StartDateAndTime / 1)
    ModEndTime = EndDateAndTime – 1 * Int(EndDateAndTime / 1)

    may i please know what exactly does this block do?(in details if possible)

    2.And about the startdateandtime and enddateandtime do i need to pass these to calculate the working hours diff or would it be possible to fetch them directly from a table i have in oracle?

    I am kind of a dummy right now so all help is welcomed!

    thanks and sorry in advance!

Comments on this article are closed. Why?

Site last updated: February 9, 2012