I currently have a function in my SQL database that adds a certain amount of business days to a date, e.g. if you enter a date that is a Thursday and add two days, it will r
This answer has been significantly altered since it was accepted, since the original was wrong. I'm more confident in the new query though, and it doesn't depend on DATEFIRST
I think this should cover it:
declare @fromDate datetime
declare @daysToAdd int
select @fromDate = '20130123',@DaysToAdd = 4
declare @Saturday int
select @Saturday = DATEPART(weekday,'20130126')
;with Numbers as (
select 0 as n union all select 1 union all select 2 union all select 3 union all select 4
), Split as (
select @DaysToAdd%5 as PartialDays,@DaysToAdd/5 as WeeksToAdd
), WeekendCheck as (
select WeeksToAdd,PartialDays,MAX(CASE WHEN DATEPART(weekday,DATEADD(day,n.n,@fromDate))=@Saturday THEN 1 ELSE 0 END) as HitWeekend
from
Split t
left join
Numbers n
on
t.PartialDays >= n.n
group by WeeksToAdd,PartialDays
)
select DATEADD(day,WeeksToAdd*7+PartialDays+CASE WHEN HitWeekend=1 THEN 2 ELSE 0 END,@fromDate)
from WeekendCheck
We split the time to be added into a number of weeks and a number of days within a week. We then use a small numbers table to work out if adding those few days will result in us hitting a Saturday. If it does, then we need to add 2 more days onto the total.
This answers is based on @ElmerMiller's answer.
It fixes the negative value on Sunday comment from @FistOfFury
Negative values don't work if the date passed in is Sunday
And the DATEFIRST setting comment from @Damien_The_Unbeliever
But this one does assume a particular DATEFIRST setting (7), which some of the others don't need.
Now the corrected function
CREATE FUNCTION[dbo].[AddBusinessDays](@Date DATE,@n INT)
RETURNS DATE AS
BEGIN
DECLARE @d INT,@f INT,@DW INT;
SET @f=CAST(abs(1^SIGN(DATEPART(DW, @Date)-(7-@@DATEFIRST))) AS BIT)
SET @DW=DATEPART(DW,@Date)-(7-@@DATEFIRST)*(@f^1)+@@DATEFIRST*(@f&1)
SET @d=4-SIGN(@n)*(4-@DW);
RETURN DATEADD(D,@n+((ABS(@n)+(@d%(8+SIGN(@n)))-2)/5)*2*SIGN(@n)-@d/7,@Date);
END
CREATE FUNCTION DateAddBusinessDays
(
@Days int,
@Date datetime
)
RETURNS datetime
AS
BEGIN
DECLARE @DayOfWeek int;
SET @DayOfWeek = CASE
WHEN @Days < 0 THEN (@@DateFirst + DATEPART(weekday, @Date) - 20) % 7
ELSE (@@DateFirst + DATEPART(weekday, @Date) - 2) % 7
END;
IF @DayOfWeek = 6 SET @Days = @Days - 1
ELSE IF @DayOfWeek = -6 SET @Days = @Days + 1;
RETURN @Date + @Days + (@Days + @DayOfWeek) / 5 * 2;
END;
This function can add and subtract business days regardless of the value of @@DATEFIRST. To subtract business days use a negative number of days.
Thanks Damien for the code. There was a slight error in the calcs in that it added only 1 day for the sunday, and that when the number of business days crossed a weekend (but did not land in the weekend) the extra 2 days was not taken into account. Here is a modified version of Damiens code that works with the default datefirst at 7. Hope this helps.
CREATE FUNCTION [dbo].[fn_AddBusinessDays]
(
@StartDate datetime,
@BusinessDays int
)
RETURNS datetime
AS
BEGIN
DECLARE @EndDate datetime
SET @EndDate = DATEADD(day, @BusinessDays%5 +
CASE
WHEN DATEPART(weekday,@StartDate) + @BusinessDays%5 > 6 THEN 2
ELSE 0
END,
DATEADD(week,@BusinessDays/5,@StartDate))
RETURN @EndDate
END
GO
I have tested all of the solutions proposed here and none of them work. Here are some test scenarios that broke a lot of the above solutions. (assuming Saturday and Sunday are the days you are excluding):
-Add 0 days to a Saturday - Expected result = Saturday
-Add 0 days to a Sunday - Expected result = Sunday
-Add 1 day to Friday - Expected result = the following Monday
-Add 1 day to Saturday - Expected result = the following Monday
-Add 1 day to Sunday - Expected result = the following Monday
-Add 3 days to Friday - Expected result = the following Wednesday
-Add 5 days to Saturday - Expected result = the following Friday
-Add 5 days to Friday - Expected result = the following Friday
-Subtract 1 day from Monday - Expected result = the previous Friday
-Subtract 1 day from Sunday - Expected result = the previous Friday
-Subtract 1 day from Saturday - Expected result = the previous Friday
-Subtract 3 days from Monday - Expected result = the previous Wednesday
-Subtract 5 days from Saturday - Expected result = the previous Monday
-Subtract 5 days from Monday - Expected result = the previous Monday
Here is what I wrote after reading this entire thread and picking the good pieces of logic:
CREATE FUNCTION [dbo].[BusinessDateAdd]
(
@FromDate DATE
,@DaysToAdd INT
)
RETURNS DATE
AS
BEGIN
--If there are no days to add or subtract, return the day that was passed in
IF @DaysToAdd = 0 RETURN @FromDate
DECLARE @Weeks INT
DECLARE @DMod INT
DECLARE @FromDateIndex INT
--number of weeks
SET @Weeks = @DaysToAdd/5
--remainder of days
SET @dmod = @DaysToAdd%5
--Get the FromDate day of the week, this logic standardizes the @@DateFirst to Sunday = 1
SET @FromDateIndex = (DATEPART(weekday, @FromDate) + @@DATEFIRST - 1) % 7 + 1
/*Splitting the addition vs subtraction logic for readability*/
--Adding business days
IF @DaysToAdd > 0
BEGIN
--If the FromDate is on a weekend, move it to the previous Friday
IF @FromDateIndex IN(1,7)
BEGIN
SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN -2 WHEN 7 THEN -1 END,@FromDate)
SET @FromDateIndex = 6
END
SET @FromDate = DATEADD(dd,
CASE
--If the mod goes through the weekend, add 2 days to account for it
WHEN
((@FromDateIndex = 3 --Tuesday
AND @dmod > 3) --Days until Friday
OR
(@FromDateIndex = 4 --Wednesday
AND @dmod > 2)--Days until Friday
OR
(@FromDateIndex = 5 --Thursday
AND @dmod > 1)--Days until Friday
OR
(@FromDateIndex = 6 --Friday
AND @dmod > 0))--Days until Friday
THEN
@DMod+2
--Otherwise just add the mod
ELSE
@DMod
END, @FromDate)
END
--Subtracting business days
IF @DaysToAdd < 0
BEGIN
--If the FromDate is on a weekend, move it to the next Monday
IF @FromDateIndex IN(1,7)
BEGIN
SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN 1 WHEN 7 THEN 2 END,@FromDate)
SET @FromDateIndex = 2
END
SET @FromDate = DATEADD(dd,
CASE
--If the mod goes through the weekend, subtract 2 days to account for it
WHEN
((@FromDateIndex = 5 --Thursday
AND @dmod < -3) --Days until Monday
OR
(@FromDateIndex = 4 --Wednesday
AND @dmod < -2)--Days until Monday
OR
(@FromDateIndex = 3 --Tuesday
AND @dmod < -1)--Days until Monday
OR
(@FromDateIndex = 2 --Monday
AND @dmod < 0))--Days until Monday
THEN
@DMod-2
--Otherwise just subtract the mod
ELSE
@DMod
END, @FromDate)
END
--Shift the date by the number of weeks
SET @FromDate = DATEADD(ww,@Weeks,@FromDate)
RETURN @FromDate
END
To expand on Amine's comment and Nate cook's answer above, the one-liner solution to this is:
declare @DaysToAdd int , @FromDate datetime
set @DaysToAdd=-5 --5 days prior is 3/28/14
set @FromDate='4/4/14'
select
DATEADD(day, (@DaysToAdd % 5)
+ CASE
WHEN ((@@DATEFIRST + DATEPART(weekday, @FromDate)) % 7 + (@DaysToAdd % 5)) > 6 THEN 2
ELSE 0
END
, DATEADD(week, (@DaysToAdd / 5), @FromDate))
Note you can add or subtract days to go forwards and backwards in time, respectively.