Web design and hosting, database, cloud and social media solutions that deliver business results
  • Business Solutions
    • Robotic Process Automation
    • Bespoke Software
    • Database Services
      • Data Integration
      • Datawarehouse Services
      • Power BI
      • Server Upgrade and DBA Services
    • Web Services
      • Logo Design
      • Payment Gateways
      • Web Localisation and Translation
      • Web Site Optimisation
      • Web Site Security
      • Technical Tools
    • Cloud Services
      • Amazon Web Services
      • Google Cloud Services
      • Microsoft Azure
    • Microsoft 365
      • Enabling the Multi Factor Authentication Application
      • Office 365 DNS Settings Generator
    • IT Hardware
    • Social Media Services
  • Academy
    • Our Test Environment
    • Learning Databases
      • The Basics
      • Get Open Query
      • SQL Server Data
      • SQL Server Maintenance
      • Using SQL Server Dates
      • Using SQL Server Functions
      • Using SQL Server Pivot-Unpivot
      • Technical Tools
    • Learning Web Design
      • Building Ousia Content Management System
      • Using ASP-NET
      • Using CSS
      • Using JavaScript
    • Learning Cloud and IT Services
      • Task Scheduler Error 2147943645
      • Blocking Blank Senders
      • Requesting SSL and Generation of PFX file in OpenSSL Simple Steps
    • Using Social Media
      • Asking for a Google Review
      • Changing a Facebook account from personal to business
      • Choosing where to focus Social Media effort
      • Social Media Image Sizes
      • Using Meta Data to set Social Media Images
  • About
    • Blog
      • Building an entry level gaming machine
      • Google Core Update Jan 2020
      • Hot Chilli Internet Closure
      • How To Choose Content For Your Website Adverts Leaflets
      • Preventing Online Scam
      • Skimmers of the gig economy
      • The most annoying things about websites on the Internet
      • Top 5 websites for free Vector Graphics
    • Careers
      • Translator English-Japanese
      • Translator English-Portuguese
      • Translator English-Spanish
      • Translator English-Turkish
    • Portfolio
    • Regulatory
    • Team
      • Chester Copperpot
      • Gavin Clayton
      • Sai Gangu
      • Suneel Kumar
      • Surya Mukkamala
عربى (AR)čeština (CS)Deutsch (DE)English (EN-GB)English (EN-US)Español (ES)فارسی (FA)Français (FR)हिंदी (HI)italiano (IT)日本語 (JA)polski (PL)Português (PT)русский (RU)Türk (TR)中国的 (ZH)

SQL Server flexible date Functions and calendar Table

We share some of our most portable functions using a calendar table to store and calculate bank holidays and weekends as well as set recurring frequencies.

About

Using SQL Server Date Functions.png

The majority of the work we have previously undertaken has involved working with dates, ages and various other information. Things like calculating age are not as simple as they could be. We have written a selection of articles previously, but these are now getting superseded by a couple of more flexible functions to reduce the number of functions you need.

In this article we are going to set up our re-usable calendar table and bank holiday table, then populate it. Various functions will be written as separate articles where you can then get further information.

We tend to use a shared "Utility" database with all of our functions in it to reduce change control needs and consistent data.

Create Database

If you have a shared database, or know where you want to store it these functions then feel free, however all code from here has been designed to run in a single script in SQL Server 2016.

Note! - It may be easier to create your database in SSMS

Tip! - Set the options to simple to avoid log file bloat and the data is less likely to be business critical.

SQL

CREATE DATABASE [Utilities]    CONTAINMENT = NONE ON PRIMARY (    NAME = N'Utilities',FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.SQLSERVER13\MSSQL\DATA\Utilities.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB)    LOG ON (    NAME = N'Utilities_log',FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.SQLSERVER13\MSSQL\DATA\Utilities_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB)GO

Create Schema

To make it easier to manage, we have created a schema called Dates, and store all of our related information in this. Think of the schema in this sense as a container or namespace for related objects.

