Related
When looking for a solution for my question I found the following sql (Calculate business hours between two dates).
However, I want the starting point to be 8:30 instead of 9 and the end point 17:30 instead of 17. Currently I am using an integer. Can anyone help me with this? Thanks in advance!
Create Function GetWorkingMin(#StartDate DateTime, #EndDate DateTime, #Country Varchar(2)) Returns Int
AS
Begin
Declare #WorkMin int = 0 -- Initialize counter
Declare #Reverse bit -- Flag to hold if direction is reverse
Declare #StartHour int = 9 -- Start of business hours (can be supplied as an argument if needed)
Declare #EndHour int = 17 -- End of business hours (can be supplied as an argument if needed)
Declare #Holidays Table (HDate DateTime) -- Table variable to hold holidayes
-- If dates are in reverse order, switch them and set flag
If #StartDate>#EndDate
Begin
Declare #TempDate DateTime=#StartDate
Set #StartDate=#EndDate
Set #EndDate=#TempDate
Set #Reverse=1
End
Else Set #Reverse = 0
-- 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 HDate from HOLIDAY Where COUNTRYCODE=#Country and HDATE>=DateAdd(dd, DateDiff(dd,0,#StartDate), 0)
If DatePart(HH, #StartDate)<#StartHour Set #StartDate = DateAdd(hour, #StartHour, DateDiff(DAY, 0, #StartDate)) -- If Start time is less than start hour, set it to start hour
If DatePart(HH, #StartDate)>=#EndHour+1 Set #StartDate = DateAdd(hour, #StartHour+24, DateDiff(DAY, 0, #StartDate)) -- If Start time is after end hour, set it to start hour of next day
If DatePart(HH, #EndDate)>=#EndHour+1 Set #EndDate = DateAdd(hour, #EndHour, DateDiff(DAY, 0, #EndDate)) -- If End time is after end hour, set it to end hour
If DatePart(HH, #EndDate)<#StartHour Set #EndDate = DateAdd(hour, #EndHour-24, DateDiff(DAY, 0, #EndDate)) -- If End time is before start hour, set it to end hour of previous day
If #StartDate>#EndDate Return 0
-- If Start and End is on same day
If DateDiff(Day,#StartDate,#EndDate) <= 0
Begin
If Datepart(dw,#StartDate)>1 And DATEPART(dw,#StartDate)<7 -- If day is between sunday and saturday
If (Select Count(*) From #Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,#StartDate), 0)) = 0 -- If day is not a holiday
If #EndDate<#StartDate Return 0 Else Set #WorkMin=DATEDIFF(MI, #StartDate, #EndDate) -- Calculate difference
Else Return 0
Else Return 0
End
Else Begin
Declare #Partial int=1 -- Set partial day flag
While DateDiff(Day,#StartDate,#EndDate) > 0 -- While start and end days are different
Begin
If Datepart(dw,#StartDate)>1 And DATEPART(dw,#StartDate)<7 -- If this is a weekday
Begin
If (Select Count(*) From #Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,#StartDate), 0)) = 0 -- If this is not a holiday
Begin
If #Partial=1 -- If this is the first iteration, calculate partial time
Begin
Set #WorkMin=#WorkMin + DATEDIFF(MI, #StartDate, DateAdd(hour, #EndHour, DateDiff(DAY, 0, #StartDate)))
Set #StartDate=DateAdd(hour, #StartHour+24, DateDiff(DAY, 0, #StartDate))
Set #Partial=0
End
Else Begin -- If this is a full day, add full minutes
Set #WorkMin=#WorkMin + (#EndHour-#StartHour)*60
Set #StartDate = DATEADD(DD,1,#StartDate)
End
End
Else Set (at)StartDate = DATEADD(HOUR, (at)StartHour, CAST(CAST(DATEADD(DD,1,(at)StartDate) AS DATE) AS DATETIME))
End
Else Set (at)StartDate = DATEADD(HOUR, (at)StartHour, CAST(CAST(DATEADD(DD,1,(at)StartDate) AS DATE) AS DATETIME))
End
If Datepart(dw,#StartDate)>1 And DATEPART(dw,#StartDate)<7 -- If last day is a weekday
If (Select Count(*) From #Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,#StartDate), 0)) = 0 -- And it is not a holiday
If #Partial=0 Set #WorkMin=#WorkMin + DATEDIFF(MI, #StartDate, #EndDate) Else Set #WorkMin=#WorkMin + DATEDIFF(MI, DateAdd(hour, #StartHour, DateDiff(DAY, 0, #StartDate)), #EndDate)
End
If #Reverse=1 Set #WorkMin=-#WorkMin
Return #WorkMin
End
Use TIME for start time. Then create INT variable to put converted value to minute to use for conditions.
I have created sample for you.
DECLARE #StartDate DATETIME='2018-12-10 10:00'
DECLARE #ExpectedStartTime TIME='08:30'
DECLARE #ExpectedStartMin INT=DATEPART(HOUR,#ExpectedStartTime)*60+DATEPART(MINUTE,#ExpectedStartTime)
DECLARE #ActualStartMin INT=DATEPART(HOUR,#StartDate)*60+DATEPART(MINUTE,#StartDate)
--Check before change the StartDate
SELECT #ExpectedStartMin, #ActualStartMin, #StartDate
If #ExpectedStartMin<#ActualStartMin Set #StartDate = CAST(CAST(#StartDate AS DATE) AS DATETIME)+#ExpectedStartTime
-- same way to the other conditions.
--
--
--Check after change the StartDate
SELECT #ExpectedStartMin, #ActualStartMin, #StartDate
I need an SQL function to calculate age. It has to be accurate and cover all corner cases.
It is for hospital ward for babies, so age of 30 minuets is a common case.
I have a looked on other answers but could not find one that deals with all cases.
For example:
Baby born in 2014-04-29 12:59:00.000.
And now is 2014-04-29 13:10:23.000,
Age should be 0 Years, 0 Months, 0 Days, 0 Hours, 11 minutes
It would be great if someone can provide the ultimate and definitive version of that function.
(I am afraid that simple solutions with DateDiff are not good enough. As stated in a more popular question : "The Datediff function doesn't handle year boundaries well ..."
This query will give you date diff in minutes,
select datediff(mi, '2014-04-23 05:23:59.660',getdate())
Then you can simply calc the minutes/60 for hours and minutes mod 60 for minutes
select datediff(mi, '2014-04-23 05:23:59.660',getdate())/60 as [Hours], select datediff(mi, '2014-04-23 05:23:59.660',getdate()) % 60 as [Minutes]
We can use DATEDIFF to get the Year, Month, and Day differences, and then simple division for the Seconds, Minutes, and Hours differences.
I've used #CurrentDate to recreate the original request, but #CurrentDate = GETDATE() will return the age at time of execution.
DECLARE #BirthDate DATETIME
DECLARE #CurrentDate DATETIME
SET #BirthDate = '2014-04-29 12:59:00.000'
SET #CurrentDate = '2014-04-29 13:10:23.000'
DECLARE #DiffInYears INT
DECLARE #DiffInMonths INT
DECLARE #DiffInDays INT
DECLARE #DiffInHours INT
DECLARE #DiffInMinutes INT
DECLARE #DiffInSeconds INT
DECLARE #TotalSeconds BIGINT
-- Determine Year, Month, and Day differences
SET #DiffInYears = DATEDIFF(year, #BirthDate, #CurrentDate)
IF #DiffInYears > 0
SET #BirthDate = DATEADD(year, #DiffInYears, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInYears = #DiffInYears - 1
SET #BirthDate = DATEADD(year, -1, #BirthDate)
END
SET #DiffInMonths = DATEDIFF(month, #BirthDate, #CurrentDate)
IF #DiffInMonths > 0
SET #BirthDate = DATEADD(month, #DiffInMonths, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInMonths = #DiffInMonths - 1
SET #BirthDate = DATEADD(month, -1, #BirthDate)
END
SET #DiffInDays = DATEDIFF(day, #BirthDate, #CurrentDate)
IF #DiffInDays > 0
SET #BirthDate = DATEADD(day, #DiffInDays, #BirthDate)
IF #BirthDate > #CurrentDate
BEGIN
-- Adjust for pushing #BirthDate into future
SET #DiffInDays = #DiffInDays - 1
SET #BirthDate = DATEADD(day, -1, #BirthDate)
END
-- Get number of seconds difference for Hour, Minute, Second differences
SET #TotalSeconds = DATEDIFF(second, #BirthDate, #CurrentDate)
-- Determine Seconds, Minutes, Hours differences
SET #DiffInSeconds = #TotalSeconds % 60
SET #TotalSeconds = #TotalSeconds / 60
SET #DiffInMinutes = #TotalSeconds % 60
SET #TotalSeconds = #TotalSeconds / 60
SET #DiffInHours = #TotalSeconds
-- Display results
SELECT #DiffInYears AS YearsDiff,
#DiffInMonths AS MonthsDiff,
#DiffInDays AS DaysDiff,
#DiffInHours AS HoursDiff,
#DiffInMinutes AS MinutesDiff,
#DiffInSeconds AS SecondsDiff
It will give you date diff in seconds select datediff(s, '2014-04-23 05:23:59.660',getdate())
The following is my stored procedure for setting workingday between two dates
Create procedure [dbo].[sp_workingdays](#startdate date,#enddate date,#createddatetime datetime,#adminid int)
as
declare #start date,#end date
declare #day varchar(50)
declare #timeid int
declare #daypare int
declare #workingdaytype varchar(20)
begin
set #start=#startdate
set #end=#enddate
while (#start<=#end)
begin
if #start not in (select Date from Generalholyday_details)
begin
select #day= DATENAME(dw,#start)
select #workingdaytype =case (DATEPART(DW,#start)+##DATEFIRST)%7 when 1 then 'Leave Day' when 2 then 'Full Day' when 3 then 'Full Day' when 4 then 'Full Day'when 5 then 'Full Day' when 6 then 'Full Day' when 0 then 'Half Day' end
select #timeid=Time_id from Workingdaytimesetting_details where Workingday_type=#workingdaytype
insert into Workingday_details(Working_date,working_day,Time_id) values(#start,#day,#timeid)
update Workingday_details set createddatetime=#createddatetime where createddatetime is null
update Workingday_details set adminid=#adminid where adminid is null
end
set #start=DATEADD(day,1,#start)
end
end
GO
In datepart line 1 is for sunday. but actually when i run the stored procedure, i got 1 for Saturday. how can i set 1 for sunday.In my previous system i got 1 for sunday in same procedure.
Have a look at SET DATEFIRST (Transact-SQL)
Sets the first day of the week to a number from 1 through 7.
To see the current setting of SET DATEFIRST, use the ##DATEFIRST
function.
The setting of SET DATEFIRST is set at execute or run time and not at
parse time.
Specifying SET DATEFIRST has no effect on DATEDIFF. DATEDIFF always
uses Sunday as the first day of the week to ensure the function is
deterministic.
Try this one -
SET DATEFIRST 7
DATEFIRST - MSDN
Also try this query after small refactor:
CREATE PROCEDURE [dbo].[sp_workingdays]
(
#startdate DATE
, #enddate DATE
, #createddatetime DATETIME
, #adminid INT
)
AS BEGIN
DECLARE
#start DATE
, #end DATE
, #day VARCHAR(50)
, #timeid INT
, #workingdaytype VARCHAR(20)
SELECT
#start = #startdate
, #end = #enddate
WHILE (#start <= #end) BEGIN
IF NOT EXISTS(
SELECT 1
FROM Generalholyday_details
WHERE #start = [Date]
) BEGIN
SELECT
#day = DATENAME(dw, #start)
, #workingdaytype =
CASE WHEN dt = 1 THEN 'Leave Day'
WHEN dt IN (2,3,4,5,6) THEN 'Full Day'
ELSE 'Half Day'
END
FROM (
SELECT dt = (DATEPART(DW, #start) + ##DATEFIRST) % 7
) t
SELECT #timeid = Time_id
FROM Workingdaytimesetting_details
WHERE Workingday_type = #workingdaytype
INSERT INTO Workingday_details (Working_date, working_day, Time_id)
VALUES (#start, #day, #timeid)
UPDATE Workingday_details
SET createddatetime = #createddatetime
WHERE createddatetime IS NULL
UPDATE Workingday_details
SET adminid = #adminid
WHERE adminid IS NULL
END
SET #start = DATEADD(DAY, 1, #start)
END
END
GO
I am using this query to get time taken.
SELECT DATEDIFF(dd, ActualStartDate, ActualCompletionDate) AS TimeTaken
FROM TableName
Now I want to exclude weekends and only include Mon-Fri as days counted.
Example query below, here are some details on how I solved it.
Using DATEDIFF(WK, ...) will give us the number of weeks between the 2 dates. SQL Server evaluates this as a difference between week numbers rather than based on the number of days. This is perfect, since we can use this to determine how many weekends passed between the dates.
So we can multiple that value by 2 to get the number of weekend days that occurred and subtract that from the DATEDIFF(dd, ...) to get the number of weekdays.
This doesn't behave 100% correctly when the start or end date falls on Sunday, though. So I added in some case logic at the end of the calculation to handle those instances.
You may also want to consider whether or not the DATEDIFF should be fully inclusive. e.g. Is the difference between 9/10 and 9/11 1 day or 2 days? If the latter, you'll want to add 1 to the final product.
declare #d1 datetime, #d2 datetime
select #d1 = '9/9/2011', #d2 = '9/18/2011'
select datediff(dd, #d1, #d2) - (datediff(wk, #d1, #d2) * 2) -
case when datepart(dw, #d1) = 1 then 1 else 0 end +
case when datepart(dw, #d2) = 1 then 1 else 0 end
I found when i used this there was a problem when d1 fell on saturday. Below is what i used to correct this.
declare #d1 datetime, #d2 datetime
select #d1 = '11/19/2011' , #d2 = '11/28/2011'
select datediff(dd, #d1, #d2) +case when datepart(dw, #d1) = 7 then 1 else 0 end - (datediff(wk, #d1, #d2) * 2) -
case when datepart(dw, #d1) = 1 then 1 else 0 end +
case when datepart(dw, #d2) = 1 then 1 else 0 end
BEGIN
DECLARE #totaldays INT;
DECLARE #weekenddays INT;
SET #totaldays = DATEDIFF(DAY, #startDate, #endDate)
SET #weekenddays = ((DATEDIFF(WEEK, #startDate, #endDate) * 2) + -- get the number of weekend days in between
CASE WHEN DATEPART(WEEKDAY, #startDate) = 1 THEN 1 ELSE 0 END + -- if selection was Sunday, won't add to weekends
CASE WHEN DATEPART(WEEKDAY, #endDate) = 6 THEN 1 ELSE 0 END) -- if selection was Saturday, won't add to weekends
Return (#totaldays - #weekenddays)
END
This is on SQL Server 2014
declare #d1 datetime, #d2 datetime
select #d1 = '4/19/2017', #d2 = '5/7/2017'
DECLARE #Counter int = datediff(DAY,#d1 ,#d2 )
DECLARE #C int = 0
DECLARE #SUM int = 0
WHILE #Counter > 0
begin
SET #SUM = #SUM + IIF(DATENAME(dw,
DATEADD(day,#c,#d1))IN('Sunday','Monday','Tuesday','Wednesday','Thursday')
,1,0)
SET #Counter = #Counter - 1
set #c = #c +1
end
select #Sum
If you hate CASE statements as much as I do, and want to be able to use the solution inline in your queries, just get the difference of days and subtract the count of weekend days and you'll have the desired result:
declare #d1 datetime, #d2 datetime, #days int
select #d1 = '2018/10/01', #d2 = '2018/11/01'
SET #days = DateDiff(dd, #d1, #d2) - DateDiff(ww, #d1, #d2)*2
print #days
(The only caveat--or at least point to keep in mind--is that this calculation is not inclusive of the last date, so you might need to add one day to the end date to achieve an inclusive result)
I just want to share the code I created that might help you.
DECLARE #MyCounter int = 0, #TempDate datetime, #EndDate datetime;
SET #TempDate = DATEADD(d,1,'2017-5-27')
SET #EndDate = '2017-6-3'
WHILE #TempDate <= #EndDate
BEGIN
IF DATENAME(DW,#TempDate) = 'Sunday' OR DATENAME(DW,#TempDate) = 'Saturday'
SET #MyCounter = #MyCounter
ELSE IF #TempDate not in ('2017-1-1', '2017-1-16', '2017-2-20', '2017-5-29', '2017-7-4', '2017-9-4', '2017-10-9', '2017-11-11', '2017-12-25')
SET #MyCounter = #MyCounter + 1
SET #TempDate = DATEADD(d,1,#TempDate)
CONTINUE
END
PRINT #MyCounter
PRINT #TempDate
If you do have a holiday table, you can also use that so that you don't have to list all the holidays in the ELSE IF section of the code. You can also create a function for this code and use the function whenever you need it in your query.
I hope this might help too.
Using https://stackoverflow.com/a/1804095 and JeffFisher30's answer above (https://stackoverflow.com/a/14572370/6147425) and my own need to have fractional days, I wrote this:
DateDiff(second,Start_Time,End_Time)/86400.0
-2*DateDiff(week, Start_Time, End_Time)
-Case When (DatePart(weekday, Start_Time)+##DateFirst)%7 = 1 Then 1 Else 0 End
+Case When (DatePart(weekday, End_Time)+##DateFirst)%7 = 1 Then 1 Else 0 End
Use this function to calculate the number of business days excluding Saturday and Sunday. Also it will exclude start date and it will include end date.
-- Select [dbo].[GetBussinessDays] ('02/18/2021', '03/06/2021') -- 11 days
CREATE or ALTER FUNCTION [dbo].[GetBussinessDays] (
#StartDate DATETIME,
#EndDate DATETIME
)
returns INT AS
BEGIN
DECLARE #tempStartDate DATETIME= #StartDate;
DECLARE #tempEndDate DATETIME = #EndDate;
IF(#tempStartDate IS NULL
OR
#tempEndDate IS NULL)
BEGIN
RETURN NULL;
END
--To avoid negative values reverse the date if StartDate is grater than EndDate
IF(#StartDate > #EndDate)
BEGIN
SET #StartDate = #tempEndDate;
SET #EndDate = #tempStartDate;
END
DECLARE #Counter INT = Datediff(day,#StartDate ,#EndDate);
DECLARE #TempCounter INT = 0;
DECLARE #TotalBusinessDays INT = 0;
WHILE #Counter >= 0
BEGIN
IF(#TempCounter > 0 OR #Counter = 1) -- To ignore first day's calculation
Begin
SET #TotalBusinessDays = #TotalBusinessDays + Iif(Datename(dw, Dateadd(day,#TempCounter,#StartDate)) IN('Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday'),1,0)
END
SET #Counter = #Counter - 1
SET #TempCounter = #TempCounter +1
END
RETURN #TotalBusinessDays;
END
Using #Derek Kromm answer (Current Marked Answer)
I have modified so it IS tolerant of any localisations that may be on the target server.
DECLARE #d1 DATETIME, #d2 DATETIME
SELECT #d1 = '10/01/2022', #d2 = '10/28/2022'
SELECT (datediff(dd, #d1, #EndQuery)+1) - (datediff(wk, #d1, dateadd(dd,1,#d2)) * 2)
- CASE WHEN DateName(WEEKDAY, #d1) = 'Sunday' THEN 1 ELSE 0 END -- This includes for start date edge case
+ CASE WHEN DateName(WEEKDAY, #d2) = 'Saturday' THEN 1 ELSE 0 END -- This includes for end date edge case.
This is with the end date being innclusive.
/*
EXAMPLE:
/MONDAY/
SET DATEFIRST 1
SELECT dbo.FUNC_GETDATEDIFFERENCE_WO_WEEKEND('2019-02-01','2019-02-12')
*/
CREATE FUNCTION FUNC_GETDATEDIFFERENCE_WO_WEEKEND
(
#pdtmaLastLoanPayDate DATETIME,
#pdtmaDisbursedDate DATETIME
)
RETURNS BIGINT
BEGIN
DECLARE
#mintDaysDifference BIGINT
SET #mintDaysDifference = 0
WHILE CONVERT(NCHAR(10),#pdtmaLastLoanPayDate,121) <= CONVERT(NCHAR(10),#pdtmaDisbursedDate,121)
BEGIN
IF DATEPART(WEEKDAY,#pdtmaLastLoanPayDate) NOT IN (6,7)
BEGIN
SET #mintDaysDifference = #mintDaysDifference + 1
END
SET #pdtmaLastLoanPayDate = DATEADD(DAY,1,#pdtmaLastLoanPayDate)
END
RETURN ISNULL(#mintDaysDifference,0)
END
In my table I have a Month(tinyint) and a Day(tinyint) field. I would like to have a function that takes this month and day and produces a datetime for the next date(including year) given this month and day.
So if I had Month = 9, Day = 7 it would produce 9/7/2009.
If I had Month 1, Day 1 it would produce 1/1/2010.
something like this would work. It's variation on your method, but it doesn't use the MM/DD/YYYY literal format, and it won't blowup against bad input (for better or for worse).
declare #month tinyint
declare #day tinyint
set #month = 9
set #day = 1
declare #date datetime
-- this could be inlined if desired
set #date = convert(char(4),year(getdate()))+'0101'
set #date = dateadd(month,#month-1,#date)
set #date = dateadd(day,#day-1,#date)
if #date <= getdate()-1
set #date = dateadd(year,1,#date)
select #date
Alternatively, to create a string in YYYYMMDD format:
set #date =
right('0000'+convert(char(4),year(getdate())),4)
+ right('00'+convert(char(2),#month),2)
+ right('00'+convert(char(2),#day),2)
Another method, which avoids literals all together:
declare #month tinyint
declare #day tinyint
set #month = 6
set #day = 24
declare #date datetime
declare #today datetime
-- get todays date, stripping out the hours and minutes
-- and save the value for later
set #date = floor(convert(float,getdate()))
set #today = #date
-- add the appropriate number of months and days
set #date = dateadd(month,#month-month(#date),#date)
set #date = dateadd(day,#day-day(#date),#date)
-- increment year by 1 if necessary
if #date < #today set #date = dateadd(year,1,#date)
select #date
Here is my sql example so far. I don't really like it though...
DECLARE #month tinyint,
#day tinyint,
#date datetime
SET #month = 1
SET #day = 1
-- SET DATE TO DATE WITH CURRENT YEAR
SET #date = CONVERT(datetime, CONVERT(varchar,#month) + '/' + CONVERT(varchar,#day) + '/' + CONVERT(varchar,YEAR(GETDATE())))
-- IF DATE IS BEFORE TODAY, ADD ANOTHER YEAR
IF (DATEDIFF(DAY, GETDATE(), #date) < 0)
BEGIN
SET #date = DATEADD(YEAR, 1, #date)
END
SELECT #date
Here's a solution with PostgreSQL
your_date_calculated = Year * 10000 + Month * 100 + Day
gives you a date like 20090623.
select cast( cast( your_date_calculated as varchar ) as date ) + 1
Here's my version. The core of it is just two lines, using the DATEADD function, and it doesn't require any conversion to/from strings, floats or anything else:
DECLARE #Month TINYINT
DECLARE #Day TINYINT
SET #Month = 9
SET #Day = 7
DECLARE #Result DATETIME
SET #Result =
DATEADD(month, ((YEAR(GETDATE()) - 1900) * 12) + #Month - 1, #Day - 1)
IF (#Result < GETDATE())
SET #Result = DATEADD(year, 1, #Result)
SELECT #Result