I found the below TSQL a fairly elegant solution (I don't have permissions to run functions). I found the DATEDIFF ignores DATEFIRST and I wanted my first day of the week to be a Monday. I also wanted the first working day to be set a zero and if it falls on a weekend Monday will be a zero. This may help someone who has a slightly different requirement :)
It does not handle bank holidays
SET DATEFIRST 1
SELECT
,(DATEDIFF(DD, [StartDate], [EndDate]))
-(DATEDIFF(wk, [StartDate], [EndDate]))
-(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate]))) AS [WorkingDays]
FROM /*Your Table*/
As with DATEDIFF, I do not consider the end date to be part of the interval.
The number of (for example) Sundays between @StartDate and @EndDate is the number of Sundays between an "initial" Monday and the @EndDate minus the number of Sundays between this "initial" Monday and the @StartDate. Knowing this, we can calculate the number of workdays as follows:
CREATE FUNCTION x
(
@StartDate DATETIME,
@EndDate DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE @Teller INT
SET @StartDate = DATEADD(dd,1,@StartDate)
SET @Teller = 0
IF DATEDIFF(dd,@StartDate,@EndDate) <= 0
BEGIN
SET @Teller = 0
END
ELSE
BEGIN
WHILE
DATEDIFF(dd,@StartDate,@EndDate) >= 0
BEGIN
IF DATEPART(dw,@StartDate) < 6
BEGIN
SET @Teller = @Teller + 1
END
SET @StartDate = DATEADD(dd,1,@StartDate)
END
END
RETURN @Teller
END
All Credit to Bogdan Maxim & Peter Mortensen. This is their post, I just added holidays to the function (This assumes you have a table "tblHolidays" with a datetime field "HolDate".
--Changing current database to the Master database allows function to be shared by everyone.
USE MASTER
GO
--If the function already exists, drop it.
IF EXISTS
(
SELECT *
FROM dbo.SYSOBJECTS
WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')
AND XType IN (N'FN', N'IF', N'TF')
)
DROP FUNCTION [dbo].[fn_WorkDays]
GO
CREATE FUNCTION dbo.fn_WorkDays
--Presets
--Define the input parameters (OK if reversed by mistake).
(
@StartDate DATETIME,
@EndDate DATETIME = NULL --@EndDate replaced by @StartDate when DEFAULTed
)
--Define the output data type.
RETURNS INT
AS
--Calculate the RETURN of the function.
BEGIN
--Declare local variables
--Temporarily holds @EndDate during date reversal.
DECLARE @Swap DATETIME
--If the Start Date is null, return a NULL and exit.
IF @StartDate IS NULL
RETURN NULL
--If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).
IF @EndDate IS NULL
SELECT @EndDate = @StartDate
--Strip the time element from both dates (just to be safe) by converting to whole days and back to a date.
--Usually faster than CONVERT.
--0 is a date (01/01/1900 00:00:00.000)
SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0),
@EndDate = DATEADD(dd,DATEDIFF(dd,0,@EndDate) , 0)
--If the inputs are in the wrong order, reverse them.
IF @StartDate > @EndDate
SELECT @Swap = @EndDate,
@EndDate = @StartDate,
@StartDate = @Swap
--Calculate and return the number of workdays using the input parameters.
--This is the meat of the function.
--This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes.
RETURN (
SELECT
--Start with total number of days including weekends
(DATEDIFF(dd,@StartDate, @EndDate)+1)
--Subtact 2 days for each full weekend
-(DATEDIFF(wk,@StartDate, @EndDate)*2)
--If StartDate is a Sunday, Subtract 1
-(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday'
THEN 1
ELSE 0
END)
--If EndDate is a Saturday, Subtract 1
-(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday'
THEN 1
ELSE 0
END)
--Subtract all holidays
-(Select Count(*) from [DB04\DB04].[Gateway].[dbo].[tblHolidays]
where [HolDate] between @StartDate and @EndDate )
)
END
GO
-- Test Script
/*
declare @EndDate datetime= dateadd(m,2,getdate())
print @EndDate
select [Master].[dbo].[fn_WorkDays] (getdate(), @EndDate)
*/
DECLARE
@StartDate date = '2014-01-01',
@EndDate date = '2014-01-31';
SELECT
COUNT(*) As NumberOfWeekDays
FROM dbo.Calendar
WHERE CalendarDate BETWEEN @StartDate AND @EndDate
AND IsWorkDay = 1;
If you don't have that, you can use a numbers table:
DECLARE
@StartDate datetime = '2014-01-01',
@EndDate datetime = '2014-01-31';
SELECT
SUM(CASE WHEN DATEPART(dw, DATEADD(dd, Number-1, @StartDate)) BETWEEN 2 AND 6 THEN 1 ELSE 0 END) As NumberOfWeekDays
FROM dbo.Numbers
WHERE Number <= DATEDIFF(dd, @StartDate, @EndDate) + 1 -- Number table starts at 1, we want a 0 base
They should both be fast and it takes out the ambiguity/complexity. The first option is the best but if you don't have a calendar table you can allways create a numbers table with a CTE.
This is basically CMS's answer without the reliance on a particular language setting. And since we're shooting for generic, that means it should work for all @@datefirst settings as well.
datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
/* if start is a Sunday, adjust by -1 */
+ case when datepart(weekday, <start>) = 8 - @@datefirst then -1 else 0 end
/* if end is a Saturday, adjust by -1 */
+ case when datepart(weekday, <end>) = (13 - @@datefirst) % 7 + 1 then -1 else 0 end
datediff(week, ...) always uses a Saturday-to-Sunday boundary for weeks, so that expression is deterministic and doesn't need to be modified (as long as our definition of weekdays is consistently Monday through Friday.) Day numbering does vary according to the @@datefirst setting and the modified calculations handle this correction with the small complication of some modular arithmetic.
A cleaner way to deal with the Saturday/Sunday thing is to translate the dates prior to extracting a day of week value. After shifting, the values will be back in line with a fixed (and probably more familiar) numbering that starts with 1 on Sunday and ends with 7 on Saturday.
datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
+ case when datepart(weekday, dateadd(day, @@datefirst, <start>)) = 1 then -1 else 0 end
+ case when datepart(weekday, dateadd(day, @@datefirst, <end>)) = 7 then -1 else 0 end
I've tracked this form of the solution back at least as far as 2002 and an Itzik Ben-Gan article. (https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx) Though it needed a small tweak since newer date types don't allow date arithmetic, it is otherwise identical.
EDIT:
I added back the +1 that had somehow been left off. It's also worth noting that this method always counts the start and end days. It also assumes that the end date is on or after the start date.
I found the below TSQL a fairly elegant solution (I don't have permissions to run functions). I found the
DATEDIFF
ignoresDATEFIRST
and I wanted my first day of the week to be a Monday. I also wanted the first working day to be set a zero and if it falls on a weekend Monday will be a zero. This may help someone who has a slightly different requirement :)It does not handle bank holidays
As with DATEDIFF, I do not consider the end date to be part of the interval. The number of (for example) Sundays between @StartDate and @EndDate is the number of Sundays between an "initial" Monday and the @EndDate minus the number of Sundays between this "initial" Monday and the @StartDate. Knowing this, we can calculate the number of workdays as follows:
Best regards!
All Credit to Bogdan Maxim & Peter Mortensen. This is their post, I just added holidays to the function (This assumes you have a table "tblHolidays" with a datetime field "HolDate".
Using a date table:
If you don't have that, you can use a numbers table:
They should both be fast and it takes out the ambiguity/complexity. The first option is the best but if you don't have a calendar table you can allways create a numbers table with a CTE.
This is basically CMS's answer without the reliance on a particular language setting. And since we're shooting for generic, that means it should work for all
@@datefirst
settings as well.datediff(week, ...)
always uses a Saturday-to-Sunday boundary for weeks, so that expression is deterministic and doesn't need to be modified (as long as our definition of weekdays is consistently Monday through Friday.) Day numbering does vary according to the@@datefirst
setting and the modified calculations handle this correction with the small complication of some modular arithmetic.A cleaner way to deal with the Saturday/Sunday thing is to translate the dates prior to extracting a day of week value. After shifting, the values will be back in line with a fixed (and probably more familiar) numbering that starts with 1 on Sunday and ends with 7 on Saturday.
I've tracked this form of the solution back at least as far as 2002 and an Itzik Ben-Gan article. (https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx) Though it needed a small tweak since newer
date
types don't allow date arithmetic, it is otherwise identical.EDIT: I added back the
+1
that had somehow been left off. It's also worth noting that this method always counts the start and end days. It also assumes that the end date is on or after the start date.