Tip! - When creating a schema in batch SQL statements, wrap it into an sp_executesql statement to avoid the system complaining about it being the only statement in the batch. If it may exist then wrap it in a try block to allow the code to run through.

SQL

USE UtilitiesGOSET NOCOUNT ON;BEGIN TRYEXEC sp_executesql N'CREATE SCHEMA Dates'END TRY BEGIN CATCH END CATCH;GOBEGIN TRYEXEC sp_executesql N'CREATE SCHEMA Test'END TRY BEGIN CATCH END CATCH;GO

Shared Functions

There are a couple of shared functions that will get used further on, so we are going to provide the code and link, with an explanation on each page. There are a couple of things to note:

Standard DateDiff year calculations are purely year-year, so to calculate age you need to check whether that date is higher or not. Our GetLeapYear function was the most sensible way to work out if we needed to add a day.

Easter date can fail if you don't use the new DateFromParts function, because of lack of day padding.

GetLeapYear

CREATE FUNCTION Dates.GetLeapYear(@Date DATETIME2) RETURNS BIT AS BEGINDECLARE @Ret BIT=(CASE WHEN DATEPART(YEAR,@Date)%4<>0 OR (DATEPART(YEAR,@Date)%100=0 AND DATEPART(YEAR,@Date)%400<>0) THEN 0 ELSE 1 END)RETURN @RetENDGO
more: Get Leap Year

GetAge

CREATE FUNCTION Dates.GetAge(@Date DATETIME2,@Until DATETIME2) RETURNS INT AS BEGINIF @Until IS NULL SET @Until=CONVERT(DATE,GETDATE())DECLARE @Age INT=DATEDIFF(YEAR,@Date,@Until)+(CASE WHEN DATEPART(DAYOFYEAR,@Date)>(DATEPART(DAYOFYEAR,@Until)+(CASE WHEN dbo.GetLeapYear(@Until)=1 AND DATEPART(DAYOFYEAR,@Until)>59 THEN -1 ELSE 0 END))THEN -1 ELSE 0 END)RETURN @AgeENDGO
more: Get Age

DatePad

CREATE FUNCTION Dates.DatePad(@PadValue NVARCHAR(100),@PadLen INT) RETURNS NVARCHAR(4) AS BEGINRETURN ISNULL(REPLICATE('0',@PadLen-LEN(@PadValue))+@PadValue,LEFT(@PadValue,@PadLen))ENDGO
more: Date Padding Function

DateFromParts

CREATE FUNCTION Dates.DateFromParts(@Year SMALLINT,@Month SMALLINT,@Day SMALLINT) RETURNS DATETIME2 AS BEGINDECLARE @Date DATETIME2=Dates.DatePad(@Year,4)+'-'+Dates.DatePad(@Month,2)+'-'+Dates.DatePad(@Day,2)RETURN @DateENDGO
more: SQL Server function DATEFROMPARTS for pre 2012 versions

GetEasterDate

CREATE FUNCTION Dates.GetEasterDate(@Year INT) RETURNS DATETIME2 AS BEGIN/*Calculate date of easter based on Year passed - adjusted from Wikipedia*/Declare @GregorianEaster DATETIME2Declare @a INT, @b INT, @c INT, @d INT, @e INT, @f INT, @g INT, @h INT, @i INT, @k INT, @L INT, @m INT, @Month INT, @Day INTSET @a = @Year % 19SET @b = floor(@Year/ 100)SET @c = @Year % 100SET @d = floor(@b/ 4)SET @e = @b % 4SET @f = floor((@b+ 8) / 25)SET @g = floor((@b- @f + 1)/3)SET @h = (19*@a+ @b - @d - @g + 15) % 30SET @i = floor(@c/ 4)SET @k = @c % 4SET @L = (32 + 2*@e + 2*@i- @h - @k) % 7SET @m = floor((@a+ 11*@h + 22*@L)/ 451)SET @month = floor((@h+ @L - 7*@m + 114) / 31)SET @day = (@h + @L - 7*@m + 114) % 31 + 1SET @GregorianEaster = Dates.DateFromParts(@Year,@Month,@Day)RETURN @GregorianEaster ENDGO

