HOWTO: Calculate Elapsed Business Hours Using PowerShell

Have you ever needed to calculate how many business hours / working hours have elapsed between a specific date and now?  For example, you may have some metric that says something needs to happen within 4 business hours.  We define business hours in this case as 8AM to 5PM Monday to Friday and does not include statutory holidays.  How might you go about doing that?

I found myself in this situation and figured it would be common and spent far too much time Googling trying to find someone else who built a solution for this problem.  To my surprise, I was unable to find a single working example for PowerShell.  Lots of examples for other languages such as T-SQL but nothing for PowerShell.  I was therefore forced to solve this myself.

Hopefully this will help you if you ever find yourself in a similar situation.

Before we get into the code, I’d like to start with a quick demo.  In my situation, we have a ticketing system and we want to kick off a process any time a ticket remains untouched for more than 4 business hours so we need to determine how many business hours have elapsed since the last touchpoint on the ticket.

In our example, the last time the ticket was interacted with was on December 21st, 2018 at 14:43:11.  The current date in this example is Friday January 4th, 2019 at 13:41:56.

As we see in the screenshot below, after the ticket was last touched, 2 hours 16 minutes and 49 seconds elapsed until the end of that business day.  Since this was a Friday the next 2 days were Saturday and Sunday and so we don’t add up these times.  The next week is actually the Christmas break so we also don’t want to count any of these.

The first official day back is Wednesday January 2nd, 2019.  Since the current time is newer than this we can simply add in a full 8 hours.  We do the same for the 3rd.  Finally on the 4th, since the current time is 1:41PM, that means we have already elapsed an additional 5 hours 41 minutes since 8AM.

image

Putting it all together, we see that 23 hours 58 minutes and 45 seconds worth of business hours have elapsed since 12/21/2018 @ 2:43PM.

Now that we have a baseline, how do we implement this logic in code?

We can do that by using the function below called Get-BusinessHoursElapsed.  As you can see below, by default it returns a timespan object.

image

However at least in my case, it would be much more useful if it just returned the actual time in the format of hh:mm:ss.  So to do that you can add in a parameter –ReturnString.   You’ll note that it returns the total number of hours and does not rollover into including days if it exceeds 24 hours.

image

If you need to implement something like this, the code is below:

# Calculate the number of business hours elapsed since a specified date
# Business Hours; Calculate; Working Hours; Working Days; Holidays

Function Get-WorkingDays
{
    Param 
    (
        [Parameter(Position=0,Mandatory=$True)]
        [DateTime]$Startdate,
        [Parameter(Position=1,Mandatory=$False)]
        [DateTime]$Enddate
    )
    
    $StartHour = 8
    $FinishHour = 17

    # Enter whatever dates here you do not want to be included as part of your business days/hours calculation
    $holidays = @(
        (Get-Date -Date '2018-12-24')            # Labour Day
        (Get-Date -Date '2018-12-25')            # Thanksgiving
        (Get-Date -Date '2018-12-26')            # Christmas Holidays
        (Get-Date -Date '2018-12-27')            # Christmas Holidays
        (Get-Date -Date '2018-12-28')            # Christmas Holidays
        (Get-Date -Date '2018-12-29')            # Christmas Holidays
        (Get-Date -Date '2018-12-30')            # Christmas Holidays
        (Get-Date -Date '2018-12-31')            # Christmas Holidays
        (Get-Date -Date '2019-01-01')            # Christmas Holidays
        (Get-Date -Date '2019-02-18')            # Family Day
        (Get-Date -Date '2019-04-19')            # Good Friday
        (Get-Date -Date '2019-05-20')            # Victoria Day
        (Get-Date -Date '2019-07-01')            # Canada Day
        (Get-Date -Date '2019-08-05')            # Civic Holiday
        (Get-Date -Date '2019-09-02')            # Labour Day
        (Get-Date -Date '2019-10-14')            # Thanksgiving
        (Get-Date -Date '2019-12-25')            # Christmas Holidays
        (Get-Date -Date '2019-12-26')            # Christmas Holidays
        (Get-Date -Date '2019-12-27')            # Christmas Holidays
        (Get-Date -Date '2019-12-28')            # Christmas Holidays
        (Get-Date -Date '2019-12-29')            # Christmas Holidays
        (Get-Date -Date '2019-12-30')            # Christmas Holidays
        (Get-Date -Date '2019-12-31')            # Christmas Holidays
        (Get-Date -Date '2019-01-01')            # Christmas Holidays
    )

    # If both a startdate and an enddate are provided, return the number of business days between the two
    if($Enddate)
    {
        $difference = New-TimeSpan -Start $startdate -End $enddate
        $days = [Math]::Ceiling($difference.TotalDays)

        $workdays = (1..$days) | ForEach-Object {
            $startdate
            $startdate = $startdate.AddDays(1)
        } | Where { $_.DayOfWeek -gt 0 -and $_.DayOfWeek -lt 6 -and $Holidays -notcontains $_.date } | Measure-Object | Select-Object -ExpandProperty Count

        return $Workdays
    } 
    
    # If an enddate is not provided return if the start date is within working hours or not
    else
    {
        $DayStatus = $startdate | Where { 
            $_.DayOfWeek -gt 0 -and # Not Sunday
            $_.DayOfWeek -lt 6 -and  # Not Saturday
            $Holidays -notcontains $_.date -and 
            $startdate.hour -ge $StartHour -and 
            $startdate.hour -lt $FinishHour 
        } | Measure-Object | Select -ExpandProperty Count
    
        if($DayStatus -eq 1) { $true} else {$false}
    }
}

