October 23, 2006 at 5:47 am
Hi
I’m using the DATEDIFF function to retrieve minutes from 2 dates ( DATEDIFF(HH, date1, date2)),
I want to exclude from this value the time from the week-end
For example, if
date1 = 18-10-2006 15:00PM (Wednesday)
and
date2= 23-10-2006 15:00PM (Monday)
The value retrieved should be 120 (hours), but I want to exclude 48 Hours from the week-end , how can I achieve that ?
I’m thinking about using DATENAME(DW, Date), or (datepart) but….i’m stuck ….
Any guideline for this ?
October 23, 2006 at 7:24 am
DATEDIFF(hour, date1, date2) - (DATEDIFF(week, date1, date2) * 48)
providing date1 and date2 are not sat/sun dates
Far away is close at hand in the images of elsewhere.
Anon.
October 23, 2006 at 7:42 am
Nice indeed J
It´s better now, however date1 and date2, can also be on Sundays and Saturdays…
October 23, 2006 at 9:13 am
This type of problem has occurred in a number of threads on this site with various answers, one of which is to do calculus on the days of the week by adjusting the start date forward to Mon if weekend and backwards for the end date and doing the calculation I posted.
Another method is to create a permanent Calendar table containing dates for several years and hold different values for each day, e.g. Weekday, Weekend, Bank Holiday, Hours in day, Working hours etc
Then you can use this table for joining or in your case selecting rows between your dates, summing the hours. You will have adjust the hours on the start/end dates according to the time portion of those dates.
Far away is close at hand in the images of elsewhere.
Anon.
October 24, 2006 at 3:14 am
Hi, this is a script I have used to create a stored procedure that builds a calendar table between any two given dates. You may find it useful.
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS ON
GO
/************************************************
CREATE PROCEDURE
EXECUTING PROCESDURE REQUIRES TWO ARGUMENTS: -
@STARTDATE = THE FIRST DATE IN THE CALENDAR
@ENDDATE = THE LAST DATE IN THE CALENDAR
************************************************/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[usp_create_calendar]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[usp_create_calendar]
GO
create procedure usp_create_calendar(@StartDate smalldatetime, @EndDate smalldatetime)
as
/************************************************
DROP EXISTING TABLE
************************************************/
if exists (select * from dbo.sysobjects where id = object_id(N'[dim_calendar]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dim_calendar]
/************************************************
CREATE NEW CALENDAR TABLE
************************************************/
CREATE TABLE [dim_calendar](
CalDate smalldatetime NULL
, SOMDate smalldatetime NULL
, EOMDate smalldatetime NULL
, FinYear int NULL
, FinQtr tinyint NULL
, FinQtrName varchar (20) NULL
, FinMonth tinyint NULL
, CalMonthName varchar (20) NULL
, DateKey int NULL
, RentYear int NULL
, RentPrdWk int NULL
, DayOfWeek VARCHAR (10) NULL
 
/************************************************
INSERT FIRST RECORD INTO CALENDAR TABLE
************************************************/
INSERT INTO dim_calendar
SELECT @StartDate
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
/************************************************
DECLARE VARIABLE @MAXCALDATE WHICH IS THE
LAST DATE REQUIRED IN THE CALENDAR
************************************************/
declare @MaxCalDate smalldatetime
/************************************************
SET VARIABLE @MAXCALDATE = THE ARGUMENT @ENDDATE
************************************************/
set @MaxCalDate = @EndDate
/************************************************
WHILE THE DATE IS LESS THAN OR EQUAL TO THE
LAST DATE REQUIRED ADD ANOTHER RECORD TO
THE TABLE
************************************************/
while (@MaxCalDate <= @EndDate)
begin
insert into dim_calendar
select dateadd(dd,1,max(CalDate))
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
from dim_calendar
if (select max(CalDate) from dim_calendar)>= @EndDate
break
else
continue
end
/************************************************
UPDATE EACH RECORD WITH FINANCIAL YEAR, QUARTER
AND MONTH
************************************************/
update dim_calendar
set FinYear = (
select case
when datepart(mm,CalDate) between 4 and 12
then datepart(yyyy,CalDate)+1
else datepart(yyyy,CalDate)
end
 ,
FinQtr = (
select case
when datepart(mm,CalDate) between 4 and 6
then 1
when datepart(mm,CalDate) between 7 and 9
then 2
when datepart(mm,CalDate) between 10 and 12
then 3
else 4
end
 ,
FinQtrName = (
select case
when datepart(mm,CalDate) between 4 and 6
then 'Qtr. 1 '+cast((datepart(yyyy,CalDate)+1)as char)
when datepart(mm,CalDate) between 7 and 9
then 'Qtr. 2 '+cast((datepart(yyyy,CalDate)+1)as char)
when datepart(mm,CalDate) between 10 and 12
then 'Qtr. 3 '+cast((datepart(yyyy,CalDate)+1)as char)
else 'Qtr. 4 '+cast(datepart(yyyy,CalDate)as char)
end
 ,
FinMonth = (
select case
when datepart(mm,CalDate) between 4 and 12
then datepart(mm,CalDate)-3
else datepart(mm,CalDate)+9
end
 ,
CalMonthName = datename(dd,CalDate)+' '+datename(mm,CalDate)+' '+datename(yyyy,CalDate)
, DateKey = (DATEPART(yyyy,Caldate)*100)+DATEPART(mm,Caldate)
, RentYear = (
select case
when datepart(mm,CalDate) between 4 and 12
then datepart(yyyy,CalDate)+1
else datepart(yyyy,CalDate)
end
 
, DayOfWeek = DATENAME(dw,CalDate)
--finds the first (SOMDate) date of each month and the last date of each month (EOMDate) places results
--into a temporary table. This temporary table is then used to update the calendar table
select min(CalDate)SOMDate,
max(CalDate)EOMDate
into #tmp_cal_months
from dim_calendar
group by datepart(yyyy,CalDate),
datepart(mm,CalDate)
order by datepart(yyyy,CalDate),
datepart(mm,CalDate)
update c
set c.SOMDate = t1.SOMDate,
c.EOMDate = t1.EOMDate
from #tmp_cal_months t1,
dim_calendar c
where datepart(yyyy,t1.SOMDate) = datepart(yyyy,c.CalDate)
and
datepart(mm,t1.SOMDate) = datepart(mm,c.CalDate)
drop table #tmp_cal_months
GO
SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO
Regards
Duncan
October 24, 2006 at 5:31 am
It's amazing how many things you can get wrong doing date arithmetic. I think I may have posted this function a while back (see below) which calculates the number of workdays between any two dates. These are some requirements:
1. Should be efficient (i.e. not loop through the days or use tables)
2. Should handle negative differencences
3. Needs to include David's suggestion of normalizing the start and end dates
Whatever clever thing you come up with, I suggest you always bulk test it using an alternative function which does just the counting.
It should not be very difficult to apply this to the original problem. You need to take the (result-2)*24 and add the hours from the first day(if it's not on a weekend) and the hours from the last day(if it's not on a weekend).
create function trx_workdays1(@p_startdate datetime, @p_enddate datetime) returns integer as
begin
declare @startdate datetime
declare @enddate datetime
declare @dowSat int
declare @dowSun int
declare @normstartdate datetime
declare @normenddate datetime
declare @DW int
declare @dwdest int
declare @padded_enddate datetime
declare @padded_workdays int
declare @diff int
if @p_startdate is null or @p_enddate is null
return 0
-- truncate time portion
set @startdate=convert(varchar,year(@p_startdate))+substring(convert(varchar,month(@p_startdate)+100),2,2)+substring(convert(varchar,day(@p_startdate)+100),2,2)
set @enddate=convert(varchar,year(@p_enddate))+substring(convert(varchar,month(@p_enddate)+100),2,2)+substring(convert(varchar,day(@p_enddate)+100),2,2)
-- capture these for known dates as they may vary based on SET DATEFIRST
set @dowSat=datepart(dw,'2006-10-28')
set @dowSun=datepart(dw,'2006-10-29')
-- normalize start date by moving forward, i.e. should not be a saturday or sunday
set @normstartdate=@startdate
set @DW=datepart(dw,@normstartdate)
if @DW=@dowSun
set @normstartdate=dateadd(d,1,@normstartdate)
else
if @DW=@dowSat
set @normstartdate=dateadd(d,2,@normstartdate)
-- normalize end date by moving backwards, i.e. should not be a saturday or sunday
set @normenddate=@enddate
set @DW=datepart(dw,@normenddate)
if @DW=@dowSun
set @normenddate=dateadd(d,-2,@normenddate)
else
if @DW=@dowSat
set @normenddate=dateadd(d,-1,@normenddate)
set @padded_enddate=@normenddate
set @padded_workdays=0
-- pad end date so that difference becomes a multiple of 7 days;
-- we also need to count the number of weekdays in the days we added for the padding;
set @dwdest=datepart(dw,@startdate)-1
if @dwdest=0 set @dwdest=7
while datepart(dw,@padded_enddate)<>@dwdest -- loops no more than 6 times
begin
set @padded_enddate=dateadd(d,1,@padded_enddate)
set @DW=datepart(dw,@padded_enddate)
if @DW<>@dowSat and @DW<>@dowSun set @padded_workdays=@padded_workdays+1
end
set @diff=datediff(d,@startdate,@padded_enddate)+1
-- subtract number of intervening saturdays/sundays and subtract number of workdays we added for the padding
return @diff-@diff/7*2-@padded_workdays
end
October 24, 2006 at 5:44 am
Oops, line near end
set @diff=datediff(d,@startdate,@padded_enddate)+1
shoud read
set @diff=datediff(d,@normstartdate,@padded_enddate)+1
October 24, 2006 at 8:27 am
Here is a simple approach:
declare
@date1 datetime,@date2 datetime
select
@date1=CURRENT_TIMESTAMP
select
@date2=CURRENT_TIMESTAMP-2
@date1
@date2
if datename(dw,@date1) in('Friday','Saturday','Sunday')
begin
print datediff(hh,@date2,@date1)-48
end
if
datename(dw,@date1) not in('Friday','Saturday','Sunday')
begin
print datediff(hh,@date2,@date1)
end
October 24, 2006 at 8:37 am
I preffer not to use strings manipulation (performance on large datasets)., but your solution could use and else instead of a second if which should make it run pretty fast and make it easy to maintain.
October 24, 2006 at 9:07 am
When I set @date1 to Sun '2006-11-05' and @date1 to Wed '2006-11-01' your code returns 48 hours. That doesn't look right to me.
October 24, 2006 at 5:38 pm
This will give you whole days... modify the "ends" of the range to do hours... also, not affected by @@DATEFIRST settings... very fast... very simple...
SELECT (DATEDIFF(dd,@StartDate,@EndDate)+1)
-(DATEDIFF(wk,@StartDate,@EndDate)*2)
-(CASE WHEN DATENAME(dw,@StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw,@EndDate) = 'Saturday' THEN 1 ELSE 0 END)
For a full explanation of the pieces, please refer to the following URL:
http://www.sqlservercentral.com/columnists/jmoden/calculatingworkdays.asp
--Jeff Moden
Change is inevitable... Change for the better is not.
October 24, 2006 at 6:05 pm
Sorry... had posted a solution here (and deleted it) for hours that didn't take into account days that were on the weekends... I'll be right back with the correction....
--Jeff Moden
Change is inevitable... Change for the better is not.
October 24, 2006 at 6:16 pm
This should do it... don't worry about the couple of strings you see... this is still pretty darned fast and they make it independent of the @@DATEFIRST setting... (you can worry if you want, though)
SELECT CAST(
(
(DATEDIFF(dd,@StartDate,@EndDate)+1)
-(DATEDIFF(wk,@StartDate,@EndDate)*2)
-(CASE WHEN DATENAME(dw,@StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw,@EndDate) = 'Saturday' THEN 1 ELSE 0 END)
)
-(CASE WHEN DATENAME(dw,@StartDate) = 'Sunday' THEN 0
ELSE CAST(CONVERT(CHAR(12),@StartDate,114) AS DATETIME) END)
-(CASE WHEN DATENAME(dw,@EndDate) = 'Saturday' THEN 0
ELSE (1.0-CAST(CONVERT(CHAR(12),@EndDate,114) AS DATETIME)) END)
AS FLOAT) * 24
--Jeff Moden
Change is inevitable... Change for the better is not.
May 7, 2008 at 2:51 am
Hi
I aqm looking into solving the same problem,, but i need to make this in to a trigger. so when the user enters a start time and finish time is calculate the time diff.
can this stored procedure be converted in to a trigger and if so how.??
May 7, 2008 at 4:09 am
It's pretty straight forward. You should read up on how to create a trigger. Here is a complete example:
-- create a sample table
if exists (select name from dbo.sysobjects where name = 'my_table' and type='U') drop table my_table
go
create table my_table(
k integer identity(1,1),
d1 datetime,
d2 datetime,
duration integer)
go
-- create Jeff's workdays function
if exists (select name from sysobjects where name='workdays' and type='FN') drop function workdays
go
create function workdays(@p_startdate datetime, @p_enddate datetime) returns integer as
begin
return
(DATEDIFF(dd, @p_startdate, @p_enddate)+1)
-(DATEDIFF(wk, @p_startdate, @p_enddate) * 2)
-(CASE WHEN DATENAME(dw, @p_startdate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, @p_enddate) = 'Saturday' THEN 1 ELSE 0 END)
end
go
-- create the trigger
if exists (select name from sysobjects where name='my_trigger' and type='TR') drop trigger my_trigger
go
create trigger my_trigger on my_table
for update,insert
as
if @@rowcount = 0 return
if not (update(d1) or update(d2)) return
update m
set duration=dbo.workdays(m.d1,m.d2)
from my_table m
join inserted i on m.k=i.k
where i.d1 is not null and i.d2 is not null
go
-- run some tests
insert into my_table(d1,d2)
values('20080503','20080511')
go
select * from my_table
go
update my_table set d2='20080512'
go
select * from my_table
Viewing 15 posts - 1 through 15 (of 26 total)
You must be logged in to reply to this topic. Login to reply