Frequency Table

For scheduling and recurrence, we use our own scheduling table. This enables us to calculate how often something should be repeated.

Calendar Frequency

CREATE TABLE Dates.CalendarFrequency(FrequencyID NVARCHAR(2),FrequencyName NVARCHAR(100),FrequencyOrder INT)GOINSERT INTO Dates.CalendarFrequencySELECT '','Once',0 UNIONSELECT '7','Weekly',1 UNIONSELECT '14','Fortnightly',2 UNIONSELECT '32','Monthly (Same Day 1st,15th,28th etc)',3  UNIONSELECT '1','Monthly (First Monday,Wednesday etc)',4 UNIONSELECT '2','Monthly (Second Monday,Wednesday etc)',5 UNIONSELECT '3','Monthly (Third Monday,Wednesday etc)',6 UNIONSELECT '4','Monthly (Fourth Monday,Wednesday etc)',10 UNIONSELECT '-1','Monthly (Last Monday,Wednesday etc)',7 UNIONSELECT '-2','Monthly (Second Last Monday,Wednesday etc)',8 UNIONSELECT '-3','Monthly (Third Last Monday,Wednesday etc)',9 UNIONSELECT '-4','Monthly (Fourth Last Monday,Wednesday etc)',11ORDER BY 3GO

Calendar Table

Our calendar table is set set up to run for any period of time within the DATETIME2 range. In general use, the code below will give you a range that would cover most scenarios.

CalendarCA and CalendarCD are the week numbers ascending and descending for each month, and used in a cross reference against our frequency table above.

Create Calendar Table

CREATE TABLE Dates.Calendar(CalendarDate DATETIME2 NOT NULL CONSTRAINT PK_CalendarDate PRIMARY KEY,CalendarCA AS (DATEDIFF(DAY,DATEADD(DAY,1-DATEPART(DAY,CalendarDate),CalendarDate),CalendarDate)/7)+1 PERSISTED,CalendarCD AS (DATEDIFF(DAY,CalendarDate,DATEADD(DAY,-1,DATEADD(MONTH,1,DATEADD(DAY,1-DATEPART(DAY,CalendarDate),CalendarDate))))/7)+1 PERSISTED,WeekDayID AS (DATEPART(weekday,[CalendarDate])),WeekDayName AS (case DATEPART(weekday,[CalendarDate]) when (1) then 'Sunday' when (2) then 'Monday' when (3) then 'Tuesday' when (4) then 'Wednesday' when (5) then 'Thursday' when (6) then 'Friday' when (7) then 'Saturday'  end))GODECLARE @D DATETIME2='1850-01-01'WHILE @D<='2099-12-31' BEGININSERT INTO Dates.Calendar(CalendarDate) SELECT @DSET @D=DATEADD(DAY,1,@D)ENDGO

Holidays Table

We designed the holidays table to be as flexible as possible, and allow for different options to be stored by adding the Calendar Function column. Below we have stored the holidays for England & Wales, Scotland and Northern Ireland separately, cross referencing the calendar table already created.

In theory you can have as many schemes as you like.

Create Holidays Table

