MS SQL Server : calculate age with accuracy of hours and minuets - sql

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())

Related

Calculate seconds between two dates including business hours, excluding holidays, start hour 08:30

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

if condition which executes only on weekdays, i.e. mon-fri

I want to have a condition, which executes only on weekdays,
the variable #Executedate is int type and take only the day part of the datetime,if #Executeday is a saturday or sunday, it should be reset to monday.
Here is the piece of code,
Declare #Executeday int
declare #time varchar(200)
set #time = convert(varchar(8), getdate(), 108)
if day(getdate()) < 16
begin
set #Executeday = (select top 1 Description from cts_configuration(nolock) where name like 'ReminderMailerFirstFortNightDay')
end
else
begin
set #Executeday = (select top 1 Description from cts_configuration(nolock) where name like 'ReminderMailerSecondFortNightDay')
end
if (day(getdate())=#Executeday) or (day(getdate())=#Executeday+2) or (day(getdate())=#Executeday+4)
begin
if(#time between '10:00:00' and '11:00:00')
begin
I want the if condition here...(That resets #executeday to Monday, if it is a sat or sun)
DECLARE #ExecuteDay AS int = 7;
DECLARE #Today AS date = GETDATE();
DECLARE #ExecuteDate AS date = DATEADD(day, #ExecuteDay - DATEPART(day, #Today), #Today);
DECLARE #WeekDay AS int = (##DATEFIRST + DATEPART(WEEKDAY, #ExecuteDate)) % 7;
IF #WeekDay IN (0, 1)
SET #ExecuteDate = DATEADD(day, 2 - #WeekDay, #ExecuteDate)
PRINT #ExecuteDate

get DATEDIFF excluding weekends using sql server

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

SQL can you use IF statement in a UDF and how do you do that?

I am trying to create a UDF that does 2 different things depending on the time. Below is my code. I am wondering if you can use the IF statement in a UDF because I am etting 4 errors, incorrect syntax near Begin and Returns and also a Return Statement with return value cannot be used in this context....Any suggestions?
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[udf_TEST]
(
#StartDate DATETIME,
#EndDate DATETIME
)
--DECLARE #StartDate DATETIME
--DECLARE #EndDate DATETIME
--SET #StartDate = '2010-07-06 14:46:37.577'
--SET #EndDate = '2010-07-09 09:04:31.290'
BEGIN
RETURNS VARCHAR(MAX)
(
IF (CONVERT(VARCHAR(13), #StartDate, 114) > CONVERT(VARCHAR(13), #EndDate, 114))
BEGIN
DECLARE #NonWorkTime1 INT
SET #NonWorkTime1 = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins1 INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay1 DECIMAL
SET #MinsInDay1 = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins1 = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays1 INT
SET #MinDays1 = (#AllMins1/#MinsInDay1)
--Subtracts complete day non worked minutes from final minutes between orders
DECLARE #FinalMinutes1 AS DECIMAL
SET #FinalMinutes1 = (#AllMins1 - (#MinDays1 * #NonWorkTime1) - 360 - 420)
RETURN #FinalMinutes1
END
ELSE
BEGIN
--RETURNS VARCHAR(MAX)
--How many minutes a day are not worked for trips
DECLARE #NonWorkTime INT
SET #NonWorkTime = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay DECIMAL
SET #MinsInDay = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays INT
SET #MinDays = (#AllMins/#MinsInDay)
--Subtracts complete day non worked minutes from final minutes between orders
DECLARE #FinalMinutes AS DECIMAL
SET #FinalMinutes = (#AllMins - (#MinDays * #NonWorkTime))
RETURN #FinalMinutes
END
)
END
You definitely can use IF's in a UDF.
There are a few syntax errors, but main issue is that you need to move the #FinalMinutes to outside the IF as it needs to be returned from the main scope.
Try this:
CREATE FUNCTION [dbo].[udf_TEST]
(
#StartDate DATETIME,
#EndDate DATETIME
)
RETURNS VARCHAR(MAX)
--DECLARE #StartDate DATETIME
--DECLARE #EndDate DATETIME
--SET #StartDate = '2010-07-06 14:46:37.577'
--SET #EndDate = '2010-07-09 09:04:31.290'
BEGIN
DECLARE #FinalMinutes AS DECIMAL
IF (CONVERT(VARCHAR(13), #StartDate, 114) > CONVERT(VARCHAR(13), #EndDate, 114))
BEGIN
DECLARE #NonWorkTime1 INT
SET #NonWorkTime1 = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins1 INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay1 DECIMAL
SET #MinsInDay1 = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins1 = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays1 INT
SET #MinDays1 = (#AllMins1/#MinsInDay1)
--Subtracts complete day non worked minutes from final minutes between orders
SET #FinalMinutes = (#AllMins1 - (#MinDays1 * #NonWorkTime1) - 360 - 420)
END
ELSE
BEGIN
--RETURNS VARCHAR(MAX)
--How many minutes a day are not worked for trips
DECLARE #NonWorkTime INT
SET #NonWorkTime = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay DECIMAL
SET #MinsInDay = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays INT
SET #MinDays = (#AllMins/#MinsInDay)
--Subtracts complete day non worked minutes from final minutes between orders
SET #FinalMinutes = (#AllMins - (#MinDays * #NonWorkTime))
END
RETURN #FinalMinutes
END
Corrections Made
Moved RETURNS before BEGIN
Moved declaration of #FinalMinutes1 outside of the IF block
Removed unneeded parens
Made RETURN last statement of function
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[udf_TEST]
(
#StartDate DATETIME,
#EndDate DATETIME
)
--DECLARE #StartDate DATETIME
--DECLARE #EndDate DATETIME
--SET #StartDate = '2010-07-06 14:46:37.577'
--SET #EndDate = '2010-07-09 09:04:31.290'
RETURNS VARCHAR(MAX)
BEGIN
DECLARE #FinalMinutes1 AS DECIMAL
IF (CONVERT(VARCHAR(13), #StartDate, 114) > CONVERT(VARCHAR(13), #EndDate, 114))
BEGIN
DECLARE #NonWorkTime1 INT
SET #NonWorkTime1 = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins1 INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay1 DECIMAL
SET #MinsInDay1 = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins1 = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays1 INT
SET #MinDays1 = (#AllMins1/#MinsInDay1)
--Subtracts complete day non worked minutes from final minutes between orders
SET #FinalMinutes1 = (#AllMins1 - (#MinDays1 * #NonWorkTime1) - 360 - 420)
RETURN #FinalMinutes1
END
ELSE
BEGIN
--RETURNS VARCHAR(MAX)
--How many minutes a day are not worked for trips
DECLARE #NonWorkTime INT
SET #NonWorkTime = 780
--How many minutes are between order start and end time including non working time
DECLARE #AllMins INT
--Declares how many minutes are in a day and makes it float to get remainder minutes when divided
DECLARE #MinsInDay DECIMAL
SET #MinsInDay = 1440.0
--Finds how many minutes are between start and end time excluding weekends and assignes to variable
SET #AllMins = ((DATEDIFF(mi, #StartDate, #EndDate))
-(((DATEDIFF(wk, #StartDate, #EndDate) * 2) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END) * 24) * 60)
-(((CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END) * 24) * 60))
--Calculates how many days have elapsed in the minutes that the order has taken
DECLARE #MinDays INT
SET #MinDays = (#AllMins/#MinsInDay)
--Subtracts complete day non worked minutes from final minutes between orders
DECLARE #FinalMinutes AS DECIMAL
SET #FinalMinutes = (#AllMins - (#MinDays * #NonWorkTime))
END
RETURN #FinalMinutes
END

How to calculate the local datetime from a utc datetime in tsql (sql 2005)?

i want to loop over a period of time in tsql, and print the utc datetimes and our local variant.
We live in UTC +1, so i could easily add 1 hour, but in the summertime we live in UTC +2.
In C# i can create a datetime and use a method to ask for the UTC variant and vice versa.
Till now i have this:
declare #counter int
declare #localdate datetime
declare #utcdate datetime
set #counter = 0
while #counter < 100
begin
set #counter = #counter + 1
print 'The counter is ' + cast(#counter as char)
set #utcdate = DATEADD(day,#counter,GETUTCDATE())
--set #localdate = ????
print #localdate
print #utcdate
end
I've been waiting for 5 years for a more elegant solution but since one has not emerged, I'll post what I've been using thus far...
CREATE FUNCTION [dbo].[UDTToLocalTime](#UDT AS DATETIME)
RETURNS DATETIME
AS
BEGIN
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE #Offset AS SMALLINT
SET #Offset = -5
--====================================================
--Figure out the Offset Datetime
--====================================================
DECLARE #LocalDate AS DATETIME
SET #LocalDate = DATEADD(hh, #Offset, #UDT)
--====================================================
--Figure out the DST Offset for the UDT Datetime
--====================================================
DECLARE #DaylightSavingOffset AS SMALLINT
DECLARE #Year as SMALLINT
DECLARE #DSTStartDate AS DATETIME
DECLARE #DSTEndDate AS DATETIME
--Get Year
SET #Year = YEAR(#LocalDate)
--Get First Possible DST StartDay
IF (#Year > 2006) SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate
WHILE (DATENAME(dw, #DSTStartDate) <> 'sunday') SET #DSTStartDate = DATEADD(day, 1,#DSTStartDate)
--Get First Possible DST EndDate
IF (#Year > 2006) SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate
WHILE (DATENAME(dw, #DSTEndDate) <> 'sunday') SET #DSTEndDate = DATEADD(day,1,#DSTEndDate)
--Get DaylightSavingOffset
SET #DaylightSavingOffset = CASE WHEN #LocalDate BETWEEN #DSTStartDate AND #DSTEndDate THEN 1 ELSE 0 END
--====================================================
--Finally add the DST Offset
--====================================================
RETURN DATEADD(hh, #DaylightSavingOffset, #LocalDate)
END
GO
Notes:
This is for North American servers that observer Daylight Saving Time. Please change the variable #Offest to the Timezone offset of the server running the SQL function (While NOT Observing the Daylight Savings time)...
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE #Offset AS SMALLINT
SET #Offset = -5
As the DST rules change update them here...
--Get First Possible DST StartDay
IF (#Year > 2006) SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE SET #DSTStartDate = CAST(#Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate
WHILE (DATENAME(dw, #DSTStartDate) <> 'sunday') SET #DSTStartDate = DATEADD(day, 1,#DSTStartDate)
--Get First Possible DST EndDate
IF (#Year > 2006) SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE SET #DSTEndDate = CAST(#Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate
WHILE (DATENAME(dw, #DSTEndDate) <> 'sunday') SET #DSTEndDate = DATEADD(day,1,#DSTEndDate)
Cheers,
Assuming you are using SQL 2005 upwards, you can develop a SQL CLR function to take a UTC date and convert to the local date.
This link is an MSDN How-To explaining how you can create a scalar UDF in C#.
Create a SQL function along the lines of
[SqlFunction()]
public static SqlDateTime ConvertUtcToLocal(SqlDateTime utcDate)
{
// over to you to convert SqlDateTime to DateTime, specify Kind
// as UTC, convert to local time, and convert back to SqlDateTime
}
Your sample above would then become
set #localdate = dbo.ConvertUtcToLocal(#utcdate)
SQL CLR has its overheads in terms of deployment, but I feel cases like this are where it fits in best.
This solution seems too obvious.
If you can get UTC Date with GETUTCDATE() and you can get your local date with GETDATE() you have an offset that you can apply for any datetime
SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, GETUTCDATE())
this should return the local time you executed the query,
SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, N'1/14/2011 7:00:00' )
this will return 2011-01-14 02:00:00.000 because i'm in UTC +5
Unless I'm missing something?
You can use my SQL Server Time Zone Support project to convert between IANA standard time zones, as listed here.
Example:
SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'America/Los_Angeles')
While the question's title mentions SQL Server 2005, the question is tagged with SQL Server in general.
For SQL Server 2016 and later, you can use:
SELECT yourUtcDateTime AT TIME ZONE 'Mountain Standard Time'
A list of time zones is available with SELECT * FROM sys.time_zone_info
Here is a function (again US ONLY) but it is a bit more flexible. It will convert a UTC date to the server local time.
It starts by adjusting the appointment date based on the current offset and then adjusts based on the difference of the current offset and the offset of the date of the appointment.
CREATE FUNCTION [dbo].[fnGetServerTimeFromUTC]
(
#AppointmentDate AS DATETIME,
#DateTimeOffset DATETIMEOFFSET
)
RETURNS DATETIME
AS
BEGIN
--DECLARE #AppointmentDate DATETIME;
--SET #AppointmentDate = '2016-12-01 12:00:00'; SELECT #AppointmentDate;
--Get DateTimeOffset from Server
--DECLARE #DateTimeOffset; SET #DateTimeOffset = SYSDATETIMEOFFSET();
DECLARE #DateTimeOffsetStr NVARCHAR(34) = #DateTimeOffset;
--Set a standard DatePart value for Sunday (server configuration agnostic)
DECLARE #dp_Sunday INT = 7 - ##DATEFIRST + 1;
--2006 DST Start First Sunday in April (earliest is 04-01) Ends Last Sunday in October (earliest is 10-25)
--2007 DST Start Second Sunday March (earliest is 03-08) Ends First Sunday Nov (earliest is 11-01)
DECLARE #Start2006 NVARCHAR(6) = '04-01-';
DECLARE #End2006 NVARCHAR(6) = '10-25-';
DECLARE #Start2007 NVARCHAR(6) = '03-08-';
DECLARE #End2007 NVARCHAR(6) = '11-01-';
DECLARE #ServerDST SMALLINT = 0;
DECLARE #ApptDST SMALLINT = 0;
DECLARE #Start DATETIME;
DECLARE #End DATETIME;
DECLARE #CurrentMinuteOffset INT;
DECLARE #str_Year NVARCHAR(4) = LEFT(#DateTimeOffsetStr,4);
DECLARE #Year INT = CONVERT(INT, #str_Year);
SET #CurrentMinuteOffset = CONVERT(INT, SUBSTRING(#DateTimeOffsetStr,29,3)) * 60 + CONVERT(INT, SUBSTRING(#DateTimeOffsetStr,33,2)); --Hours + Minutes
--Determine DST Range for Server Offset
SET #Start = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #Start2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #Start2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #Start) BEGIN
SET #Start = DATEADD(DAY, 1, #Start)
END;
SET #End = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #End2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #End2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #End) BEGIN
SET #End = DATEADD(DAY, 1, #End)
END;
--Determine Current Offset based on Year
IF #DateTimeOffset >= #Start AND #DateTimeOffset < #End SET #ServerDST = 1;
--Determine DST status of Appointment Date
SET #Year = YEAR(#AppointmentDate);
SET #Start = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #Start2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #Start2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #Start) BEGIN
SET #Start = DATEADD(DAY, 1, #Start)
END;
SET #End = CASE
WHEN #Year <= 2006 THEN CONVERT(DATETIME, #End2006 + #str_Year + ' 02:00:00')
ELSE CONVERT(DATETIME, #End2007 + #str_Year + ' 02:00:00')
END;
WHILE #dp_Sunday <> DATEPART(WEEKDAY, #End) BEGIN
SET #End = DATEADD(DAY, 1, #End)
END;
--Determine Appointment Offset based on Year
IF #AppointmentDate >= #Start AND #AppointmentDate < #End SET #ApptDST = 1;
SET #AppointmentDate = DATEADD(MINUTE, #CurrentMinuteOffset + 60 * (#ApptDST - #ServerDST), #AppointmentDate)
RETURN #AppointmentDate
END
GO
For those stuck in SQL Server 2005 and don't want or can't use a udf - and particularly does outside of the USA - I've taken #Bobman's approach and generalized it. The following will work in the USA, Europe, New Zealand and Australia, with the caveat that not all Australian states observe DST, even states that are in the same "base" timezone. It's also easy to add DST-rules that aren't yet supported, just add a line to the #calculation values.
-- =============================================
-- Author: Herman Scheele
-- Create date: 20-08-2016
-- Description: Convert UTC datetime to local datetime
-- based on server time-distance from utc.
-- =============================================
create function dbo.UTCToLocalDatetime(#UTCDatetime datetime)
returns datetime as begin
declare #LocalDatetime datetime, #DSTstart datetime, #DSTend datetime
declare #calculation table (
frm smallint,
til smallint,
since smallint,
firstPossibleStart datetime,-- Put both of these with local non-DST time!
firstPossibleEnd datetime -- (In Europe we turn back the clock from 3 AM to 2 AM, which means it happens 2 AM non-DST time)
)
insert into #calculation
values
(-9, -2, 1967, '1900-04-24 02:00', '1900-10-25 01:00'), -- USA first DST implementation
(-9, -2, 1987, '1900-04-01 02:00', '1900-10-25 01:00'), -- USA first DST extension
(-9, -2, 2007, '1900-03-08 02:00', '1900-11-01 01:00'), -- USA second DST extension
(-1, 3, 1900, '1900-03-25 02:00', '1900-10-25 02:00'), -- Europe
(9.5,11, 1971, '1900-10-01 02:00', '1900-04-01 02:00'), -- Australia (not all Aus states in this time-zone have DST though)
(12, 13, 1974, '1900-09-24 02:00', '1900-04-01 02:00') -- New Zealand
select top 1 -- Determine if it is DST /right here, right now/ (regardless of input datetime)
#DSTstart = dateadd(year, datepart(year, getdate())-1900, firstPossibleStart), -- Grab first possible Start and End of DST period
#DSTend = dateadd(year, datepart(year, getdate())-1900, firstPossibleEnd),
#DSTstart = dateadd(day, 6 - (datepart(dw, #DSTstart) + ##datefirst - 2) % 7, #DSTstart),-- Shift Start and End of DST to first sunday
#DSTend = dateadd(day, 6 - (datepart(dw, #DSTend) + ##datefirst - 2) % 7, #DSTend),
#LocalDatetime = dateadd(hour, datediff(hour, getutcdate(), getdate()), #UTCDatetime), -- Add hours to current input datetime (including possible DST hour)
#LocalDatetime = case
when frm < til and getdate() >= #DSTstart and getdate() < #DSTend -- If it is currently DST then we just erroneously added an hour above,
or frm > til and (getdate() >= #DSTstart or getdate() < #DSTend) -- substract 1 hour to get input datetime in current non-DST timezone,
then dateadd(hour, -1, #LocalDatetime) -- regardless of whether it is DST on the date of the input datetime
else #LocalDatetime
end
from #calculation
where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
and datepart(year, getdate()) >= since
order by since desc
select top 1 -- Determine if it was/will be DST on the date of the input datetime in a similar fashion
#DSTstart = dateadd(year, datepart(year, #LocalDatetime)-1900, firstPossibleStart),
#DSTend = dateadd(year, datepart(year, #LocalDatetime)-1900, firstPossibleEnd),
#DSTstart = dateadd(day, 6 - (datepart(dw, #DSTstart) + ##datefirst - 2) % 7, #DSTstart),
#DSTend = dateadd(day, 6 - (datepart(dw, #DSTend) + ##datefirst - 2) % 7, #DSTend),
#LocalDatetime = case
when frm < til and #LocalDatetime >= #DSTstart and #LocalDatetime < #DSTend -- If it would be DST on the date of the input datetime,
or frm > til and (#LocalDatetime >= #DSTstart or #LocalDatetime < #DSTend) -- add this hour to the input datetime.
then dateadd(hour, 1, #LocalDatetime)
else #LocalDatetime
end
from #calculation
where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
and datepart(year, #LocalDatetime) >= since
order by since desc
return #LocalDatetime
end
This function looks at the difference between local and utc time at the moment it runs to determine which DST-rules to apply. It then knows whether doing datediff(hour, getutcdate(), getdate()) includes a DST hour or not and subtracts it if it does. Then it determines whether it was or will be DST at the date of the input UTC datetime and if so adds the DST hour back.
This comes with one quirk, which is that during the last hour of DST and the first hour of non-DST, the function has no way of determining which it is and assumes the latter. So regardless of input-datetime, if this codes runs during the last hour of DST it will give the wrong outcome. Which means this works 99.9886% of the time.
GETUTCDATE() just gives you the current time in UTC, any DATEADD() you do to this value will not include any daylight savings time shifts.
Your best bet is build your own UTC conversion table or just use something like this:
http://www.codeproject.com/KB/database/ConvertUTCToLocal.aspx
Bobman's answer is close, but has a couple bugs:
1) You must compare local DAYLIGHT time (instead of local STANDARD time) to the Daylight Saving End DateTime.
2) SQL BETWEEN is Inclusive so you should be comparing using ">= and <" instead of BETWEEN.
Here is a working modified version along with some test cases:
(Again, this only works for United States)
-- Test cases:
-- select dbo.fn_utc_to_est_date('2016-03-13 06:59:00.000') -- -> 2016-03-13 01:59:00.000 (Eastern Standard Time)
-- select dbo.fn_utc_to_est_date('2016-03-13 07:00:00.000') -- -> 2016-03-13 03:00:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 05:59:00.000') -- -> 2016-11-06 01:59:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 06:00:00.000') -- -> 2016-11-06 01:00:00.000 (Eastern Standard Time)
CREATE FUNCTION [dbo].[fn_utc_to_est_date]
(
#utc datetime
)
RETURNS datetime
as
begin
-- set offset in standard time (WITHOUT daylight saving time)
declare #offset smallint
set #offset = -5 --EST
declare #localStandardTime datetime
SET #localStandardTime = dateadd(hh, #offset, #utc)
-- DST in USA starts on the second sunday of march and ends on the first sunday of november.
-- DST was extended beginning in 2007:
-- https://en.wikipedia.org/wiki/Daylight_saving_time_in_the_United_States#Second_extension_.282005.29
-- If laws/rules change, obviously the below code needs to be updated.
declare #dstStartDate datetime,
#dstEndDate datetime,
#year int
set #year = datepart(year, #localStandardTime)
-- get the first possible DST start day
if (#year > 2006) set #dstStartDate = cast(#year as char(4)) + '-03-08 02:00:00'
else set #dstStartDate = cast(#year as char(4)) + '-04-01 02:00:00'
while ((datepart(weekday,#dstStartDate) != 1)) begin --while not sunday
set #dstStartDate = dateadd(day, 1, #dstStartDate)
end
-- get the first possible DST end day
if (#year > 2006) set #dstEndDate = cast(#year as char(4)) + '-11-01 02:00:00'
else set #dstEndDate = cast(#year as char(4)) + '-10-25 02:00:00'
while ((datepart(weekday,#dstEndDate) != 1)) begin --while not sunday
set #dstEndDate = dateadd(day, 1, #dstEndDate)
end
declare #localTimeFinal datetime,
#localTimeCompare datetime
-- if local date is same day as #dstEndDate day,
-- we must compare the local DAYLIGHT time to the #dstEndDate (otherwise we compare using local STANDARD time).
-- See: http://www.timeanddate.com/time/change/usa?year=2016
if (datepart(month,#localStandardTime) = datepart(month,#dstEndDate)
and datepart(day,#localStandardTime) = datepart(day,#dstEndDate)) begin
set #localTimeCompare = dateadd(hour, 1, #localStandardTime)
end
else begin
set #localTimeCompare = #localStandardTime
end
set #localTimeFinal = #localStandardTime
-- check for DST
if (#localTimeCompare >= #dstStartDate and #localTimeCompare < #dstEndDate) begin
set #localTimeFinal = dateadd(hour, 1, #localTimeFinal)
end
return #localTimeFinal
end
I recently had to do the same thing. The trick is figuring out the offset from UTC, but it's not a hard trick. You simply use DateDiff to get the difference in hours between local and UTC. I wrote a function to take care of this.
Create Function ConvertUtcDateTimeToLocal(#utcDateTime DateTime)
Returns DateTime
Begin
Declare #utcNow DateTime
Declare #localNow DateTime
Declare #timeOffSet Int
-- Figure out the time difference between UTC and Local time
Set #utcNow = GetUtcDate()
Set #localNow = GetDate()
Set #timeOffSet = DateDiff(hh, #utcNow, #localNow)
DECLARE #localTime datetime
Set #localTime = DateAdd(hh, #timeOffset, #utcDateTime)
-- Check Results
return #localTime
End
GO
This does have on crucial short coming: If a time zone uses a fractional offset, such as Nepal which is GMT+5:45, this will fail because this only deals with whole hours. However, it should fit your needs just fine.