SQL Server: How Many Work/Week Days In Date Range

I had the need to calculate how many week days existed in an arbitrary range of dates today. I Googled for a while but didn't find anything I liked and I really didn't want to iterate over the entire range and count. For what it's worth, this is what I hacked out.

 CREATE FUNCTION [dbo].[f_week_days_in_period] (@start_date datetime, @end_date datetime)
 RETURNS INT
 AS
 BEGIN
     
     -- If the start date is a weekend, move it foward to the next weekday
     WHILE datepart(weekday, @start_date) in (1,7) -- Sunday, Saturday
     BEGIN
         SET @start_date = dateadd(d,1,@start_date)
10      END
11      
12      -- If the end date is a weekend, move it back to the last weekday
13      WHILE datepart(weekday, @end_date) in (1,7) -- Sunday, Saturday
14      BEGIN
15          SET @end_date = dateadd(d,-1,@end_date)
16      END
17      
18      -- Weekdays are total days in perion minus weekends. (2 days per weekend)
19      -- Extra weekend days were trimmed off the period above.
20      -- I am adding an extra day to the total to make it inclusive.
21      --     i.e. 1/1/2008 to 1/1/2008 is one day because it includes the 1st
22      RETURN (datediff(d,@start_date,@end_date) + 1) - (datediff(ww,@start_date,@end_date) * 2)
23  
24  END

It is called like this:

 select dbo.f_week_days_in_period('10/1/2008','10/31/2008')

The function trims off any weekend days from the start and end of the period since they aren't going to count towards our total anyway. It then counts the total days and subtracts 2 days for each weekend that occurs in the range. I am adding an extra day to the total to make it inclusive. For instance, 1/1/2008 to 1/1/2008 is one day because it includes the 1st.

The function does NOT exclude holidays. If you have a holiday table, you can count the number of dates in that table who fall between the start and end dates and are a weekday "datepart(weekday, @holiday_date) not in (1,7)" and then subtract the holidays from the total before returning it.

TweetBacks
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
ike's Gravatar Not sure if you saw this one. There was another one on CFLib.org already before I posted this, but I didn't care for the solution because it looped over the dates one day at a time, so if you were counting days across several months the iterations could add up and you might wind up with an unexpected performance drain. So I created this one that does it mathematically by week except for the last partial week:

http://cflib.org/udf/countArbitraryDays

I'm not sure how often it would be used, but I figured there might also be cases of say, "we have this meeting on tuesdays and thursdays" so they want to count just those 2 days and could do that by passing in an argument of the days they want to omit like "1,2,4,6,7" to see how many meetings there would be between two dates.
# Posted By ike | 10/15/08 6:44 AM
Brad Wood's Gravatar @Ike: I'll keep that one in mind if I ever need a CF solution. My specific problem today dictated that I use a SQL solution though.
# Posted By Brad Wood | 10/15/08 7:04 AM
ike's Gravatar Oh np ... Didn't realize that was the need when I read the article. :)
# Posted By ike | 10/15/08 8:04 AM
Daniel D's Gravatar I generaly would us a day table with a is work day field. Then you just count where between start and end and workday. You avoid looping in sql and let it do what it is good at working with sets of data.
# Posted By Daniel D | 10/15/08 9:28 PM
Brad Wood's Gravatar @Daniel: How do you populate the table? Also, an explicit table approach would limit your calculations to only the records you had populated.
# Posted By Brad Wood | 10/16/08 1:02 AM
matt's Gravatar Uh :) Yes, when I first time faced "CountDayBetween" function in visual basic
(which d.m) I improvized just like you. Anyway this is wrong solution, because you iterate.
You can mathematically extend this (very valid) function which is mostly prepared by my friend,
who is mathematician.. I only optimized it

Shared Function countDaysBetween(ByVal fromDate As Date, ByVal toDate As Date) As Integer
Dim y1% = fromDate.Year - 1
Dim y2% = toDate.Year - 1

Dim ytd% = (y2 - y1) * 365 + y2 \ 4 - y1 \ 4 + y2 \ 400 - y1 \ 400 - y2 \ 100 + y1 \ 100
Dim dm%() = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
' +31 +28 +31 +30 +31 +30 +31 +31 +30 +31 +30
Dim d1%, d2%
Dim m1% = fromDate.Month
Dim d% = fromDate.Day
If lDateTime.Math.isLeapYear(y1 + 1) Then : If m1 > 2 Then : d1 = dm(m1) + 1 + d : Else : d1 = dm(m1) + d : End If
Else : d1 = dm(m1) + d : End If

Dim m2% = toDate.Month
d = toDate.Day
If lDateTime.Math.isLeapYear(y2 + 1) Then : If m2 > 2 Then : d2 = dm(m2) + 1 + d : Else : d2 = dm(m2) + d : End If
Else : d2 = dm(m2) + d : End If

Return ytd + d2 - d1
End Function
# Posted By matt | 11/1/08 8:43 PM
Fauzi's Gravatar Thanks brad,

This helped me a lot. Could you be so kind to explain how to incorporate the holiday table in this function? My knowledge in this matter is a bit limited.
# Posted By Fauzi | 12/25/08 7:58 PM
Brad Wood's Gravatar @Fauzi :

If you had a table called "holiday" which contained a reccord for each holiday you wanted excluded from the calculation you would simply need to do a select from that table where the holiday_date
was in your date range and was NOT already a weekend. The number of records you found would be subtracted from the previous total.

It would look something like this (untested):

RETURN @prev_total - (SELECT count(1)
FROM holiday
WHERE holiday_date >= @start_date
AND holiday_date <= @end_date
AND datepart(weekday, holiday_date) not in (1,7))
# Posted By Brad Wood | 12/29/08 9:49 PM
Abhi's Gravatar this is perfect ... thanks for posting ..
In my case I just get End Date ... so just by doing smal tweak I got what I want ...

DECLARE @start_date DATETIME
   DECLARE @end_date DATETIME

   select @end_date = '01/16/2009'
   SELECT @start_date = CONVERT(VARCHAR(25),DATEADD(dd,-(DAY(@end_date)-1),@end_date),101)

   WHILE datepart(weekday, @start_date) in (1,7) -- Sunday, Saturday
BEGIN
SET @start_date = dateadd(d,1,@start_date)
END

-- If the end date is a weekend, move it back to the last weekday
WHILE datepart(weekday, @end_date) in (1,7) -- Sunday, Saturday
BEGIN
SET @end_date = dateadd(d,-1,@end_date)
END

-- Weekdays are total days in perion minus weekends. (2 days per weekend)
-- Extra weekend days were trimmed off the period above.
-- I am adding an extra day to the total to make it inclusive.
-- i.e. 1/1/2008 to 1/1/2008 is one day because it includes the 1st
SELECT (datediff(d,@start_date,@end_date) + 1) - (datediff(ww,@start_date,@end_date) * 2) AS NumerOfWorkDays
# Posted By Abhi | 2/19/09 9:29 AM


BlogCFC was created by Raymond Camden. This blog is running version 5.9.5. Contact Blog Owner