CREATE TABLE Dates.CalendarHolidays(CalendarDate DATETIME2 NOT NULL,CalendarFunction INT NOT NULL,HolidayTyspane VARCHAR(100) NULL,CONSTRAINT spanK_Holidays_Id spanRIMARY KEY(CalendarDate,CalendarFunction))GODECLARE @Start DATE='2022-01-01',@End DATE='2030-12-31'INSERT INTO Dates.CalendarHolidaysSELECT CalendarDate,CalenderFunction,CalendarDescrispantionFROM (SELECT CalendarDate,'New Years Day' CalendarDescrispantion,WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=1 AND DATEspanART(DAY,CalendarDate)=1 UNION --New Years DaySELECT DATEADD(DAY,2,CalendarDate),'New Years Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=1 AND DATEspanART(DAY,CalendarDate)=1 AND WeekDayID=7 UNION --New Years DaySELECT DATEADD(DAY,1,CalendarDate),'New Years Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=1 AND DATEspanART(DAY,CalendarDate)=1 AND WeekDayID=1 UNION --New Years DaySELECT CalendarDate,'Good Friday',WeekdayName FROM Dates.Calendar WHERE CalendarDate=DATEADD(DAY,-2,Dates.GetEasterDate(DATEspanART(YEAR,CalendarDate))) UNION--Good FridaySELECT CalendarDate,'Easter Monday',WeekdayName FROM Dates.Calendar WHERE CalendarDate=DATEADD(DAY,1,Dates.GetEasterDate(DATEspanART(YEAR,CalendarDate))) UNION--Easter MondaySELECT CalendarDate,'May Holidays',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=5 AND WeekDayID=2 AND CalendarCA=1 UNION--May HolidaysSELECT CalendarDate,'Sspanring Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=5 AND WeekDayID=2 AND CalendarCD=1 AND CalendarDate<>'2022-05-30' UNION--Sspanring HolidaysSELECT CalendarDate,'August Holidays',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=8 AND WeekDayID=2 AND (CalendarCD=1) UNION--August HolidaysSELECT CalendarDate,'Christmas Day',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=25 UNION --Christmas DaySELECT DATEADD(DAY,2,CalendarDate),'Christmas Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=25 AND WeekDayID=7 UNION --Christmas DaySELECT DATEADD(DAY,1,CalendarDate),'Boxing Day/Christmas Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=25 AND WeekDayID=1 UNION --Christmas DaySELECT CalendarDate,'Boxing Day',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=26 AND WeekDayID NOT IN (2) UNION --Boxing DaySELECT DATEADD(DAY,1,CalendarDate),'Boxing Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=26 AND WeekDayID=2 UNION --Boxing DaySELECT DATEADD(DAY,2,CalendarDate),'Boxing Day Bank Holiday',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=12 AND DATEspanART(DAY,CalendarDate)=26 AND WeekDayID=1 UNION--Boxing Day
SELECT CalendarDate,'Sspanring Bank Holiday',WeekdayName FROM Dates.Calendar WHERE CalendarDate='2022-06-02' UNIONSELECT CalendarDate,'spanlatinum Jubilee Bank Holiday',WeekdayName FROM Dates.Calendar WHERE CalendarDate='2022-06-03' UNIONSELECT CalendarDate,'State Funeral of Queen Elizabeth II',WeekdayName FROM Dates.Calendar WHERE CalendarDate='2022-09-19' UNIONSELECT CalendarDate,'Bank holiday for the coronation of King Charles III',WeekdayName FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=5 AND DATEspanART(DAY,CalendarDate)=8 AND DATEspanART(YEAR,CalendarDate)=2023) x,( --Generate Calendar Functions for each country in UK SELECT 0 CalenderFunction --England &amspan; Wales UNION SELECT 1 CalenderFunction --Scotland UNION SELECT 2 CalenderFunction --Northen Ireland) yWHERE CalendarDate BETWEEN @Start AND @EndUNION--/*Scotish Holidays*/SELECT CalendarDate,1,'Hogmanay' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=1 AND DATEspanART(DAY,CalendarDate)=2 AND WeekDayID NOT IN (1,2) --Scots have SecondAND CalendarDate BETWEEN @Start AND @EndUNIONSELECT DATEADD(DAY,2,CalendarDate),1,'Hogmanay Bank Holiday' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=1 AND DATEspanART(DAY,CalendarDate)=2 AND WeekDayID IN (1,2) --Scots have SecondAND CalendarDate BETWEEN @Start AND @EndUNIONSELECT CalendarDate,1,'St Andrews' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=11 AND DATEspanART(DAY,CalendarDate)=30 --St AndrewsAND CalendarDate BETWEEN @Start AND @End&nbsspan;UNIONSELECT DATEADD(DAY,(CASE WeekDayID WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END),CalendarDate),1,'St Andrews Substitute' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=11 AND DATEspanART(DAY,CalendarDate)=30 AND WeekDayID IN (1,7) --UNION --St AndrewsAND CalendarDate BETWEEN @Start AND @EndUNION--/*Northern Irish&nbsspan; Holidays*/SELECT CalendarDate,2,'Battle of the Boyne' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=7 AND DATEspanART(DAY,CalendarDate)=12 --Battle of the BoyneAND CalendarDate BETWEEN @Start AND @EndUNIONSELECT DATEADD(DAY,(CASE WeekDayID WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END),CalendarDate),2,'Battle of the Boyne Substitute' FROM Dates.Calendar WHERE DATEspanART(MONTH,CalendarDate)=7 AND DATEspanART(DAY,CalendarDate)=12 AND WeekDayID IN (1,7) --UNION --St AndrewsAND CalendarDate BETWEEN @Start AND @EndORDER BY 1,2