Function Get-BusinessHoursElapsed([datetime]$CompareDate, [switch]$ReturnString )
{
    # Define when you want to start and end the day using military time
    [datetime]$StartofDay = '8:00:00'
    [datetime]$EndofDay = '17:00:00'

    $Now = Get-Date "1/4/2019 13:41:56"

    # If the specified date falls within working hours, add up the total amount of time elapsed since the day begin and subtract the time from the end of the day that hasn't elapsed yet
    if((Get-WorkingDays $CompareDate)) { $ElapsedTime = ($EndofDay.TimeOfDay - $CompareDate.TimeOfDay) }
    if((Get-WorkingDays $Now)) { $ElapsedTime = $ElapsedTime + ($Now.TimeOfDay - $StartofDay.TimeOfDay) }

    # Get the total number of working days between the specified date and now and add 8 hours for each working day to our total
    $WorkingDays = Get-WorkingDays $CompareDate $Now
    $InBetweenHours = (($WorkingDays) - 1) * 8
    $ElapsedTime = $ElapsedTime.add((New-TimeSpan -Hours $InBetweenHours))


    # Format the output so that it looks like an elapsed time including leading zeros where required
    if($ReturnString)
    {
        # We want our final output to include only hours.  We do NOT want it to show days if it goes past 24 hours which is the default behavior so we'll convert any days into hours
        $Hours = (($ElapsedTime.Days * 24) + $ElapsedTime.hours)

        $Hours = $Hours.tostring("00")
        $Minutes = $($ElapsedTime.Minutes).tostring("00")
        $Seconds = $($ElapsedTime.Seconds).tostring("00")
        $ElapsedTime = "$Hours`:$Minutes`:$Seconds"
        
        return $ElapsedTime
    }
    else
    {
        return $ElapsedTime
    }
    
}

Get-BusinessHoursElapsed (Get-Date "12/21/2018 14:43:11") -ReturnString

2 comments

    • Ashish on March 27, 2019 at 11:18 am
    • Reply

    Hi One correction – issue when “compare date” and “Today” are the same.
    ———————————–
    if((Get-WorkingDays $Now)) {
    if ($CompareDate.ToString(“yyyy-M-dd”) -eq $Now.ToString(“yyyy-M-dd”)) {
    $ElapsedTime = ($Now.TimeOfDay – $StartofDay.TimeOfDay);
    }else {
    $ElapsedTime = $ElapsedTime + ($Now.TimeOfDay – $StartofDay.TimeOfDay);
    }
    }

    • Patil on August 23, 2020 at 3:09 pm
    • Reply

    Its not working when holiday comes in between week. Please update the code.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.