Calculating due date using business hours and holidaysHow to return only the Date from a SQL Server DateTime datatypeAdd business days to date in SQL without loopsMySQL time/date calculationCalculating Business Hours in SQLSQL Server 2008 - Sum business minutes between two dates taking into account custom holidays and weekendstime/date calculationsql working days holidaysdifference between two dates without weekends and holidays Sql query ORACLECalculate Expected End Time In SQL Servercalculate hours excluding weekends, holidays and non-working hours with SSMS
How do I ensure my employees don't abuse my flexible work hours policy?
How to securely dispose of a smartphone?
Undetectable mail tracker
A* pathfinding algorithm too slow
Cooking a nice pan seared steak for picky eaters
I just started should I accept a farewell lunch for a coworker I don't know?
What was the point of separating stdout and stderr?
Converting Geographic Coordinates into Lambert2008 coordinates
Why doesn't SpaceX land boosters in Africa?
Why was p[:] designed to work differently in these two situations?
Journal standards vs. personal standards
List Manipulation : a,b,c,d,e,f,g,h into a,b,c,d,e,f,g,h
80's-90's TV show or movie about life clocks
What happens if a caster is surprised while casting a spell with a long casting time?
What do you call a notepad used to keep a record?
Sharing referee/AE report online to point out a grievous error in refereeing
Is it okay to fade a human face just to create some space to place important content over it?
How useful would a hydroelectric plant be in the post-apocalypse world?
What election rules and voting rights are guaranteed by the US Constitution?
Copy group of files (Filename*) to backup (Filename*.bak)
The Lucas argument vs the theorem-provers--who wins and why?
If two black hole event horizons overlap (touch) can they ever separate again?
Closest Proximity of Oceans to Freshwater Springs
Why was Pan Am Flight 103 flying over Lockerbie?
Calculating due date using business hours and holidays
How to return only the Date from a SQL Server DateTime datatypeAdd business days to date in SQL without loopsMySQL time/date calculationCalculating Business Hours in SQLSQL Server 2008 - Sum business minutes between two dates taking into account custom holidays and weekendstime/date calculationsql working days holidaysdifference between two dates without weekends and holidays Sql query ORACLECalculate Expected End Time In SQL Servercalculate hours excluding weekends, holidays and non-working hours with SSMS
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
I need to calculate due date / end date for SLAs. As input values I have the start date and a timespan (in minutes). This calculation needs to take into account business hours, weekends, and holidays.
I've seen a lot of examples where the input is start date and end date, but have been struggling finding anything similar to the above input values.
Is there an elegant solution to this problem? Is there a way to calculate due date without using a loop? I can't think of a way to do the calculation without doing something similar to the following terrible algorithm:
- Create a return variable "due date" and set it to input variable
"start date" - Create a control variable "used minutes" and set it to 0
- Create a loop with the condition "used minutes" <= "input timespan"
- Inside the loop, add a second to the "due date" return variable
- Inside the loop, check if the second is within hours of operation
(checking business hours, weekends, and holidays). If so, increment
control variable "used minutes" by 1. - Upon exiting the loop, return variable "due date"
sql sql-server-2005
add a comment |
I need to calculate due date / end date for SLAs. As input values I have the start date and a timespan (in minutes). This calculation needs to take into account business hours, weekends, and holidays.
I've seen a lot of examples where the input is start date and end date, but have been struggling finding anything similar to the above input values.
Is there an elegant solution to this problem? Is there a way to calculate due date without using a loop? I can't think of a way to do the calculation without doing something similar to the following terrible algorithm:
- Create a return variable "due date" and set it to input variable
"start date" - Create a control variable "used minutes" and set it to 0
- Create a loop with the condition "used minutes" <= "input timespan"
- Inside the loop, add a second to the "due date" return variable
- Inside the loop, check if the second is within hours of operation
(checking business hours, weekends, and holidays). If so, increment
control variable "used minutes" by 1. - Upon exiting the loop, return variable "due date"
sql sql-server-2005
2
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52
add a comment |
I need to calculate due date / end date for SLAs. As input values I have the start date and a timespan (in minutes). This calculation needs to take into account business hours, weekends, and holidays.
I've seen a lot of examples where the input is start date and end date, but have been struggling finding anything similar to the above input values.
Is there an elegant solution to this problem? Is there a way to calculate due date without using a loop? I can't think of a way to do the calculation without doing something similar to the following terrible algorithm:
- Create a return variable "due date" and set it to input variable
"start date" - Create a control variable "used minutes" and set it to 0
- Create a loop with the condition "used minutes" <= "input timespan"
- Inside the loop, add a second to the "due date" return variable
- Inside the loop, check if the second is within hours of operation
(checking business hours, weekends, and holidays). If so, increment
control variable "used minutes" by 1. - Upon exiting the loop, return variable "due date"
sql sql-server-2005
I need to calculate due date / end date for SLAs. As input values I have the start date and a timespan (in minutes). This calculation needs to take into account business hours, weekends, and holidays.
I've seen a lot of examples where the input is start date and end date, but have been struggling finding anything similar to the above input values.
Is there an elegant solution to this problem? Is there a way to calculate due date without using a loop? I can't think of a way to do the calculation without doing something similar to the following terrible algorithm:
- Create a return variable "due date" and set it to input variable
"start date" - Create a control variable "used minutes" and set it to 0
- Create a loop with the condition "used minutes" <= "input timespan"
- Inside the loop, add a second to the "due date" return variable
- Inside the loop, check if the second is within hours of operation
(checking business hours, weekends, and holidays). If so, increment
control variable "used minutes" by 1. - Upon exiting the loop, return variable "due date"
sql sql-server-2005
sql sql-server-2005
asked Dec 29 '14 at 2:48
user1886415user1886415
1753 silver badges15 bronze badges
1753 silver badges15 bronze badges
2
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52
add a comment |
2
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52
2
2
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52
add a comment |
6 Answers
6
active
oldest
votes
You need a table with valid business hours, with the weekends and holidays excluded (or marked as weekend/holiday so you can skip them.) Each row represents one day and the number of working hours for that day. Then you query the business hours table from your start date to the first (min) date where the sum(hours*60) is greater than your minutes parameter, excluding marked weekend/holiday rows. That gives you your end date.
Here's the day table:
CREATE TABLE [dbo].[tblDay](
[dt] [datetime] NOT NULL,
[dayOfWk] [int] NULL,
[dayOfWkInMo] [int] NULL,
[isWeekend] [bit] NOT NULL,
[holidayID] [int] NULL,
[workingDayCount] [int] NULL,
CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED
(
[dt] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
here's how I populate the table with days:
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@Dt datetime ,
@wkInMo int,
@firstDwOfMo int,
@holID int,
@workDayCount int,
@weekday int,
@month int,
@day int,
@isWkEnd bit
set @workDayCount = 0
SET @Dt = CONVERT( datetime, '2008-01-01' )
while @dt < '2020-01-01'
begin
delete from tblDay where dt = @dt
set @weekday = datepart( weekday, @Dt )
set @month = datepart(month,@dt)
set @day = datepart(day,@dt)
if @day = 1 -- 1st of mo
begin
set @wkInMo = 1
set @firstDwOfMo = @weekday
end
if ((@weekday = 7) or (@weekday = 1))
set @isWkEnd = 1
else
set @isWkEnd = 0
if @isWkEnd = 0 and (@month = 1 and @day = 1)
set @holID=1 -- new years on workday
else if @weekday= 6 and (@month = 12 and @day = 31)
set @holID=1 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 1 and @day = 2)
set @holID=1 -- holiday on sun, change to mon
else if @wkInMo = 3 and @weekday= 2 and @month = 1
set @holID = 2 -- mlk
else if @wkInMo = 3 and @weekday= 2 and @month = 2
set @holID = 3 -- President’s
else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
set @holID = 4 -- memorial on 4th mon, no 5th
else if @wkInMo = 5 and @weekday= 2 and @month = 5
set @holID = 4 -- memorial on 5th mon
else if @isWkEnd = 0 and (@month = 7 and @day = 4)
set @holID=5 -- July 4 on workday
else if @weekday= 6 and (@month = 7 and @day = 3)
set @holID=5 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 7 and @day = 5)
set @holID=5 -- holiday on sun, change to mon
else if @wkInMo = 1 and @weekday= 2 and @month = 9
set @holID = 6 -- Labor
else if @isWkEnd = 0 and (@month = 11 and @day = 11)
set @holID=7 -- Vets day on workday
else if @weekday= 6 and (@month = 11 and @day = 10)
set @holID=7 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 11 and @day = 12)
set @holID=7 -- holiday on sun, change to mon
else if @wkInMo = 4 and @weekday= 5 and @month = 11
set @holID = 8 -- thx
else if @holID = 8
set @holID = 9 -- dy after thx
else if @isWkEnd = 0 and (@month = 12 and @day = 25)
set @holID=10 -- xmas day on workday
else if @weekday= 6 and (@month = 12 and @day = 24)
set @holID=10 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 12 and @day = 26)
set @holID=10 -- holiday on sun, change to mon
else
set @holID = null
insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount
if @isWkEnd=0 and @holID is null
set @workDayCount = @workDayCount + 1
set @dt = @dt + 1
if datepart( weekday, @Dt ) = @firstDwOfMo
set @wkInMo = @wkInMo + 1
end
END
I also have a holiday table, but everyone's holidays are different:
holidayID holiday rule description
1 New Year's Day Jan. 1
2 Martin Luther King Day third Mon. in Jan.
3 Presidents' Day third Mon. in Feb.
4 Memorial Day last Mon. in May
5 Independence Day 4-Jul
6 Labor Day first Mon. in Sept
7 Veterans' Day Nov. 11
8 Thanksgiving fourth Thurs. in Nov.
9 Fri after Thanksgiving Friday after Thanksgiving
10 Christmas Day Dec. 25
HTH
add a comment |
This is the best I could do, still uses a loop but uses date functions instead of incrementing a minutes variable. Hope you like it.
--set up our source data
declare @business_hours table
(
work_day varchar(10),
open_time varchar(8),
close_time varchar(8)
)
insert into @business_hours values ('Monday', '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday', '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')
declare @holidays table
(
holiday varchar(10)
)
insert into @holidays values ('2015-01-01')
insert into @holidays values ('2015-01-02')
--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960
declare @start_date datetime = '2014-12-31 16:12:47'
declare @time_span int = 960-- time till due in minutes
declare @true bit = 'true'
declare @false bit = 'false'
declare @due_date datetime --our output
--other variables
declare @date_string varchar(10)
declare @today_closing datetime
declare @is_workday bit = @true
declare @is_holiday bit = @false
--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates
set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)
while (@time_span > 0)
begin
set @due_date = DATEADD(MINUTE,@time_span,@start_date)
set @date_string = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))
if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date)))
set @is_workday = @true
else
set @is_workday = @false
if exists(select holiday from @holidays where holiday = @date_string)
set @is_holiday = @true
else
set @is_holiday = @false
if @is_workday = @true and @is_holiday = @false
begin
if @due_date > @today_closing
set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
else
set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end
set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
end
select @due_date
add a comment |
Here is an option using a WorkSchedule
table, which will contain the business hours that are available to count towards the SLA. To account for weekends and holidays, just do not insert records for these days into the WorkSchedule
table.
This solution also uses a "Tally" table aka numbers table in the due date calc. I also included debug output to help you see what is going on, so just comment out or uncomment any debug sections to see less/more info.
I used SQL temp tables in this example so that you can run it without messing up your current database schema, but you should replace with physical tables if you use this solution.
Test Data setups:
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO
--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go
--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT
workStart = dateadd(hour, 8, t.workDate)
, workEnd = dateadd(hour, 17, t.workDate)
FROM (
SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
FROM sys.objects o cross apply sys.objects o2
) t
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;
GO
Code to calculate Due Date:
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;
DECLARE @workStartDayOne datetime;
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;
--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT:
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.
--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart
--increase the SLA timespan mins to account for difference between work start and start time
, @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;
--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;
--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
SELECT TOP (@SLA_Adjusted)
s.WorkStart, s.WorkEnd
, WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
, t.N as MinuteOfWorkDay
, RowNum = row_number() over(order by s.WorkStart, t.N)
FROM #WorkSchedule s
INNER JOIN #Tally t
ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
WHERE s.WorkStart >= @workStartDayOne
ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute
FROM cteWorkMins m
WHERE m.RowNum = @SLA_Adjusted
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m
--WHERE m.RowNum = @SLA_Adjusted
ORDER BY m.WorkMinute
--*/
;
select @dueDate as DueDate;
GO
Test Cleanup:
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
GO
add a comment |
as I understood from your question, what you need is as follow
- You have given start date and number of minutes added to it, then you need to get the due date
- To get the due date, you need to exclude the holidays and the due date should be during business day
here is what you can do
declare @start datetime,
@min int,
@days int
set @start= '28 Dec 2014'
set @min = 2200
-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))
-- get the due date
select max(Date)
from
(select row_number() over( order by t.Id)-1 as Id,t.Date
from DateMetadata t
inner join BusinessDays b on Day(t.Date) = b.Day
where t.Date > = @start and not exists(select 1 from Holidays h
where h.Day=Day(t.Date)
and h.Month=Month(t.Date))) as p
where p.Id < @days
Note :that DateMetadata table you will setup it in your database once
the setup for the above code :
create table Holidays(Id int identity(1,1),
Name nvarchar(50),
Day int,
Month int)
create table BusinessDays(Id int identity(1,1),
Name nvarchar(20),
Day int)
-- i am putting some days that are known,
-- it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)
-- i am assuming that the business days are from monday till friday and
-- saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
this table is needed and you will setup it once
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
Date datetime)
declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
insert into DateMetadata(Date) values(@date)
set @date = @date + 1
end
here a working DEMO
if you need any explanation, i am ready
hope it will help you
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
add a comment |
Sql to Calculate due date excluding holidays and considering business hour as below :- < note : - It's working correctly Business hour (8-5) Maintain holiday table
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
>
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare @holidays table (HDate DateTime) -- Table variable to hold holidayes
---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)
--check for weekends
set @start = case(datepart(dw,@start)+@@datefirst-1)%7
when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
else @start end
-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')
-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
-- loop start
while (@slamins > 0)
begin
-- prepared closing date time based on start date
set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
set @due_date = @start
-- calculate number of Minute between start date and closing date
set @temp = DATEDIFF(N, @start , @today_closing);
--check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7)
set @is_workday = @true
else
set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
set @is_holiday =@false
else
set @is_holiday = @true
if @is_workday = @true and @is_holiday = @false
begin
if(@temp < @slamins)
begin
set @slamins = @slamins - @temp
end
else
begin
set @due_date = DATEADD(MINUTE,@slamins,@start)
set @slamins = 0
print @due_date
end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end
select @due_date
add a comment |
I created a function to calculate due date from the table, once it's populated as per Beth's and others' approaches (various similar methods for doing this, as you can see -- it only took me about an hour to think about all the UK holidays and populate the table including Easter dates up to 2029 without using these exact guides).
Note that my table contains SLA in business hours (8 hours in a normal day, 5 days in a normal week), your business hours may vary but you can amend this easily, just make sure your business hours are set the same for both the SLA table and the function below.
Code below is T-SQL (written in SSMS v17.8.1)
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
@CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)
/*
baldmosher™
2019-03-25
* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
* <24hrs calendar = <8hrs business
* =24hrs calendar = 8hrs business
* >24hrs calendar = 8hrs business * business days
*/
BEGIN
IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;
DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;
--SET DATEFIRST 1;
DECLARE @DueDate DATETIME;
DECLARE @BusHrsStart DECIMAL(18,10) = 8 ; -- start of Business Hours (8am)
DECLARE @BusHrsClose DECIMAL(18,10) = 16 ; -- close of Business Hours (4pm)
--DECLARE @WkndStart DECIMAL(18,10) = 6 ; -- start of weekend (Sat)
DECLARE @Hours_Today SMALLINT ; -- # hours left in day to process ticket
-- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
-- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart
--created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
END
IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
--created after normal hours, adjust @CreatedDate to @BusHrsStart next day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
--adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
SET @CreatedDate = CAST(@CreatedDate AS DATE)
StartNextWorkingDay:
IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0
BEGIN
SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
GOTO StartNextWorkingDay
END
--DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0); -- midnight, Monday next week
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate); -- BusHrsStart
END
-- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
SET @DueDate = @CreatedDate
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
DueNextWorkingDay:
IF @SLA_Hours_Remaining > @Hours_Today
BEGIN
-- PRINT 'Due another day'
SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today --adjust remaining time after today's hours
SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME))) --adjust DueDate to first thing next day
END
IF @SLA_Hours_Remaining <= @Hours_Today
BEGIN
-- PRINT 'Due today'
SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
END
ELSE
BEGIN
GOTO DueNextWorkingDay
END
-- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))
RETURN @DueDate
END
GO
Table for SLAs:
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
[SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
[Project] [VARCHAR](20) NULL,
[Issue_Type] [VARCHAR](50) NULL,
[Epic_Name] [VARCHAR](50) NULL,
[SLA_Business_Hours] [SMALLINT] NULL,
[Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f27682810%2fcalculating-due-date-using-business-hours-and-holidays%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
You need a table with valid business hours, with the weekends and holidays excluded (or marked as weekend/holiday so you can skip them.) Each row represents one day and the number of working hours for that day. Then you query the business hours table from your start date to the first (min) date where the sum(hours*60) is greater than your minutes parameter, excluding marked weekend/holiday rows. That gives you your end date.
Here's the day table:
CREATE TABLE [dbo].[tblDay](
[dt] [datetime] NOT NULL,
[dayOfWk] [int] NULL,
[dayOfWkInMo] [int] NULL,
[isWeekend] [bit] NOT NULL,
[holidayID] [int] NULL,
[workingDayCount] [int] NULL,
CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED
(
[dt] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
here's how I populate the table with days:
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@Dt datetime ,
@wkInMo int,
@firstDwOfMo int,
@holID int,
@workDayCount int,
@weekday int,
@month int,
@day int,
@isWkEnd bit
set @workDayCount = 0
SET @Dt = CONVERT( datetime, '2008-01-01' )
while @dt < '2020-01-01'
begin
delete from tblDay where dt = @dt
set @weekday = datepart( weekday, @Dt )
set @month = datepart(month,@dt)
set @day = datepart(day,@dt)
if @day = 1 -- 1st of mo
begin
set @wkInMo = 1
set @firstDwOfMo = @weekday
end
if ((@weekday = 7) or (@weekday = 1))
set @isWkEnd = 1
else
set @isWkEnd = 0
if @isWkEnd = 0 and (@month = 1 and @day = 1)
set @holID=1 -- new years on workday
else if @weekday= 6 and (@month = 12 and @day = 31)
set @holID=1 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 1 and @day = 2)
set @holID=1 -- holiday on sun, change to mon
else if @wkInMo = 3 and @weekday= 2 and @month = 1
set @holID = 2 -- mlk
else if @wkInMo = 3 and @weekday= 2 and @month = 2
set @holID = 3 -- President’s
else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
set @holID = 4 -- memorial on 4th mon, no 5th
else if @wkInMo = 5 and @weekday= 2 and @month = 5
set @holID = 4 -- memorial on 5th mon
else if @isWkEnd = 0 and (@month = 7 and @day = 4)
set @holID=5 -- July 4 on workday
else if @weekday= 6 and (@month = 7 and @day = 3)
set @holID=5 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 7 and @day = 5)
set @holID=5 -- holiday on sun, change to mon
else if @wkInMo = 1 and @weekday= 2 and @month = 9
set @holID = 6 -- Labor
else if @isWkEnd = 0 and (@month = 11 and @day = 11)
set @holID=7 -- Vets day on workday
else if @weekday= 6 and (@month = 11 and @day = 10)
set @holID=7 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 11 and @day = 12)
set @holID=7 -- holiday on sun, change to mon
else if @wkInMo = 4 and @weekday= 5 and @month = 11
set @holID = 8 -- thx
else if @holID = 8
set @holID = 9 -- dy after thx
else if @isWkEnd = 0 and (@month = 12 and @day = 25)
set @holID=10 -- xmas day on workday
else if @weekday= 6 and (@month = 12 and @day = 24)
set @holID=10 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 12 and @day = 26)
set @holID=10 -- holiday on sun, change to mon
else
set @holID = null
insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount
if @isWkEnd=0 and @holID is null
set @workDayCount = @workDayCount + 1
set @dt = @dt + 1
if datepart( weekday, @Dt ) = @firstDwOfMo
set @wkInMo = @wkInMo + 1
end
END
I also have a holiday table, but everyone's holidays are different:
holidayID holiday rule description
1 New Year's Day Jan. 1
2 Martin Luther King Day third Mon. in Jan.
3 Presidents' Day third Mon. in Feb.
4 Memorial Day last Mon. in May
5 Independence Day 4-Jul
6 Labor Day first Mon. in Sept
7 Veterans' Day Nov. 11
8 Thanksgiving fourth Thurs. in Nov.
9 Fri after Thanksgiving Friday after Thanksgiving
10 Christmas Day Dec. 25
HTH
add a comment |
You need a table with valid business hours, with the weekends and holidays excluded (or marked as weekend/holiday so you can skip them.) Each row represents one day and the number of working hours for that day. Then you query the business hours table from your start date to the first (min) date where the sum(hours*60) is greater than your minutes parameter, excluding marked weekend/holiday rows. That gives you your end date.
Here's the day table:
CREATE TABLE [dbo].[tblDay](
[dt] [datetime] NOT NULL,
[dayOfWk] [int] NULL,
[dayOfWkInMo] [int] NULL,
[isWeekend] [bit] NOT NULL,
[holidayID] [int] NULL,
[workingDayCount] [int] NULL,
CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED
(
[dt] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
here's how I populate the table with days:
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@Dt datetime ,
@wkInMo int,
@firstDwOfMo int,
@holID int,
@workDayCount int,
@weekday int,
@month int,
@day int,
@isWkEnd bit
set @workDayCount = 0
SET @Dt = CONVERT( datetime, '2008-01-01' )
while @dt < '2020-01-01'
begin
delete from tblDay where dt = @dt
set @weekday = datepart( weekday, @Dt )
set @month = datepart(month,@dt)
set @day = datepart(day,@dt)
if @day = 1 -- 1st of mo
begin
set @wkInMo = 1
set @firstDwOfMo = @weekday
end
if ((@weekday = 7) or (@weekday = 1))
set @isWkEnd = 1
else
set @isWkEnd = 0
if @isWkEnd = 0 and (@month = 1 and @day = 1)
set @holID=1 -- new years on workday
else if @weekday= 6 and (@month = 12 and @day = 31)
set @holID=1 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 1 and @day = 2)
set @holID=1 -- holiday on sun, change to mon
else if @wkInMo = 3 and @weekday= 2 and @month = 1
set @holID = 2 -- mlk
else if @wkInMo = 3 and @weekday= 2 and @month = 2
set @holID = 3 -- President’s
else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
set @holID = 4 -- memorial on 4th mon, no 5th
else if @wkInMo = 5 and @weekday= 2 and @month = 5
set @holID = 4 -- memorial on 5th mon
else if @isWkEnd = 0 and (@month = 7 and @day = 4)
set @holID=5 -- July 4 on workday
else if @weekday= 6 and (@month = 7 and @day = 3)
set @holID=5 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 7 and @day = 5)
set @holID=5 -- holiday on sun, change to mon
else if @wkInMo = 1 and @weekday= 2 and @month = 9
set @holID = 6 -- Labor
else if @isWkEnd = 0 and (@month = 11 and @day = 11)
set @holID=7 -- Vets day on workday
else if @weekday= 6 and (@month = 11 and @day = 10)
set @holID=7 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 11 and @day = 12)
set @holID=7 -- holiday on sun, change to mon
else if @wkInMo = 4 and @weekday= 5 and @month = 11
set @holID = 8 -- thx
else if @holID = 8
set @holID = 9 -- dy after thx
else if @isWkEnd = 0 and (@month = 12 and @day = 25)
set @holID=10 -- xmas day on workday
else if @weekday= 6 and (@month = 12 and @day = 24)
set @holID=10 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 12 and @day = 26)
set @holID=10 -- holiday on sun, change to mon
else
set @holID = null
insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount
if @isWkEnd=0 and @holID is null
set @workDayCount = @workDayCount + 1
set @dt = @dt + 1
if datepart( weekday, @Dt ) = @firstDwOfMo
set @wkInMo = @wkInMo + 1
end
END
I also have a holiday table, but everyone's holidays are different:
holidayID holiday rule description
1 New Year's Day Jan. 1
2 Martin Luther King Day third Mon. in Jan.
3 Presidents' Day third Mon. in Feb.
4 Memorial Day last Mon. in May
5 Independence Day 4-Jul
6 Labor Day first Mon. in Sept
7 Veterans' Day Nov. 11
8 Thanksgiving fourth Thurs. in Nov.
9 Fri after Thanksgiving Friday after Thanksgiving
10 Christmas Day Dec. 25
HTH
add a comment |
You need a table with valid business hours, with the weekends and holidays excluded (or marked as weekend/holiday so you can skip them.) Each row represents one day and the number of working hours for that day. Then you query the business hours table from your start date to the first (min) date where the sum(hours*60) is greater than your minutes parameter, excluding marked weekend/holiday rows. That gives you your end date.
Here's the day table:
CREATE TABLE [dbo].[tblDay](
[dt] [datetime] NOT NULL,
[dayOfWk] [int] NULL,
[dayOfWkInMo] [int] NULL,
[isWeekend] [bit] NOT NULL,
[holidayID] [int] NULL,
[workingDayCount] [int] NULL,
CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED
(
[dt] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
here's how I populate the table with days:
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@Dt datetime ,
@wkInMo int,
@firstDwOfMo int,
@holID int,
@workDayCount int,
@weekday int,
@month int,
@day int,
@isWkEnd bit
set @workDayCount = 0
SET @Dt = CONVERT( datetime, '2008-01-01' )
while @dt < '2020-01-01'
begin
delete from tblDay where dt = @dt
set @weekday = datepart( weekday, @Dt )
set @month = datepart(month,@dt)
set @day = datepart(day,@dt)
if @day = 1 -- 1st of mo
begin
set @wkInMo = 1
set @firstDwOfMo = @weekday
end
if ((@weekday = 7) or (@weekday = 1))
set @isWkEnd = 1
else
set @isWkEnd = 0
if @isWkEnd = 0 and (@month = 1 and @day = 1)
set @holID=1 -- new years on workday
else if @weekday= 6 and (@month = 12 and @day = 31)
set @holID=1 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 1 and @day = 2)
set @holID=1 -- holiday on sun, change to mon
else if @wkInMo = 3 and @weekday= 2 and @month = 1
set @holID = 2 -- mlk
else if @wkInMo = 3 and @weekday= 2 and @month = 2
set @holID = 3 -- President’s
else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
set @holID = 4 -- memorial on 4th mon, no 5th
else if @wkInMo = 5 and @weekday= 2 and @month = 5
set @holID = 4 -- memorial on 5th mon
else if @isWkEnd = 0 and (@month = 7 and @day = 4)
set @holID=5 -- July 4 on workday
else if @weekday= 6 and (@month = 7 and @day = 3)
set @holID=5 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 7 and @day = 5)
set @holID=5 -- holiday on sun, change to mon
else if @wkInMo = 1 and @weekday= 2 and @month = 9
set @holID = 6 -- Labor
else if @isWkEnd = 0 and (@month = 11 and @day = 11)
set @holID=7 -- Vets day on workday
else if @weekday= 6 and (@month = 11 and @day = 10)
set @holID=7 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 11 and @day = 12)
set @holID=7 -- holiday on sun, change to mon
else if @wkInMo = 4 and @weekday= 5 and @month = 11
set @holID = 8 -- thx
else if @holID = 8
set @holID = 9 -- dy after thx
else if @isWkEnd = 0 and (@month = 12 and @day = 25)
set @holID=10 -- xmas day on workday
else if @weekday= 6 and (@month = 12 and @day = 24)
set @holID=10 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 12 and @day = 26)
set @holID=10 -- holiday on sun, change to mon
else
set @holID = null
insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount
if @isWkEnd=0 and @holID is null
set @workDayCount = @workDayCount + 1
set @dt = @dt + 1
if datepart( weekday, @Dt ) = @firstDwOfMo
set @wkInMo = @wkInMo + 1
end
END
I also have a holiday table, but everyone's holidays are different:
holidayID holiday rule description
1 New Year's Day Jan. 1
2 Martin Luther King Day third Mon. in Jan.
3 Presidents' Day third Mon. in Feb.
4 Memorial Day last Mon. in May
5 Independence Day 4-Jul
6 Labor Day first Mon. in Sept
7 Veterans' Day Nov. 11
8 Thanksgiving fourth Thurs. in Nov.
9 Fri after Thanksgiving Friday after Thanksgiving
10 Christmas Day Dec. 25
HTH
You need a table with valid business hours, with the weekends and holidays excluded (or marked as weekend/holiday so you can skip them.) Each row represents one day and the number of working hours for that day. Then you query the business hours table from your start date to the first (min) date where the sum(hours*60) is greater than your minutes parameter, excluding marked weekend/holiday rows. That gives you your end date.
Here's the day table:
CREATE TABLE [dbo].[tblDay](
[dt] [datetime] NOT NULL,
[dayOfWk] [int] NULL,
[dayOfWkInMo] [int] NULL,
[isWeekend] [bit] NOT NULL,
[holidayID] [int] NULL,
[workingDayCount] [int] NULL,
CONSTRAINT [PK_tblDay] PRIMARY KEY CLUSTERED
(
[dt] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
here's how I populate the table with days:
CREATE PROCEDURE [dbo].[usp_tblDay]
AS
BEGIN
SET NOCOUNT ON;
DECLARE
@Dt datetime ,
@wkInMo int,
@firstDwOfMo int,
@holID int,
@workDayCount int,
@weekday int,
@month int,
@day int,
@isWkEnd bit
set @workDayCount = 0
SET @Dt = CONVERT( datetime, '2008-01-01' )
while @dt < '2020-01-01'
begin
delete from tblDay where dt = @dt
set @weekday = datepart( weekday, @Dt )
set @month = datepart(month,@dt)
set @day = datepart(day,@dt)
if @day = 1 -- 1st of mo
begin
set @wkInMo = 1
set @firstDwOfMo = @weekday
end
if ((@weekday = 7) or (@weekday = 1))
set @isWkEnd = 1
else
set @isWkEnd = 0
if @isWkEnd = 0 and (@month = 1 and @day = 1)
set @holID=1 -- new years on workday
else if @weekday= 6 and (@month = 12 and @day = 31)
set @holID=1 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 1 and @day = 2)
set @holID=1 -- holiday on sun, change to mon
else if @wkInMo = 3 and @weekday= 2 and @month = 1
set @holID = 2 -- mlk
else if @wkInMo = 3 and @weekday= 2 and @month = 2
set @holID = 3 -- President’s
else if @wkInMo = 4 and @weekday= 2 and @month = 5 and datepart(month,@dt+7) = 6
set @holID = 4 -- memorial on 4th mon, no 5th
else if @wkInMo = 5 and @weekday= 2 and @month = 5
set @holID = 4 -- memorial on 5th mon
else if @isWkEnd = 0 and (@month = 7 and @day = 4)
set @holID=5 -- July 4 on workday
else if @weekday= 6 and (@month = 7 and @day = 3)
set @holID=5 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 7 and @day = 5)
set @holID=5 -- holiday on sun, change to mon
else if @wkInMo = 1 and @weekday= 2 and @month = 9
set @holID = 6 -- Labor
else if @isWkEnd = 0 and (@month = 11 and @day = 11)
set @holID=7 -- Vets day on workday
else if @weekday= 6 and (@month = 11 and @day = 10)
set @holID=7 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 11 and @day = 12)
set @holID=7 -- holiday on sun, change to mon
else if @wkInMo = 4 and @weekday= 5 and @month = 11
set @holID = 8 -- thx
else if @holID = 8
set @holID = 9 -- dy after thx
else if @isWkEnd = 0 and (@month = 12 and @day = 25)
set @holID=10 -- xmas day on workday
else if @weekday= 6 and (@month = 12 and @day = 24)
set @holID=10 -- holiday on sat, change to fri
else if @weekday= 2 and (@month = 12 and @day = 26)
set @holID=10 -- holiday on sun, change to mon
else
set @holID = null
insert into tblDay select @dt,@weekday,@wkInMo,@isWkEnd,@holID,@workDayCount
if @isWkEnd=0 and @holID is null
set @workDayCount = @workDayCount + 1
set @dt = @dt + 1
if datepart( weekday, @Dt ) = @firstDwOfMo
set @wkInMo = @wkInMo + 1
end
END
I also have a holiday table, but everyone's holidays are different:
holidayID holiday rule description
1 New Year's Day Jan. 1
2 Martin Luther King Day third Mon. in Jan.
3 Presidents' Day third Mon. in Feb.
4 Memorial Day last Mon. in May
5 Independence Day 4-Jul
6 Labor Day first Mon. in Sept
7 Veterans' Day Nov. 11
8 Thanksgiving fourth Thurs. in Nov.
9 Fri after Thanksgiving Friday after Thanksgiving
10 Christmas Day Dec. 25
HTH
edited Jan 5 '15 at 22:54
answered Dec 31 '14 at 23:47
BethBeth
8,9971 gold badge16 silver badges36 bronze badges
8,9971 gold badge16 silver badges36 bronze badges
add a comment |
add a comment |
This is the best I could do, still uses a loop but uses date functions instead of incrementing a minutes variable. Hope you like it.
--set up our source data
declare @business_hours table
(
work_day varchar(10),
open_time varchar(8),
close_time varchar(8)
)
insert into @business_hours values ('Monday', '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday', '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')
declare @holidays table
(
holiday varchar(10)
)
insert into @holidays values ('2015-01-01')
insert into @holidays values ('2015-01-02')
--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960
declare @start_date datetime = '2014-12-31 16:12:47'
declare @time_span int = 960-- time till due in minutes
declare @true bit = 'true'
declare @false bit = 'false'
declare @due_date datetime --our output
--other variables
declare @date_string varchar(10)
declare @today_closing datetime
declare @is_workday bit = @true
declare @is_holiday bit = @false
--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates
set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)
while (@time_span > 0)
begin
set @due_date = DATEADD(MINUTE,@time_span,@start_date)
set @date_string = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))
if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date)))
set @is_workday = @true
else
set @is_workday = @false
if exists(select holiday from @holidays where holiday = @date_string)
set @is_holiday = @true
else
set @is_holiday = @false
if @is_workday = @true and @is_holiday = @false
begin
if @due_date > @today_closing
set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
else
set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end
set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
end
select @due_date
add a comment |
This is the best I could do, still uses a loop but uses date functions instead of incrementing a minutes variable. Hope you like it.
--set up our source data
declare @business_hours table
(
work_day varchar(10),
open_time varchar(8),
close_time varchar(8)
)
insert into @business_hours values ('Monday', '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday', '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')
declare @holidays table
(
holiday varchar(10)
)
insert into @holidays values ('2015-01-01')
insert into @holidays values ('2015-01-02')
--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960
declare @start_date datetime = '2014-12-31 16:12:47'
declare @time_span int = 960-- time till due in minutes
declare @true bit = 'true'
declare @false bit = 'false'
declare @due_date datetime --our output
--other variables
declare @date_string varchar(10)
declare @today_closing datetime
declare @is_workday bit = @true
declare @is_holiday bit = @false
--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates
set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)
while (@time_span > 0)
begin
set @due_date = DATEADD(MINUTE,@time_span,@start_date)
set @date_string = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))
if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date)))
set @is_workday = @true
else
set @is_workday = @false
if exists(select holiday from @holidays where holiday = @date_string)
set @is_holiday = @true
else
set @is_holiday = @false
if @is_workday = @true and @is_holiday = @false
begin
if @due_date > @today_closing
set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
else
set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end
set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
end
select @due_date
add a comment |
This is the best I could do, still uses a loop but uses date functions instead of incrementing a minutes variable. Hope you like it.
--set up our source data
declare @business_hours table
(
work_day varchar(10),
open_time varchar(8),
close_time varchar(8)
)
insert into @business_hours values ('Monday', '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday', '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')
declare @holidays table
(
holiday varchar(10)
)
insert into @holidays values ('2015-01-01')
insert into @holidays values ('2015-01-02')
--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960
declare @start_date datetime = '2014-12-31 16:12:47'
declare @time_span int = 960-- time till due in minutes
declare @true bit = 'true'
declare @false bit = 'false'
declare @due_date datetime --our output
--other variables
declare @date_string varchar(10)
declare @today_closing datetime
declare @is_workday bit = @true
declare @is_holiday bit = @false
--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates
set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)
while (@time_span > 0)
begin
set @due_date = DATEADD(MINUTE,@time_span,@start_date)
set @date_string = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))
if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date)))
set @is_workday = @true
else
set @is_workday = @false
if exists(select holiday from @holidays where holiday = @date_string)
set @is_holiday = @true
else
set @is_holiday = @false
if @is_workday = @true and @is_holiday = @false
begin
if @due_date > @today_closing
set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
else
set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end
set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
end
select @due_date
This is the best I could do, still uses a loop but uses date functions instead of incrementing a minutes variable. Hope you like it.
--set up our source data
declare @business_hours table
(
work_day varchar(10),
open_time varchar(8),
close_time varchar(8)
)
insert into @business_hours values ('Monday', '08:30:00', '17:00:00')
insert into @business_hours values ('Tuesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Wednesday', '08:30:00', '17:00:00')
insert into @business_hours values ('Thursday', '08:30:00', '17:00:00')
insert into @business_hours values ('Friday', '08:30:00', '18:00:00')
insert into @business_hours values ('Saturday', '09:00:00', '14:00:00')
declare @holidays table
(
holiday varchar(10)
)
insert into @holidays values ('2015-01-01')
insert into @holidays values ('2015-01-02')
--Im going to assume the SLA of 2 standard business days (0900-1700) = 8*60*2 = 960
declare @start_date datetime = '2014-12-31 16:12:47'
declare @time_span int = 960-- time till due in minutes
declare @true bit = 'true'
declare @false bit = 'false'
declare @due_date datetime --our output
--other variables
declare @date_string varchar(10)
declare @today_closing datetime
declare @is_workday bit = @true
declare @is_holiday bit = @false
--Given our timespan is in minutes, lets also assume we dont care about seconds in start or due dates
set @start_date = DATEADD(ss,datepart(ss,@start_date)*-1,@start_date)
while (@time_span > 0)
begin
set @due_date = DATEADD(MINUTE,@time_span,@start_date)
set @date_string = FORMAT(DATEADD(dd, 0, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @today_closing = (select convert(datetime,@date_string + ' ' + close_time) from @business_hours where work_day = DATENAME(weekday,@start_date))
if exists((select work_day from @business_hours where work_day = DATENAME(weekday,@start_date)))
set @is_workday = @true
else
set @is_workday = @false
if exists(select holiday from @holidays where holiday = @date_string)
set @is_holiday = @true
else
set @is_holiday = @false
if @is_workday = @true and @is_holiday = @false
begin
if @due_date > @today_closing
set @time_span = @time_span - datediff(MINUTE, @start_date, @today_closing)
else
set @time_span = @time_span - datediff(minute, @start_date, @due_date)
end
set @date_string = FORMAT(DATEADD(dd, 1, DATEDIFF(dd, 0, @start_date)),'yyyy-MM-dd')
set @start_date = CONVERT(datetime, @date_string + ' ' + isnull((select open_time from @business_hours where work_day = DATENAME(weekday,convert(datetime,@date_string))),''))
end
select @due_date
answered Jan 1 '15 at 23:26
G BG B
1,2546 silver badges10 bronze badges
1,2546 silver badges10 bronze badges
add a comment |
add a comment |
Here is an option using a WorkSchedule
table, which will contain the business hours that are available to count towards the SLA. To account for weekends and holidays, just do not insert records for these days into the WorkSchedule
table.
This solution also uses a "Tally" table aka numbers table in the due date calc. I also included debug output to help you see what is going on, so just comment out or uncomment any debug sections to see less/more info.
I used SQL temp tables in this example so that you can run it without messing up your current database schema, but you should replace with physical tables if you use this solution.
Test Data setups:
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO
--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go
--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT
workStart = dateadd(hour, 8, t.workDate)
, workEnd = dateadd(hour, 17, t.workDate)
FROM (
SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
FROM sys.objects o cross apply sys.objects o2
) t
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;
GO
Code to calculate Due Date:
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;
DECLARE @workStartDayOne datetime;
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;
--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT:
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.
--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart
--increase the SLA timespan mins to account for difference between work start and start time
, @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;
--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;
--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
SELECT TOP (@SLA_Adjusted)
s.WorkStart, s.WorkEnd
, WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
, t.N as MinuteOfWorkDay
, RowNum = row_number() over(order by s.WorkStart, t.N)
FROM #WorkSchedule s
INNER JOIN #Tally t
ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
WHERE s.WorkStart >= @workStartDayOne
ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute
FROM cteWorkMins m
WHERE m.RowNum = @SLA_Adjusted
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m
--WHERE m.RowNum = @SLA_Adjusted
ORDER BY m.WorkMinute
--*/
;
select @dueDate as DueDate;
GO
Test Cleanup:
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
GO
add a comment |
Here is an option using a WorkSchedule
table, which will contain the business hours that are available to count towards the SLA. To account for weekends and holidays, just do not insert records for these days into the WorkSchedule
table.
This solution also uses a "Tally" table aka numbers table in the due date calc. I also included debug output to help you see what is going on, so just comment out or uncomment any debug sections to see less/more info.
I used SQL temp tables in this example so that you can run it without messing up your current database schema, but you should replace with physical tables if you use this solution.
Test Data setups:
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO
--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go
--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT
workStart = dateadd(hour, 8, t.workDate)
, workEnd = dateadd(hour, 17, t.workDate)
FROM (
SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
FROM sys.objects o cross apply sys.objects o2
) t
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;
GO
Code to calculate Due Date:
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;
DECLARE @workStartDayOne datetime;
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;
--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT:
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.
--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart
--increase the SLA timespan mins to account for difference between work start and start time
, @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;
--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;
--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
SELECT TOP (@SLA_Adjusted)
s.WorkStart, s.WorkEnd
, WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
, t.N as MinuteOfWorkDay
, RowNum = row_number() over(order by s.WorkStart, t.N)
FROM #WorkSchedule s
INNER JOIN #Tally t
ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
WHERE s.WorkStart >= @workStartDayOne
ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute
FROM cteWorkMins m
WHERE m.RowNum = @SLA_Adjusted
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m
--WHERE m.RowNum = @SLA_Adjusted
ORDER BY m.WorkMinute
--*/
;
select @dueDate as DueDate;
GO
Test Cleanup:
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
GO
add a comment |
Here is an option using a WorkSchedule
table, which will contain the business hours that are available to count towards the SLA. To account for weekends and holidays, just do not insert records for these days into the WorkSchedule
table.
This solution also uses a "Tally" table aka numbers table in the due date calc. I also included debug output to help you see what is going on, so just comment out or uncomment any debug sections to see less/more info.
I used SQL temp tables in this example so that you can run it without messing up your current database schema, but you should replace with physical tables if you use this solution.
Test Data setups:
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO
--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go
--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT
workStart = dateadd(hour, 8, t.workDate)
, workEnd = dateadd(hour, 17, t.workDate)
FROM (
SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
FROM sys.objects o cross apply sys.objects o2
) t
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;
GO
Code to calculate Due Date:
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;
DECLARE @workStartDayOne datetime;
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;
--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT:
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.
--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart
--increase the SLA timespan mins to account for difference between work start and start time
, @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;
--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;
--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
SELECT TOP (@SLA_Adjusted)
s.WorkStart, s.WorkEnd
, WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
, t.N as MinuteOfWorkDay
, RowNum = row_number() over(order by s.WorkStart, t.N)
FROM #WorkSchedule s
INNER JOIN #Tally t
ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
WHERE s.WorkStart >= @workStartDayOne
ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute
FROM cteWorkMins m
WHERE m.RowNum = @SLA_Adjusted
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m
--WHERE m.RowNum = @SLA_Adjusted
ORDER BY m.WorkMinute
--*/
;
select @dueDate as DueDate;
GO
Test Cleanup:
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
GO
Here is an option using a WorkSchedule
table, which will contain the business hours that are available to count towards the SLA. To account for weekends and holidays, just do not insert records for these days into the WorkSchedule
table.
This solution also uses a "Tally" table aka numbers table in the due date calc. I also included debug output to help you see what is going on, so just comment out or uncomment any debug sections to see less/more info.
I used SQL temp tables in this example so that you can run it without messing up your current database schema, but you should replace with physical tables if you use this solution.
Test Data setups:
CREATE TABLE #WorkSchedule(WorkStart datetime not null primary key, WorkEnd datetime not null);
GO
CREATE TABLE #Tally (N int not null primary key);
GO
--POPULATE TEST DATA
--populate Tally table
insert into #Tally (N)
select top 10000 N = row_number() over(order by o.object_id)
from sys.objects o cross apply sys.objects o2
;
go
--POPULATE WITH DUMMY TEST DATA
INSERT INTO #WorkSchedule(WorkStart, WorkEnd)
SELECT
workStart = dateadd(hour, 8, t.workDate)
, workEnd = dateadd(hour, 17, t.workDate)
FROM (
SELECT top 10000 workDate = dateadd(day, row_number() over(order by o.object_id), '2000-01-01')
FROM sys.objects o cross apply sys.objects o2
) t
--Exclude weekends from work schedule
WHERE datename(weekday, t.workDate) not in ('Saturday','Sunday')
;
GO
Code to calculate Due Date:
SET NOCOUNT ON;
DECLARE @startDate datetime;
DECLARE @SLA_timespan_mins int;
DECLARE @workStartDayOne datetime;
DECLARE @SLA_Adjusted int;
DECLARE @dueDate datetime;
--SET PARAM VALUES HERE FOR TESTING TO ANY DATE/SLA TIMESPAN YOU WANT:
SET @startDate = '2014-01-04 05:00'; --Saturday
SET @SLA_timespan_mins = 10 * 60 ; --10 hrs.
--get the info day 1, since your start date might be after the work start time.
select top 1 @workStartDayOne = s.WorkStart
--increase the SLA timespan mins to account for difference between work start and start time
, @SLA_Adjusted = case when @startDate > s.WorkStart then datediff(minute, s.WorkStart, @startDate) else 0 end + @SLA_timespan_mins
from #WorkSchedule s
where s.WorkEnd > @startDate
and s.WorkStart <> s.WorkEnd
order by s.WorkStart asc
;
--DEBUG info:
select 'Debug Info' as DebugInfo, @startDate AS StartDate, @workStartDayOne as workStartDayOne, @SLA_timespan_mins as SLA_timespan_mins, @SLA_Adjusted as SLA_Adjusted;
--now sum all the non work hours during that period and determine the additional mins that need added.
;with cteWorkMins as
(
SELECT TOP (@SLA_Adjusted)
s.WorkStart, s.WorkEnd
, WorkMinute = dateadd(minute, t.N, cast(s.WorkStart as datetime))
, t.N as MinuteOfWorkDay
, RowNum = row_number() over(order by s.WorkStart, t.N)
FROM #WorkSchedule s
INNER JOIN #Tally t
ON t.N between 1 and datediff(minute, s.WorkStart, s.WorkEnd)
WHERE s.WorkStart >= @workStartDayOne
ORDER BY s.WorkStart, t.N
)
/**/
SELECT @dueDate = m.WorkMinute
FROM cteWorkMins m
WHERE m.RowNum = @SLA_Adjusted
--*/
/**
--DEBUG: this query will show every minute that is accounted for during the Due Date calculation.
SELECT m.*
FROM cteWorkMins m
--WHERE m.RowNum = @SLA_Adjusted
ORDER BY m.WorkMinute
--*/
;
select @dueDate as DueDate;
GO
Test Cleanup:
IF object_id('TEMPDB..#WorkSchedule') IS NOT NULL
DROP TABLE #WorkSchedule;
GO
IF object_id('TEMPDB..#Tally') IS NOT NULL
DROP TABLE #Tally;
GO
answered Jan 3 '15 at 17:52
BateTechBateTech
4,4573 gold badges15 silver badges30 bronze badges
4,4573 gold badges15 silver badges30 bronze badges
add a comment |
add a comment |
as I understood from your question, what you need is as follow
- You have given start date and number of minutes added to it, then you need to get the due date
- To get the due date, you need to exclude the holidays and the due date should be during business day
here is what you can do
declare @start datetime,
@min int,
@days int
set @start= '28 Dec 2014'
set @min = 2200
-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))
-- get the due date
select max(Date)
from
(select row_number() over( order by t.Id)-1 as Id,t.Date
from DateMetadata t
inner join BusinessDays b on Day(t.Date) = b.Day
where t.Date > = @start and not exists(select 1 from Holidays h
where h.Day=Day(t.Date)
and h.Month=Month(t.Date))) as p
where p.Id < @days
Note :that DateMetadata table you will setup it in your database once
the setup for the above code :
create table Holidays(Id int identity(1,1),
Name nvarchar(50),
Day int,
Month int)
create table BusinessDays(Id int identity(1,1),
Name nvarchar(20),
Day int)
-- i am putting some days that are known,
-- it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)
-- i am assuming that the business days are from monday till friday and
-- saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
this table is needed and you will setup it once
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
Date datetime)
declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
insert into DateMetadata(Date) values(@date)
set @date = @date + 1
end
here a working DEMO
if you need any explanation, i am ready
hope it will help you
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
add a comment |
as I understood from your question, what you need is as follow
- You have given start date and number of minutes added to it, then you need to get the due date
- To get the due date, you need to exclude the holidays and the due date should be during business day
here is what you can do
declare @start datetime,
@min int,
@days int
set @start= '28 Dec 2014'
set @min = 2200
-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))
-- get the due date
select max(Date)
from
(select row_number() over( order by t.Id)-1 as Id,t.Date
from DateMetadata t
inner join BusinessDays b on Day(t.Date) = b.Day
where t.Date > = @start and not exists(select 1 from Holidays h
where h.Day=Day(t.Date)
and h.Month=Month(t.Date))) as p
where p.Id < @days
Note :that DateMetadata table you will setup it in your database once
the setup for the above code :
create table Holidays(Id int identity(1,1),
Name nvarchar(50),
Day int,
Month int)
create table BusinessDays(Id int identity(1,1),
Name nvarchar(20),
Day int)
-- i am putting some days that are known,
-- it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)
-- i am assuming that the business days are from monday till friday and
-- saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
this table is needed and you will setup it once
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
Date datetime)
declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
insert into DateMetadata(Date) values(@date)
set @date = @date + 1
end
here a working DEMO
if you need any explanation, i am ready
hope it will help you
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
add a comment |
as I understood from your question, what you need is as follow
- You have given start date and number of minutes added to it, then you need to get the due date
- To get the due date, you need to exclude the holidays and the due date should be during business day
here is what you can do
declare @start datetime,
@min int,
@days int
set @start= '28 Dec 2014'
set @min = 2200
-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))
-- get the due date
select max(Date)
from
(select row_number() over( order by t.Id)-1 as Id,t.Date
from DateMetadata t
inner join BusinessDays b on Day(t.Date) = b.Day
where t.Date > = @start and not exists(select 1 from Holidays h
where h.Day=Day(t.Date)
and h.Month=Month(t.Date))) as p
where p.Id < @days
Note :that DateMetadata table you will setup it in your database once
the setup for the above code :
create table Holidays(Id int identity(1,1),
Name nvarchar(50),
Day int,
Month int)
create table BusinessDays(Id int identity(1,1),
Name nvarchar(20),
Day int)
-- i am putting some days that are known,
-- it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)
-- i am assuming that the business days are from monday till friday and
-- saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
this table is needed and you will setup it once
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
Date datetime)
declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
insert into DateMetadata(Date) values(@date)
set @date = @date + 1
end
here a working DEMO
if you need any explanation, i am ready
hope it will help you
as I understood from your question, what you need is as follow
- You have given start date and number of minutes added to it, then you need to get the due date
- To get the due date, you need to exclude the holidays and the due date should be during business day
here is what you can do
declare @start datetime,
@min int,
@days int
set @start= '28 Dec 2014'
set @min = 2200
-- get the number of days
set @days=datediff(day,@start,dateadd(minute,@min,@start))
-- get the due date
select max(Date)
from
(select row_number() over( order by t.Id)-1 as Id,t.Date
from DateMetadata t
inner join BusinessDays b on Day(t.Date) = b.Day
where t.Date > = @start and not exists(select 1 from Holidays h
where h.Day=Day(t.Date)
and h.Month=Month(t.Date))) as p
where p.Id < @days
Note :that DateMetadata table you will setup it in your database once
the setup for the above code :
create table Holidays(Id int identity(1,1),
Name nvarchar(50),
Day int,
Month int)
create table BusinessDays(Id int identity(1,1),
Name nvarchar(20),
Day int)
-- i am putting some days that are known,
-- it depends on you to define which holidays you want
insert into Holidays (Name,Day,Month) values('Christmas',25,12)
insert into Holidays(Name,Day,Month) values('New Year',31,12)
insert into Holidays(Name,Day,Month) values('Valentine',14,2)
insert into Holidays(Name,Day,Month) values('Mothers day',21,3)
insert into Holidays(Name,Day,Month) values('April fools day',1,4)
-- i am assuming that the business days are from monday till friday and
-- saturday and sunday are off days
insert into BusinessDays(Name,Day) values ('Monday',1)
insert into BusinessDays(Name,Day) values('Tuesday',2)
insert into BusinessDays(Name,Day) values('Wednesday',3)
insert into BusinessDays(Name,Day) values('Thursday',4)
insert into BusinessDays(Name,Day) values('Friday',5)
this table is needed and you will setup it once
-- set up a table that contains all dates from now till 2050 for example
-- and you can change the value of 2050 depending on your needs
-- this table you will setup it once
create table DateMetadata(Id int identity(1,1),
Date datetime)
declare @date datetime
set @date='01 Jan 2014'
while @date < '31 Dec 2050'
begin
insert into DateMetadata(Date) values(@date)
set @date = @date + 1
end
here a working DEMO
if you need any explanation, i am ready
hope it will help you
answered Jan 6 '15 at 18:02
MonahMonah
5,4705 gold badges18 silver badges48 bronze badges
5,4705 gold badges18 silver badges48 bronze badges
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
add a comment |
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
1
1
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
That is not how holidays work. Some of them, yes, but not for anything that changes per year (i.e. Thanksgiving = 4th Thursday in November, and July 4th could be observed on either the 3rd or 5th if the 4th is on a Saturday or Sunday, respectively).
– Solomon Rutzky
Jan 6 '15 at 18:31
1
1
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
@srutzky at the end it depends on the country he is from and the table i defined is somehow flexible to define any holiday he wants independent from the year, i looked to the calendar and i picked some
– Monah
Jan 6 '15 at 18:38
add a comment |
Sql to Calculate due date excluding holidays and considering business hour as below :- < note : - It's working correctly Business hour (8-5) Maintain holiday table
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
>
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare @holidays table (HDate DateTime) -- Table variable to hold holidayes
---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)
--check for weekends
set @start = case(datepart(dw,@start)+@@datefirst-1)%7
when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
else @start end
-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')
-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
-- loop start
while (@slamins > 0)
begin
-- prepared closing date time based on start date
set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
set @due_date = @start
-- calculate number of Minute between start date and closing date
set @temp = DATEDIFF(N, @start , @today_closing);
--check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7)
set @is_workday = @true
else
set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
set @is_holiday =@false
else
set @is_holiday = @true
if @is_workday = @true and @is_holiday = @false
begin
if(@temp < @slamins)
begin
set @slamins = @slamins - @temp
end
else
begin
set @due_date = DATEADD(MINUTE,@slamins,@start)
set @slamins = 0
print @due_date
end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end
select @due_date
add a comment |
Sql to Calculate due date excluding holidays and considering business hour as below :- < note : - It's working correctly Business hour (8-5) Maintain holiday table
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
>
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare @holidays table (HDate DateTime) -- Table variable to hold holidayes
---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)
--check for weekends
set @start = case(datepart(dw,@start)+@@datefirst-1)%7
when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
else @start end
-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')
-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
-- loop start
while (@slamins > 0)
begin
-- prepared closing date time based on start date
set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
set @due_date = @start
-- calculate number of Minute between start date and closing date
set @temp = DATEDIFF(N, @start , @today_closing);
--check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7)
set @is_workday = @true
else
set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
set @is_holiday =@false
else
set @is_holiday = @true
if @is_workday = @true and @is_holiday = @false
begin
if(@temp < @slamins)
begin
set @slamins = @slamins - @temp
end
else
begin
set @due_date = DATEADD(MINUTE,@slamins,@start)
set @slamins = 0
print @due_date
end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end
select @due_date
add a comment |
Sql to Calculate due date excluding holidays and considering business hour as below :- < note : - It's working correctly Business hour (8-5) Maintain holiday table
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
>
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare @holidays table (HDate DateTime) -- Table variable to hold holidayes
---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)
--check for weekends
set @start = case(datepart(dw,@start)+@@datefirst-1)%7
when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
else @start end
-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')
-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
-- loop start
while (@slamins > 0)
begin
-- prepared closing date time based on start date
set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
set @due_date = @start
-- calculate number of Minute between start date and closing date
set @temp = DATEDIFF(N, @start , @today_closing);
--check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7)
set @is_workday = @true
else
set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
set @is_holiday =@false
else
set @is_holiday = @true
if @is_workday = @true and @is_holiday = @false
begin
if(@temp < @slamins)
begin
set @slamins = @slamins - @temp
end
else
begin
set @due_date = DATEADD(MINUTE,@slamins,@start)
set @slamins = 0
print @due_date
end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end
select @due_date
Sql to Calculate due date excluding holidays and considering business hour as below :- < note : - It's working correctly Business hour (8-5) Maintain holiday table
CREATE TABLE [dbo].[holiday](
[id] [int] IDENTITY(1,1) NOT NULL,
[region] [nvarchar](10) NULL,
[Hdate] [date] NULL,
)
>
declare @start datetime= getdate()
declare @slamins int =960 --- SLA time in mins
declare @Country varchar(2)='NA'
declare @start_hour int = 8 -- business start hour
declare @end_hour int = 17 -- business end hour
declare @true bit = 'true'
declare @false bit = 'false'
declare @is_workday bit = @true
declare @is_holiday bit = @false
declare @due_date datetime
declare @today_closing datetime
declare @temp int = 0
declare @holidays table (HDate DateTime) -- Table variable to hold holidayes
---- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
Insert Into @Holidays (HDate) Select date from HOLIDAY Where region=@Country and Hdate>=DateAdd(dd, DateDiff(dd,0,@start), 0)
--check for weekends
set @start = case(datepart(dw,@start)+@@datefirst-1)%7
when 0 then Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
when 6 then Convert(dateTime,CONVERT(varchar,@start+2,101)+' 08:00:00')
else @start end
-- check if start time is before business hour
if datepart(hh, @start) < @start_hour set @start = Convert(dateTime,CONVERT(varchar,@start,101)+' 08:00:00')
-- check if start time is after business hour
if datepart(hh, @start) >= @end_hour set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
-- loop start
while (@slamins > 0)
begin
-- prepared closing date time based on start date
set @today_closing = Convert(dateTime,CONVERT(varchar,@start,101)+' 17:00:00')
set @due_date = @start
-- calculate number of Minute between start date and closing date
set @temp = DATEDIFF(N, @start , @today_closing);
--check for weekends
if (DATEPART(dw, @start)!=1 AND DATEPART(dw, @start)!=7)
set @is_workday = @true
else
set @is_workday = @false
--check for holidays
if (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@start), 0)) = 0
set @is_holiday =@false
else
set @is_holiday = @true
if @is_workday = @true and @is_holiday = @false
begin
if(@temp < @slamins)
begin
set @slamins = @slamins - @temp
end
else
begin
set @due_date = DATEADD(MINUTE,@slamins,@start)
set @slamins = 0
print @due_date
end
end
set @start = Convert(dateTime,CONVERT(varchar,@start+1,101)+' 08:00:00')
end
select @due_date
answered Jun 24 '16 at 11:26
Subhash KadSubhash Kad
111 bronze badge
111 bronze badge
add a comment |
add a comment |
I created a function to calculate due date from the table, once it's populated as per Beth's and others' approaches (various similar methods for doing this, as you can see -- it only took me about an hour to think about all the UK holidays and populate the table including Easter dates up to 2029 without using these exact guides).
Note that my table contains SLA in business hours (8 hours in a normal day, 5 days in a normal week), your business hours may vary but you can amend this easily, just make sure your business hours are set the same for both the SLA table and the function below.
Code below is T-SQL (written in SSMS v17.8.1)
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
@CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)
/*
baldmosher™
2019-03-25
* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
* <24hrs calendar = <8hrs business
* =24hrs calendar = 8hrs business
* >24hrs calendar = 8hrs business * business days
*/
BEGIN
IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;
DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;
--SET DATEFIRST 1;
DECLARE @DueDate DATETIME;
DECLARE @BusHrsStart DECIMAL(18,10) = 8 ; -- start of Business Hours (8am)
DECLARE @BusHrsClose DECIMAL(18,10) = 16 ; -- close of Business Hours (4pm)
--DECLARE @WkndStart DECIMAL(18,10) = 6 ; -- start of weekend (Sat)
DECLARE @Hours_Today SMALLINT ; -- # hours left in day to process ticket
-- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
-- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart
--created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
END
IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
--created after normal hours, adjust @CreatedDate to @BusHrsStart next day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
--adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
SET @CreatedDate = CAST(@CreatedDate AS DATE)
StartNextWorkingDay:
IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0
BEGIN
SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
GOTO StartNextWorkingDay
END
--DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0); -- midnight, Monday next week
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate); -- BusHrsStart
END
-- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
SET @DueDate = @CreatedDate
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
DueNextWorkingDay:
IF @SLA_Hours_Remaining > @Hours_Today
BEGIN
-- PRINT 'Due another day'
SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today --adjust remaining time after today's hours
SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME))) --adjust DueDate to first thing next day
END
IF @SLA_Hours_Remaining <= @Hours_Today
BEGIN
-- PRINT 'Due today'
SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
END
ELSE
BEGIN
GOTO DueNextWorkingDay
END
-- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))
RETURN @DueDate
END
GO
Table for SLAs:
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
[SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
[Project] [VARCHAR](20) NULL,
[Issue_Type] [VARCHAR](50) NULL,
[Epic_Name] [VARCHAR](50) NULL,
[SLA_Business_Hours] [SMALLINT] NULL,
[Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO
add a comment |
I created a function to calculate due date from the table, once it's populated as per Beth's and others' approaches (various similar methods for doing this, as you can see -- it only took me about an hour to think about all the UK holidays and populate the table including Easter dates up to 2029 without using these exact guides).
Note that my table contains SLA in business hours (8 hours in a normal day, 5 days in a normal week), your business hours may vary but you can amend this easily, just make sure your business hours are set the same for both the SLA table and the function below.
Code below is T-SQL (written in SSMS v17.8.1)
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
@CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)
/*
baldmosher™
2019-03-25
* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
* <24hrs calendar = <8hrs business
* =24hrs calendar = 8hrs business
* >24hrs calendar = 8hrs business * business days
*/
BEGIN
IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;
DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;
--SET DATEFIRST 1;
DECLARE @DueDate DATETIME;
DECLARE @BusHrsStart DECIMAL(18,10) = 8 ; -- start of Business Hours (8am)
DECLARE @BusHrsClose DECIMAL(18,10) = 16 ; -- close of Business Hours (4pm)
--DECLARE @WkndStart DECIMAL(18,10) = 6 ; -- start of weekend (Sat)
DECLARE @Hours_Today SMALLINT ; -- # hours left in day to process ticket
-- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
-- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart
--created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
END
IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
--created after normal hours, adjust @CreatedDate to @BusHrsStart next day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
--adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
SET @CreatedDate = CAST(@CreatedDate AS DATE)
StartNextWorkingDay:
IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0
BEGIN
SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
GOTO StartNextWorkingDay
END
--DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0); -- midnight, Monday next week
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate); -- BusHrsStart
END
-- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
SET @DueDate = @CreatedDate
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
DueNextWorkingDay:
IF @SLA_Hours_Remaining > @Hours_Today
BEGIN
-- PRINT 'Due another day'
SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today --adjust remaining time after today's hours
SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME))) --adjust DueDate to first thing next day
END
IF @SLA_Hours_Remaining <= @Hours_Today
BEGIN
-- PRINT 'Due today'
SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
END
ELSE
BEGIN
GOTO DueNextWorkingDay
END
-- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))
RETURN @DueDate
END
GO
Table for SLAs:
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
[SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
[Project] [VARCHAR](20) NULL,
[Issue_Type] [VARCHAR](50) NULL,
[Epic_Name] [VARCHAR](50) NULL,
[SLA_Business_Hours] [SMALLINT] NULL,
[Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO
add a comment |
I created a function to calculate due date from the table, once it's populated as per Beth's and others' approaches (various similar methods for doing this, as you can see -- it only took me about an hour to think about all the UK holidays and populate the table including Easter dates up to 2029 without using these exact guides).
Note that my table contains SLA in business hours (8 hours in a normal day, 5 days in a normal week), your business hours may vary but you can amend this easily, just make sure your business hours are set the same for both the SLA table and the function below.
Code below is T-SQL (written in SSMS v17.8.1)
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
@CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)
/*
baldmosher™
2019-03-25
* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
* <24hrs calendar = <8hrs business
* =24hrs calendar = 8hrs business
* >24hrs calendar = 8hrs business * business days
*/
BEGIN
IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;
DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;
--SET DATEFIRST 1;
DECLARE @DueDate DATETIME;
DECLARE @BusHrsStart DECIMAL(18,10) = 8 ; -- start of Business Hours (8am)
DECLARE @BusHrsClose DECIMAL(18,10) = 16 ; -- close of Business Hours (4pm)
--DECLARE @WkndStart DECIMAL(18,10) = 6 ; -- start of weekend (Sat)
DECLARE @Hours_Today SMALLINT ; -- # hours left in day to process ticket
-- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
-- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart
--created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
END
IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
--created after normal hours, adjust @CreatedDate to @BusHrsStart next day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
--adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
SET @CreatedDate = CAST(@CreatedDate AS DATE)
StartNextWorkingDay:
IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0
BEGIN
SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
GOTO StartNextWorkingDay
END
--DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0); -- midnight, Monday next week
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate); -- BusHrsStart
END
-- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
SET @DueDate = @CreatedDate
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
DueNextWorkingDay:
IF @SLA_Hours_Remaining > @Hours_Today
BEGIN
-- PRINT 'Due another day'
SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today --adjust remaining time after today's hours
SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME))) --adjust DueDate to first thing next day
END
IF @SLA_Hours_Remaining <= @Hours_Today
BEGIN
-- PRINT 'Due today'
SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
END
ELSE
BEGIN
GOTO DueNextWorkingDay
END
-- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))
RETURN @DueDate
END
GO
Table for SLAs:
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
[SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
[Project] [VARCHAR](20) NULL,
[Issue_Type] [VARCHAR](50) NULL,
[Epic_Name] [VARCHAR](50) NULL,
[SLA_Business_Hours] [SMALLINT] NULL,
[Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO
I created a function to calculate due date from the table, once it's populated as per Beth's and others' approaches (various similar methods for doing this, as you can see -- it only took me about an hour to think about all the UK holidays and populate the table including Easter dates up to 2029 without using these exact guides).
Note that my table contains SLA in business hours (8 hours in a normal day, 5 days in a normal week), your business hours may vary but you can amend this easily, just make sure your business hours are set the same for both the SLA table and the function below.
Code below is T-SQL (written in SSMS v17.8.1)
CREATE FUNCTION [JIRA].[Fn_JIRA_Due_Date] (
@CreatedDate DATETIME, @SLA_Business_Hours INTEGER
) RETURNS DATETIME
AS
-- SELECT [JIRA].[Fn_JIRA_Due_Date]('2019-12-28 08:00:00', 24)
/*
baldmosher™
2019-03-25
* Function returns the DueDate for a JIRA ticket, based on the CreatedDate and the SLA (based on the Issue Type, or the Epic for Tasks) and business hours per date (set in [JIRA].[Ref_JIRA_Business_Hours])
* Called by IUP to store this at the time the ticket is loaded
* Can only consider SLA in Business Hours:
* <24hrs calendar = <8hrs business
* =24hrs calendar = 8hrs business
* >24hrs calendar = 8hrs business * business days
*/
BEGIN
IF @CreatedDate IS NULL OR @SLA_Business_Hours IS NULL RETURN NULL;
DECLARE @SLA_Hours_Remaining SMALLINT = @SLA_Business_Hours;
--SET DATEFIRST 1;
DECLARE @DueDate DATETIME;
DECLARE @BusHrsStart DECIMAL(18,10) = 8 ; -- start of Business Hours (8am)
DECLARE @BusHrsClose DECIMAL(18,10) = 16 ; -- close of Business Hours (4pm)
--DECLARE @WkndStart DECIMAL(18,10) = 6 ; -- start of weekend (Sat)
DECLARE @Hours_Today SMALLINT ; -- # hours left in day to process ticket
-- PRINT 'Created ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--!!!! extend to the next whole hour just to simplify reporting -- need to work on fixing this eventually
SET @CreatedDate = DATEADD(MINUTE,60-DATEPART(MINUTE,@CreatedDate),@CreatedDate)
-- PRINT 'Rounded ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--check if created outside business hours and adjust CreatedDate to start the clock first thing at the next business hours start of day (days are checked next)
IF DATEPART(HOUR,@CreatedDate) < @BusHrsStart
--created before normal hours, adjust @CreatedDate later to @BusHrsStart same day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate AS DATE) AS DATETIME))
END
IF DATEPART(HOUR,@CreatedDate) >= @BusHrsClose
--created after normal hours, adjust @CreatedDate to @BusHrsStart next day
BEGIN
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,CAST(CAST(@CreatedDate+1 AS DATE) AS DATETIME))
--adjust CreatedDate to start the clock the next day with >0 business hours (i.e. extend if it falls on a weekend or holiday)
SET @CreatedDate = CAST(@CreatedDate AS DATE)
StartNextWorkingDay:
IF (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = @CreatedDate ORDER BY [Date]) = 0
BEGIN
SET @CreatedDate = DATEADD(DAY,1,@CreatedDate)
GOTO StartNextWorkingDay
END
--DATEADD(DAY, DATEDIFF(DAY,0,@CreatedDate+7)/7*7,0); -- midnight, Monday next week
SET @CreatedDate = DATEADD(HOUR,@BusHrsStart,@CreatedDate); -- BusHrsStart
END
-- PRINT 'Started ' + CAST(CAST(@CreatedDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@CreatedDate AS TIME) AS VARCHAR(8))
--third, check the business hours for each date from CreatedDate onwards to determine the relevant DueDate
SET @DueDate = @CreatedDate
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
SET @Hours_Today = @BusHrsStart + (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date]) - DATEPART(HOUR, @CreatedDate)
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
DueNextWorkingDay:
IF @SLA_Hours_Remaining > @Hours_Today
BEGIN
-- PRINT 'Due another day'
SET @SLA_Hours_Remaining = @SLA_Hours_Remaining - @Hours_Today --adjust remaining time after today's hours
SET @Hours_Today = (SELECT TOP(1) [Business_Hours] FROM [JIRA].[Ref_JIRA_Business_Hours] b WHERE b.[Date] = CAST(@DueDate AS DATE) ORDER BY [Date])
-- PRINT 'SLA Hrs ' + CAST(@SLA_Hours_Remaining AS VARCHAR(2))
-- PRINT 'Hrs Today ' + CAST(@Hours_Today AS VARCHAR(2))
SET @DueDate = DATEADD(DAY,1,DATEADD(HOUR,@BusHrsStart,CAST(CAST(@DueDate AS DATE) AS DATETIME))) --adjust DueDate to first thing next day
END
IF @SLA_Hours_Remaining <= @Hours_Today
BEGIN
-- PRINT 'Due today'
SET @DueDate = DATEADD(HOUR,@SLA_Hours_Remaining,@DueDate)
END
ELSE
BEGIN
GOTO DueNextWorkingDay
END
-- PRINT 'DueDate ' + CAST(CAST(@DueDate AS DATE) AS VARCHAR(10)) + ' ' + CAST(CAST(@DueDate AS TIME) AS VARCHAR(8))
RETURN @DueDate
END
GO
Table for SLAs:
CREATE TABLE [JIRA].[Ref_JIRA_SLAs](
[SLA_SK] [SMALLINT] IDENTITY(1,1) NOT NULL,
[Project] [VARCHAR](20) NULL,
[Issue_Type] [VARCHAR](50) NULL,
[Epic_Name] [VARCHAR](50) NULL,
[SLA_Business_Hours] [SMALLINT] NULL,
[Comments] [VARCHAR](8000) NULL
) ON [PRIMARY] WITH (DATA_COMPRESSION = PAGE)
GO
edited Mar 29 at 18:55
answered Mar 25 at 12:28
baldmosherbaldmosher
898 bronze badges
898 bronze badges
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f27682810%2fcalculating-due-date-using-business-hours-and-holidays%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
You need to give an example of some inputs and expected results if you hope to get an answer that works. You also need to answer some questions, such as: are business hours the same for each business day?, and what do you consider holidays? But I think you can do this without either a loop or a table of dates and business hours.
– Solomon Rutzky
Jan 2 '15 at 2:58
can you mention a sample, and yes you can do it without a loop, just want a sample in other to write the code as you want
– Monah
Jan 6 '15 at 17:02
This looks like possible duplicate of: stackoverflow.com/questions/1044688/…
– panpernicek
Jan 7 '15 at 14:52