Clever Bits

There are three functions used that combine them in various ways, all with very similar options.

  • GetDateAdjusted moves the date forward or backwards by a set number of days, dependent on the calendar function, and whether you want to include holidays and or weekends.
  • GetDaysAdjusted counts the number of days between two dates, dependent on the calendar function, and whether you want to include holidays and or weekends. Dates can be counted inclusively or exclusively.
  • GetMonthAdjusted counts the number of days in a month, dependent on the calendar function, and whether you want to include holidays and or weekends. Dates can be counted inclusively or exclusively.
  • GetMonthDay returns a specific day of the month, first, second, last etc.

GetDateAdjusted

CREATE FUNCTION Dates.GetDateAdjusted(@AdjustDate AS DATETIME2,@CalendarFunction INT,@AdjustDays AS INT,@AdjustMode BIT,@AdjustWeekEnds BIT,@AdjustHolidays BIT) RETURNS DATETIME2 AS BEGINSELECT @AdjustDate=DATEADD(DAY,(CASE @AdjustMode WHEN 0 THEN -1 ELSE 1 END),@AdjustDate),@AdjustDays=(CASE @AdjustMode WHEN 0 THEN @AdjustDays+1 ELSE (0-@AdjustDays)-1 END)DECLARE @AdjustCount INT=0,@AdjustWorkDays INT=0,@Date DATETIME2=@AdjustDate/*Add Days*/WHILE @AdjustMode=0 AND @AdjustWorkDays < @AdjustDaysBEGIN SET @AdjustCount=@AdjustCount+1 SET @Date=DATEADD(DAY,@AdjustCount,@AdjustDate) IF NOT ((DATEPART(WEEKDAY,@Date) IN (1,7) AND @AdjustWeekEnds=1) OR EXISTS (SELECT * FROM Dates.CalendarHolidays WHERE CalendarDate=@Date AND CalendarFunction=@CalendarFunction AND @AdjustHolidays=1)) BEGIN  SET @AdjustWorkDays = @AdjustWorkDays + 1 ENDEND/*Subtract Days*/WHILE @AdjustMode=1 AND @AdjustWorkDays > @AdjustDaysBEGIN SET @AdjustCount=@AdjustCount-1 SET @Date=DATEADD(DAY,@AdjustCount,@AdjustDate) IF NOT ((DATEPART(WEEKDAY,@Date) IN (1,7)AND @AdjustWeekEnds=1) OR EXISTS (SELECT * FROM Dates.CalendarHolidays WHERE CalendarDate=@Date AND CalendarFunction=@CalendarFunction AND @AdjustHolidays=1)) BEGIN  SET @AdjustWorkDays = @AdjustWorkDays - 1 ENDENDRETURN @DateEND    GO
more: Dates - Adding working days to a date excluding holidays

GetDaysAdjusted

