How can I calculate business hours between two dates? For example we have two dates; 01/01/2010 15:00 and 04/01/2010 12:00 And we have working hours 09:00 to 17:00 in weekda
An alternative solution from @Pavanred's, coming at things from a more data-based angle:
Create a table with all the dates you want to consider in it. For each day, set a number of working hours, like so:
WorkingDate Hours Comment
=========== ===== ==================
1 Jan 2011 0 Saturday
2 Jan 2011 0 Sunday
3 Jan 2011 0 Public Holiday
4 Jan 2011 8 Normal working day
5 Jan 2011 8 Normal working day
-- and so on, for all the days you want to report on.
This will take a small amount of setting up -- you can pre-populate it for weeks versus weekends automatically, then adjust for public holidays, etc, as necessary.
But, what you lose in the setting up, you gain in ease of querying:
SELECT
SUM(Hours)
FROM
working_days
WHERE
WorkingDate BETWEEN @StartDate AND @EndDate
...and this can work out as an easier approach if you need to start adding more complicated rules for what defines a working day, or if your working hours vary depending on the day, etc.
It also makes the rules more easily "editable", as you don't need to change any actual code to change the definitions of a working day, add public holidays, etc.
A version I wrote today taking into account bank holidays. Note: This is not thoroughly tested and could no doubt be improved.
CREATE FUNCTION [dbo].[WorkingHoursBetween2Dates]
(
@dtFrom datetime,
@dtTo datetime
)
RETURNS INT
BEGIN
DECLARE @tblDates AS TABLE (DateValue DATE)
DECLARE @dFrom date = @dtFrom
DECLARE @dTo date = @dtTo
DECLARE @intDays int
DECLARE @intHours int = 0
DECLARE @dFromWorkday bit = CASE WHEN (DATENAME(WEEKDAY, @dFrom) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dFrom) THEN 0 ELSE 1 END
DECLARE @dToWorkday bit = CASE WHEN (DATENAME(WEEKDAY, @dTo) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dTo) THEN 0 ELSE 1 END
IF DATEPART(HOUR,@dtFrom) < 9
SET @dtFrom = DATEADD(HOUR,9,CAST(CAST(@dtFrom AS DATE) AS DATETIME))
ELSE
IF DATEPART(HOUR,@dtFrom) > 17
SET @dtFrom = DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME))
IF DATEPART(HOUR,@dtTo) < 9
SET @dtTo = DATEADD(HOUR,9,CAST(CAST(@dtTo AS DATE) AS DATETIME))
ELSE
IF DATEPART(HOUR,@dtTo) > 17
SET @dtTo = DATEADD(HOUR,17,CAST(CAST(@dtTo AS DATE) AS DATETIME))
WHILE @dFrom <= @dTo
BEGIN
INSERT INTO @tblDates
(
DateValue
)
SELECT @dFrom
WHERE NOT ((DATENAME(WEEKDAY, @dFrom) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dFrom))
SET @dFrom = DATEADD(DAY,1,@dFrom)
END
SET @intDays = CASE WHEN EXISTS(SELECT * FROM @tblDates) THEN (SELECT COUNT(*) FROM @tblDates) - 1 ELSE 0 END
IF @intDays = 0
BEGIN
IF @dFromWorkday = 1
IF DATEPART(HOUR,@dtFrom) < 17
BEGIN
IF DATEDIFF(DAY,@dtFrom,@dtTo)=0
SET @intHours = DATEDIFF(HOUR,@dtFrom,@dtTo)
ELSE
SET @intHours = DATEDIFF(HOUR,@dtFrom,DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME)))
END
IF @dToWorkday = 1 AND DATEDIFF(DAY,@dtFrom,@dtTo)<>0
IF DATEPART(HOUR,@dtTo) >= 17
SET @intHours = @intHours + 8
ELSE
IF DATEPART(HOUR,@dtTo) > 9
SET @intHours = @intHours + DATEPART(HOUR,@dtTo) - 9
END
ELSE
BEGIN
IF @dFromWorkday = 1
IF DATEPART(HOUR,@dtFrom) < 17
BEGIN
SET @intHours = DATEDIFF(HOUR,@dtFrom,DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME)))
SET @intDays = @intDays - 1
END
IF DATEPART(HOUR,@dtTo) < 17
SET @intHours = @intHours + (@intDays * 8) + CASE WHEN @dToWorkday = 1 THEN DATEDIFF(HOUR,DATEADD(HOUR,9,CAST(CAST(@dtTo AS DATE) AS DATETIME)), @dtTo) ELSE 0 END
ELSE
SET @intHours = @intHours + ((@intDays + 1) * 8)
END
RETURN (@intHours)
END