I just wrote a T-SQL query based on CTE that generates support information to calculate working days in a period. Simply look at the WorkingDaySequential measure, which difference between two dates is the number of elapsed working days.

In the real world:

  • The Holidays CTE could be replaced by a real table with user-inserted data.
  • The temporary [#Calendar] could be a real table, providing necessary data access from DM

and probably much else.

The following code is provided “as is”, without any warranty about its behavior.

UPDATE 2007-11-28 – fixed two missing conditions in the query

SET STATISTICS IO ON 
GO 
SET DATEFIRST 7 -- Default is 7 (US / Sunday)  
GO 

DECLARE @StartYear AS INT DECLARE @EndYear AS INT 
SET @StartYear = 2006;
SET @EndYear = 2015;
WITH Holidays AS (
  -- Italian Holidays     (use year 1900 for recurrencies dates)  
            SELECT Date = CAST('19000101' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19000106' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19000425' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19000501' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19000602' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19000815' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19001101' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19001208' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19001225' AS DATETIME) 
  UNION ALL SELECT Date = CAST('19001226' AS DATETIME) 
  UNION ALL -- Holidays changing date each year  
            SELECT Date = CAST('20060417' AS DATETIME) 
  UNION ALL SELECT Date = CAST('20070409' AS DATETIME)
), 
Years AS (
  SELECT YYYY = @StartYear 
  UNION ALL 
  SELECT YYYY + 1 FROM Years WHERE YYYY < @EndYear
), 
Months AS (
  SELECT MM = 1 
  UNION ALL 
  SELECT MM + 1 FROM Months WHERE MM < 12
), 
Days AS (
  SELECT DD = 1 
  UNION ALL 
  SELECT DD + 1 FROM Days WHERE DD < 31
), 
DatesRaw AS (
  SELECT 
    YYYY = YYYY, 
    MM = MM, 
    DD = DD, 
    ID_Date = YYYY * 10000 + MM * 100 + DD, 
    DateString = CAST(YYYY * 10000 + MM * 100 + DD AS VARCHAR), 
    Date = CASE WHEN ISDATE(YYYY * 10000 + MM * 100 + DD) = 1 THEN CAST(
      CAST(YYYY * 10000 + MM * 100 + DD AS VARCHAR) AS DATETIME
    ) ELSE NULL END 
  FROM 
    Years 
    CROSS JOIN Months 
    CROSS JOIN Days 
  WHERE 
    ISDATE(YYYY * 10000 + MM * 100 + DD) = 1
) 
SELECT 
  DatesRaw.*, 
  DayOfWeek = DATEPART(dw, DatesRaw.Date), 
  CalendarDaySequential = CAST(DatesRaw.Date AS INT), 
  WorkingDay = CAST(
    CASE DATEPART(dw, DatesRaw.Date) 
        WHEN 1 THEN 0 -- Sunday 
        WHEN 7 THEN 0 -- Saturday
        ELSE CASE 
            WHEN recurring.Date IS NULL AND fixed.Date IS NULL THEN 1 
            ELSE 0 END 
    END AS BIT
  ) INTO #Calendar     
FROM 
  DatesRaw 
  LEFT JOIN Holidays recurring ON recurring.Date = DATEADD(
    Year, 
    1900 - YEAR(DatesRaw.Date), 
    DatesRaw.Date
  ) 
  LEFT JOIN Holidays fixed ON fixed.Date = DatesRaw.Date 
ORDER BY ID_Date 
GO 

SELECT *, WorkingDaySequential = ( SELECT COUNT(WorkingDay) FROM #Calendar wd3 WHERE wd3.CalendarDaySequential <= wd1.CalendarDaySequential AND wd3.WorkingDay = 1 ) FROM #Calendar wd1 ORDER BY ID_Date 
GO