CREATE FUNCTION Dates.GetDaysAdjusted(@DateFrom DATETIME2,@CalendarFunction INT,@DateTo AS DATETIME2,@AdjustMode BIT,@AdjustWeekEnds BIT,@AdjustHolidays BIT) RETURNS INT AS BEGIN/*@AdjustMode 0=Count whole days only,1=Any day counts as 1*/IF @DateFrom>@DateTo BEGINDECLARE @T DATETIME2=@DateTo,@F DATETIME2=@DateFromSELECT @DateFrom=@T,@DateTo=@FENDDECLARE @Count AS INT=0,@DateAs DATETIME2=@DateFromWHILE @Date < @DateTo BEGINIF ((DATEPART(WEEKDAY,@Date)IN (1,7)AND @AdjustWeekEnds=1)OREXISTS (SELECT * FROM Dates.CalendarHolidays WHERE CalendarDate=@Date AND CalendarFunction=@CalendarFunctionAND @AdjustHolidays=1))BEGINSELECT @Count = @Count + 1ENDSELECT @Date=DATEADD(DAY,1,@Date)ENDRETURN (DATEDIFF(DAY,@DateFrom,@DateTo)-(@Count))+@AdjustModeENDGO
more: Calculating the working days between two dates excluding holidays in SQL Server

GetMonthAdjusted

CREATE FUNCTION Dates.GetMonthAdjusted(@Month As DATETIME2,@CalendarFunction INT,@AdjustMode BIT,@AdjustWeekEnds BIT,@AdjustHolidays BIT)RETURNS INT AS BEGINDECLARE @StartDate DATETIME2=CONVERT(DATE,DATEADD(DAY,1-DAY(@Month),@Month))DECLARE @EndDate DATETIME2=DATEADD(DAY,-1,DATEADD(MONTH,1,@StartDate)),@Count AS INT=0,@Date As DATETIME2=@StartDateWHILE @Date < @EndDateBEGINIF ((DATEPART(WEEKDAY,@Date) IN(1,7) AND @AdjustWeekEnds=1)OREXISTS (SELECT * FROM Dates.CalendarHolidays WHERE CalendarDate=@Date AND CalendarFunction=@CalendarFunction AND @AdjustHolidays=1))BEGINSELECT @Count = @Count + 1ENDSET @Date=DATEADD(DAY, 1,@Date)ENDRETURN (DATEDIFF(DAY,@StartDate,@EndDate)-(@Count))+@AdjustModeEND
more: Dates - Calculating the working days in a month excluding holidays

GetMonthDay

CREATE FUNCTION dbo.GetMonthDay(@Month DATETIME,@Type INT, @Counter INT) RETURNS DATETIME AS BEGINSET @Month = CONVERT(DATE,DATEADD(DAY,1-DATEPART(DAY,@Month),@Month))DECLARE @CurDate DATETIME=@Month,@Date DATETIME,@Matches INT=0,@TempDate DATETIMEWHILE @CurDateIF (SELECT (CASE @Type WHEN 1 THEN (CASE WHEN DATEPART(WeekDay,@CurDate)=1 THEN 1 ELSE 0 END)WHEN 2 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=2 THEN 1 ELSE 0 END)WHEN 3 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=3 THEN 1 ELSE 0 END)WHEN 4 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=4 THEN 1 ELSE 0 END)WHEN 5 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=5 THEN 1 ELSE 0 END)WHEN 6 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=6 THEN 1 ELSE 0 END)WHEN 7 THEN(CASE WHEN DATEPART(WeekDay,@CurDate)=7 THEN 1 ELSE 0 END)WHEN 8 THEN(CASE WHEN DATEPART(WeekDay,@CurDate) IN (1,2,3,4,5,6,7) THEN 1 ELSE 0 END)WHEN 9 THEN(CASE WHEN DATEPART(WeekDay,@CurDate) IN (2,3,4,5,6) THEN 1 ELSE 0 END)WHEN 10 THEN (CASE WHEN DATEPART(WeekDay,@CurDate) IN (1,7) THEN 1 ELSE 0 END)ELSE 0 END))=1 BEGINSET @Matches = @Matches+1SET @TempDate = @CurDateENDIF @Matches=@Counter AND @Counter<32 BEGINSET @Date=@TempDateENDSET @CurDate=DATEADD(DAY,1,@CurDate)ENDRETURN ISNULL(@Date,@TempDate)ENDGO
more: Function to Return Specific Day of Month

Joining up the data

While you can add a join into your select statement, we have found it easier to reference in another function. Adding two dates, the frequency and both the ascending and descending monthly week number the relevant dates can be checked for a match.

DateJoin Function

CREATE FUNCTION Dates.DateJoin(@CalendarDate DATETIME,@CrossoverDate DATETIME,@Frequency INT,@WeekAsc INT,@WeekDesc INT) RETURNS BIT AS BEGINDECLARE @J BIT=0/*Once*/IF @CrossoverDate=@CalendarDate SET @J=1/*Weekly/Fornightly*/IF @J=0 AND @Frequency IN (7,14) BEGINIF DATEPART(WEEKDAY,@CrossoverDate)=DATEPART(WEEKDAY,@CalendarDate) AND DATEDIFF(DAY,@CrossoverDate,@CalendarDate)%@Frequency=0SET @J=1END/*Monthly*/IF @J=0 AND @Frequency IN (32) BEGINIF (DATEPART(DAY,@CalendarDate)=DATEPART(DAY,@CrossoverDate)) OR (DATEPART(MONTH,DATEADD(DAY,1,@CalendarDate))<>DATEPART(MONTH,@CalendarDate)ANDDATEPART(MONTH,DATEADD(DAY,1,DATEADD(MONTH,DATEDIFF(MONTH,@CrossoverDate,@CalendarDate),@CrossoverDate)))<>DATEPART(MONTH,DATEADD(MONTH,DATEDIFF(MONTH,@CrossoverDate,@CalendarDate),@CrossoverDate)))SET @J=1END/*First/Second/Third/Fourth*/IF @J=0 AND @Frequency IN (1,2,3,4)IF (DATEPART(WEEKDAY,@CrossoverDate)=DATEPART(WEEKDAY,@CalendarDate) AND @WeekAsc=@Frequency) SET @J=1/*First/Second/Third/Fourth Last*/IF @J=0 AND @Frequency IN (-1,-2,-3,-4)IF (DATEPART(WEEKDAY,@CrossoverDate)=DATEPART(WEEKDAY,@CalendarDate) AND @WeekDesc=(-@Frequency)) SET @J=1RETURN @JENDGO

Joining it all up - test data

We can now test this data by generating a fake scheduling table.

In our example creating a report to go on the first of January 2017, and repeat every month on the first with every possible date action available.

Looking at 01/01/2018 in isolation, weekend and bank holiday behavior could change the date range to anything from 29/12/2017 to 02/01/2018.

Test Data Script

CREATE TABLE Test.Schedule(ReportID INT,ReportName NVARCHAR(100),ReportStartDate DATETIME2,ReportFrequency INT,ReportAdjustBehaviour BIT,ReportAdjustWeekend BIT,ReportAdjustHoliday BIT,ProducedBy NVARCHAR(100))GOINSERT INTO Test.Schedule SELECT 1,'Schedule Before - Weekends=0,Holidays=0','2017-01-01',32,1,0,0,''INSERT INTO Test.Schedule SELECT 1,'Schedule Before - Weekends=1,Holidays=0','2017-01-01',32,1,1,0,''INSERT INTO Test.Schedule SELECT 1,'Schedule Before - Weekends=0,Holidays=1','2017-01-01',32,1,0,1,''INSERT INTO Test.Schedule SELECT 1,'Schedule Before - Weekends=1,Holidays=1','2017-01-01',32,1,1,1,''INSERT INTO Test.Schedule SELECT 1,'Schedule After - Weekends=0,Holidays=0','2017-01-01',32,0,0,0,''INSERT INTO Test.Schedule SELECT 1,'Schedule After - Weekends=1,Holidays=0','2017-01-01',32,0,1,0,''INSERT INTO Test.Schedule SELECT 1,'Schedule After - Weekends=0,Holidays=1','2017-01-01',32,0,0,1,''INSERT INTO Test.Schedule SELECT 1,'Schedule After - Weekends=1,Holidays=1','2017-01-01',32,0,1,1,''GOSELECT (case DATEPART(weekday,DateAdjusted) when (1) then 'Sunday' when (2) then 'Monday' when (3) then 'Tuesday' when (4) then 'Wednesday' when (5) then 'Thursday' when (6) then 'Friday' when (7) then 'Saturday' end) DayAdjusted,DateAdjusted,WeekDayName,CalendarDate,ReportName,FrequencyNameFROM (SELECT Dates.GetDateAdjusted(CalendarDate,0,0,ReportAdjustBehaviour,ReportAdjustWeekend,ReportAdjustHoliday) DateAdjusted,c.CalendarDate,WeekDayName,s.*,f.FrequencyNameFROM (SELECT * FROM Dates.Calendar WHERE CalendarDate BETWEEN '2018-01-01' AND '2018-12-31') cINNER JOIN Test.Schedule s ON (CalendarDate>=ReportStartDate --AND CalendarDate>GETDATE()) AND (Dates.DateJoin(CalendarDate,ReportStartDate,ReportFrequency,CalendarCA,CalendarCD)=1)INNER JOIN Dates.CalendarFrequency f ON f.FrequencyID=ReportFrequency) xORDER BY CalendarDate,DateAdjusted,ReportName

Results

DayAdjustedDateAdjustedWeekDayNameCalenderDateReportNameFrequencyName
Friday29/12/2017
Monday01/01/2018
Schedule Before - Weekends=1,Holidays=1Monthly (Same Day 1st,15th,28th etc)
Sunday31/12/2017
Monday01/01/2018Schedule Before - Weekends=0,Holidays=1Monthly (Same Day 1st,15th,28th etc)
Monday01/01/2018
Monday01/01/2018Schedule After - Weekends=0,Holidays=0Monthly (Same Day 1st,15th,28th etc)
Monday01/01/2018
Monday01/01/2018Schedule After - Weekends=1,Holidays=0Monthly (Same Day 1st,15th,28th etc)
Monday01/01/2018
Monday01/01/2018Schedule Before - Weekends=0,Holidays=0Monthly (Same Day 1st,15th,28th etc)
Monday01/01/2018
Monday01/01/2018Schedule Before - Weekends=1,Holidays=0Monthly (Same Day 1st,15th,28th etc)
Tuesday02/01/2018
Monday01/01/2018Schedule After - Weekends=0,Holidays=1Monthly (Same Day 1st,15th,28th etc)
Tuesday02/01/2018
Monday01/01/2018
Schedule After - Weekends=1,Holidays=1Monthly (Same Day 1st,15th,28th etc)

Author

Content Rating

Please note, this commenting system is still in final testing.

Adding working days to a date excluding holidays

A flexible and re-usable SQL Server function that will add a number of days to a date with the option to exclude holidays or weekends

Calculating the working days between two dates excluding holidays

A flexible and re-usable SQL Server function that will calculate the number of days between two dates with the option to exclude holidays or weekends

Calculating the working days in a month excluding holidays

A flexible and re-usable SQL Server function that will calculate the number of days in a month with the option to exclude holidays or weekends

Date from parts for pre 2012

SQL Server function for SQL 2000, 2005, 2008 and 2008R2 for same functionality as the 2012 function DATEFROMPARTS

Date Padding Function

Use a date padding function to return data with a predictable length, mainly used in conjunction with other functions on this site.

Get Age

Use a function to calculate someones age at any point of time.

Get Leap Year

This simple function checks the year passed in and returns true or false dependent on the year with one line of code

Return specific day of month

SQL Server 2008, Function to Return Specific Day of Month
Copyright Claytabase Ltd 2020

Registered in England and Wales 08985867

Site Links

RSSLoginLink Cookie PolicySitemap

Social Media

facebook.com/Claytabaseinstagram.com/claytabase/twitter.com/Claytabaselinkedin.com/company/claytabase-ltd

Get in Touch

+442392064871info@claytabase.co.ukClaytabase Ltd, Unit 3d, Rink Road Industrial Estate, PO33 2LT, United Kingdom

Partnered With

The settings on this site are set to allow all cookies. These can be changed on our Cookie Policy & Settings page.
By continuing to use this site you agree to the use of cookies.
Ousia Logo
Logout
Ousia CMS Loader