I have a function which converts UTC time into local time based on a certain office location.
The two parameters for the function are the UTC as datetime2 data type and Office as int data type.
SELECT [fn].[ConvertFromUTC]('2021-03-14 07:00:00', 5740)
Result: 2021-03-14 03:00:00
Here is the a table that I am wanting to convert all the UTC times to their local times based on the Support Site location (office).
What would be the best way to go about doing this? I tried using something like this but am not sure how to iterate through each support site and UTC time. Suggestions? Would a Cursor be ideal in this scenario?
DECLARE #meh nvarchar(50)
DECLARE #x datetime2
DECLARE #y int
Set #x = '2021-03-14 07:00:00'
Set #y = 20608
EXEC #meh = fn.ConvertFromUTC
#DateTime = #x,
#Office = #y
SELECT #meh
Here is the code for the function.
ALTER Function [fn].[ConvertFromUTC]
/*This function converts a DateTime from UTC to local time at each office.*/
(
#DateTime DATETIME2(0),
#Office int
)
RETURNS DATETIME2(0)
AS
BEGIN
DECLARE #TimeZone nvarchar(50)
DECLARE #Result DATETIME2(0)
IF #Office IN ('20608','5740')
BEGIN
SET #TimeZone = 'US Eastern Standard Time'
SET #Result = #DateTime at time zone 'UTC' at time zone #TimeZone
END
ELSE IF #Office = '597'
BEGIN
SET #TimeZone = 'W. Europe Standard Time'
SET #Result = #DateTime at time zone 'UTC' at time zone #TimeZone
SET #Result = DATEADD(HOUR,-1,#Result) /* 'AT TIME ZONE' functionality for Europe uses the wrong offset. This corrects it.*/
END
ELSE IF #Office = '6179' /*Not using 'AT TIME ZONE' functionality because it doesn't recognize that Australia observes Daylight Savings Time*/
BEGIN
/*DateTime is the end and beginning of Sydney daylight savings time in UTC.
Ends first Sunday in April at 2:00:00 and begins first Sunday in October at 3:00:00
In UTC, Ends first Saturday in April at 16:00:00 and begins first Saturday in October 16:00:00*/
IF #DateTime >= DATEADD(dd, (5-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(#DateTime)-1900) * 12 + 3,0))%7)),DATEADD(mm,(YEAR(#DateTime)-1900) * 12 + 3,0))+'16:00:00'
AND #DateTime < DATEADD(dd,0 + (5-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(#DateTime)-1900) * 12 + 9,0))%7)),DATEADD(mm,(YEAR(#DateTime)-1900) * 12 + 9,0))+'16:00:00'
BEGIN
SET #Result = DATEADD(hour,10,#DateTime)
END
ELSE
BEGIN
SET #Result = DATEADD(hour,11,#DateTime)
END
END
RETURN #Result
END
Firstly, what you have currently is termed a scalar User Defined Function (UDF).
You can use it quite simply like this:
SELECT fn.ConvertFromUTC(t.YourDate, 5740)
FROM YourTable t;
Scalar UDFs are slow for various reasons and should be avoided, and although most are helped by SQL Server 2019's UDF inlining, it is normally better to rewrite this as an inline Table Valued Function. This returns a rowset, and is a bit like a parameterized view.
To make it inline, it must be a single RETURN (SELECT statement
CREATE OR ALTER Function [fn].[ConvertFromUTC]
/*This function converts a DateTime from UTC to local time at each office.*/
(
#DateTime DATETIME2(0),
#Office int
)
RETURNS TABLE
AS RETURN
(
SELECT Result = CASE
WHEN #Office IN ('20608', '5740')
THEN #DateTime AT TIME ZONE 'UTC' AT TIME ZONE 'US Eastern Standard Time'
WHEN #Office = '597'
THEN DATEADD(HOUR, -1, #DateTime AT TIME ZONE 'UTC' AT TIME ZONE 'W. Europe Standard Time')
WHEN #Office = '6179'
THEN
/*DateTime is the end and beginning of Sydney daylight savings time in UTC.
Ends first Sunday in April at 2:00:00 and begins first Sunday in October at 3:00:00
In UTC, Ends first Saturday in April at 16:00:00 and begins first Saturday in October 16:00:00*/
CASE WHEN #DateTime >= DATEADD(dd, (5-(DATEDIFF(dd, 0, DATEADD(mm, (YEAR(#DateTime) - 1900) * 12 + 3, 0)) %7)), DATEADD(mm,(YEAR(#DateTime) - 1900) * 12 + 3, 0)) + '16:00:00'
AND #DateTime < DATEADD(dd, 0 + (5 - (DATEDIFF(dd, 0, DATEADD(mm, (YEAR(#DateTime)-1900) * 12 + 9, 0)) % 7)), DATEADD(mm,(YEAR(#DateTime) - 1900) * 12 + 9, 0)) + '16:00:00'
THEN DATEADD(hour,10,#DateTime)
ELSE DATEADD(hour,11,#DateTime)
END
END
);
GO
You can use it like this:
SELECT utc.Result
FROM YourTable t
CROSS APPLY fn.ConvertFromUTC(t.YourDate, 5740) utc;
-- Because it's only one value you can also do this
SELECT
(SELECT utc.Result FROM fn.ConvertFromUTC(t.YourDate, 5740))
FROM YourTable t;
I must say, I take issue with the original writer of this function, who clearly knows about AT TIME ZONE, but thinks it doesn't work properly.
SET #TimeZone = 'W. Europe Standard Time'
SET #Result = #DateTime at time zone 'UTC' at time zone #TimeZone
SET #Result = DATEADD(HOUR,-1,#Result) /* 'AT TIME ZONE' functionality for Europe uses the wrong offset. This corrects it.*/
Europe is not monolithic, presumably the correct time zone should have been 'GMT Standard Time'.
ELSE IF #Office = '6179' /*Not using 'AT TIME ZONE' functionality because it doesn't recognize that Australia observes Daylight Savings Time*/
BEGIN
/*DateTime is the end and beginning of Sydney daylight savings time in UTC.
Again, Australia is not one time zone, and I suspect that E. Australia Standard Time was used instead of 'AUS Eastern Standard Time'.
You can see all the available time zones on the server with select * from sys.time_zone_info, you can also add more time zones via Windows.
Another thing is that this function is supposed to converts a DateTime from UTC to local time, but using AT TIME ZONE twice is used only when converting from one time zone to another, it should only be done once if the time is already in 'UTC'.
One further point: I suggest you actually store the correct time zone within the table you are querying, then you can pass through the time zone instead of #Office and avoid a bunch of CASE expressions.
Related
We have data stored in our timezone ('AUS Eastern Standard Time') into SQL Server database, and we have to do calculations. Is it possible to convert both dates to UTC for calculations, so that it calculates the right difference?
DECLARE #startdate DateTime = '2022-10-01 23:13:00.000'; --UTC 2022-10-01 13:13:00.00
DECLARE #enddate DateTime = '2022-10-02 12:08:00.000' --UTC 2022-10-02 01:08:00.00
select CAST((#enddate - #startdate) as time(0)) 'Difference'
Time difference: 12:55:00
Actual Time Difference: 11:55:00
Datetime values will be problematic without an offset as #Larnu suggest in comments. This will occur when clocks are set back and result in overlapping times before and after the time change. You'll need rules to determine the offset in that case.
In your example, where clocks are set forward, there should be no overlap or ambiguity. This allows one to use AT DATE TIME AUS Eastern Standard Time') to convert the datetime values to datetimeoffset for the duration calculation. If an invalid datetime value is stored (within the gap when clocks should have been change forward), AT DATE TIME will return the offset after the clock change.
DECLARE #startdate DateTime = '2022-10-01 23:13:00.000'; --UTC 2022-10-01 13:13:00.00
DECLARE #enddate DateTime = '2022-10-02 12:08:00.000'; --UTC 2022-10-02 01:08:00.00
SELECT DATEADD(second
, DATEDIFF(second, #startdate AT TIME ZONE 'AUS Eastern Standard Time'
, #enddate AT TIME ZONE 'AUS Eastern Standard Time')
, CAST('00:00:00' AS time(0)));
So you'll have one hour of the year you'll need to develop business rules for. AT DATE TIME will return the offset before the change during the ambiguous interval when clocks are set backwards.
DATETIME is a not recommended because it's a datatype with plenty of erronous features and it is not accurate.
Use DATETIME2 instaed (MS recommendation)
Then SET the TIMEZONE with the operator AT TIME ZONE :
Use ALSO DATETDIFF to get a difference because a minus sign must only apply to numbers not for date/time calculus...
Finally :
DECLARE #startdate DateTime2 = '2022-10-01 23:13:00.000';
DECLARE #enddate DateTime2 = '2022-10-02 12:08:00.000';
DECLARE #difsecond INT = DATEDIFF(s, #startdate AT TIME ZONE 'AUS Eastern Standard Time', #enddate AT TIME ZONE 'AUS Eastern Standard Time')
SELECT CAST(DATEADD(s, #difsecond, CAST('2000-01-01' AS DATETIME2(0))) AS TIME)
Using SQL Server 2016. This is in a SP for a report. When given date range of 02/22/2017, report is including items for 02/21/2017. Dates are stored in the db as DateTimeOffset.
In this query I am amtrying to return on the 22nd, but I am also getting 21st.
#start and #end represent the date range entered by the user. #storeddate is the date from the db used to filter the report. My plan is to convert the offset date then pull the 'date' part out to filter on, but it isnt working.
declare #start date = '2017-02-22';
declare #end date = '2017-02-22';
declare #storeddate datetimeoffset = '2017-02-22 00:00:19.0000000 +00:00';
;with dates as
(
select #storeddate as 'raw'
, #storeddate AT TIME ZONE 'Pacific Standard Time' as offset
, CONVERT(datetime, #storeddate) AT TIME ZONE 'Pacific Standard Time' as dt
, CONVERT(datetime, #storeddate AT TIME ZONE 'Pacific Standard Time') as d
, CAST(CONVERT(datetime, #storeddate) AT TIME ZONE 'Pacific Standard Time' as date) as 'casted'
, #start as 'start'
, #end as 'end'
)
select * from dates
WHERE (
CAST(CONVERT(datetime, #storeddate) AT TIME ZONE 'Pacific Standard Time' as date) >= #start
AND CAST(CONVERT(datetime, #storeddate) AT TIME ZONE 'Pacific Standard Time' as date) < DATEADD(day, 1, #end) )
Edit to add note:
Its kind of an odd scenario. This is an intranet web app used only in oregon. The web developer used some javascript datepicker library that changed all his dates to UTC and he couldn't figure out how to change them back so he just stored them in the db as datetimeoffset. So now I have to change all the reports to show the correct dates. The 'At Time Zone' fixed the displaying of the dates in the reports, but it is not working in the where clauses for the date range filters.
Does this have to do with your timezones? You're putting in #storeddate without an offset and then it looks like you're evaluating for Pacific time (-8:00, isn't it?). It seems like that would shift the returned data.
Edit: Try using dateadd to modify the date:
declare #start date = '2017-02-22';
declare #end date = '2017-02-22';
declare #storeddate table (rawdate datetimeoffset)
insert into #storeddate
values( '2017-02-22 00:00:19.0000000 +00:00')
,('2017-02-21 00:00:19.0000000 +00:00')
,('2017-02-22 00:18:19.0000000 +00:00')
,('2017-02-23 00:18:19.0000000 +00:00')
;with dates as
(
select rawdate as 'raw'
, cast(dateadd(hh,-8,rawdate) as date) as offset
, #start as 'start'
, #end as 'end'
from #storeddate
)
select * from dates
WHERE (
offset >= #start
and offset < DATEADD(day, 1, #end))
I had the closing bracket on the CONVERT in the wrong place, so I was converting the #storeddate to date instead of the #storedate AT TIME ZONE. So the answer was this:
WHERE CAST(CONVERT(datetime, #storeddate AT TIME ZONE 'Pacific Standard Time') as date)
instead of
WHERE CAST(CONVERT(datetime, #storeddate) AT TIME ZONE 'Pacific Standard Time' as date)
etc...
In my table, I have a column that contains date in millisecond like this:
table a
dateinmili
1440301846096 //first six month date
1443589721039 //second six month date
I use that for my Android device and it works fine. When I want to use this time in a PROCEDURE in SQL Server and convert this time to human time (understandable for human) and date I have a problem.
I'm in Iran which uses UTC time in first six Persian date month 4.30 and 3.30 in second six month.
For convert date in PROCEDURE I use this code:
CONVERT(nVARCHAR(10),DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), DATEADD(ss,dateinmili/1000,'1970-01-01')),8) as date
DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), DATEADD(ss,dateinmili/1000,'1970-01-01')) as time
and here is my problem:
When I convert date in second six month and date registered in first six month of year, I get 1 hour difference between real time and converted time. I know that is because
DATEDIFF(mi, GETUTCDATE(), GETDATE())
method which return different between UTC time and local time when ever its called (in my example return 3:30 not 4:30 ) but I don't know how can I fix that?
I can add column which contain current UTC time but I am looking for another way.
update
I see this question and it's not my problem convert long to date.
My problem is in my country UTC time is not constant in whole year and change between 3.30 and 4.30, for example I have date registered in first six month (Persian six month) like 1440271800000 and convert it now which we are in second six month (Persian six month) and use this code for convert.
declare #unixTS bigint
set #unixTS = 1440271800000
select dateadd(ms, #unixTS%(3600*24*1000),
dateadd(day, #unixTS/(3600*24*1000), '1970-01-01 03:30:00.0')
)
I get this
2015-08-22 23:00:00.000
but it's not right date; the right date is:
2015-08-23 00:00:00.000
because when time registered UTC was 4.30 and not 3.30 but know when I convert
it UTC is 3.30.
I wish if there was a method in SQL which return past UTC time different; I mean put a date to that and return that time different between local time and gmt time my problem solved.
I hope you understand my problem.
In the US we have Daylight Savings Time in the summer, in most areas that means that we are also not fixed offset from UTC. Older versions of MS Dynamics CRM used to save everything in UTC, so when we wanted to export data in local time, we had a similar exercise. I created a set of SQL functions that would take the standard GMT offset and the datetime to I wanted to convert and figure out whether to apply the standard or DST offset and return the local datetime. If your offset follows a set of rules, then you can modify this:
CREATE function [dbo].[DC_GMTtoLocal]
(#OrigGMT datetime,
#StandardOffset int)
RETURNS datetime
AS
BEGIN
DECLARE #RevDate datetime
set #RevDate = CASE dbo.DC_DaylightSavingTime_IsInEffect(#OrigGMT)
WHEN 1 THEN DATEADD(hour, - #StandardOffset + 1, #OrigGMT) -- in DST
ELSE DATEADD(hour, - #StandardOffset, #OrigGMT) -- Not In DST
END
return #RevDate
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE function [dbo].[DC_DaylightSavingTime_IsInEffect]
(#DtTime datetime)
RETURNS tinyint
AS
BEGIN
DECLARE #DLSStart datetime
, #DLSEnd datetime
, #DLSActive tinyint
SET #DLSActive = 0
If DATEADD(YEAR,3,GETDATE()) > #DtTime
BEGIN
SET #DLSStart =(SELECT dbo.DC_GetDaylightSavingsTimeStart(CONVERT(varchar,DATEPART(YEAR,#DtTime))))
SET #DLSEnd =(SELECT dbo.DC_GetDaylightSavingsTimeEnd(CONVERT(varchar,DATEPART(YEAR,#DtTime))))
IF #DtTime BETWEEN #DLSStart AND #DLSEnd
BEGIN
SET #DLSActive = 1
END
--SET #DLSActive = 0
END
RETURN #DLSActive
END
GO
CREATE function [dbo].[DC_GetDaylightSavingsTimeStart]
(#Year varchar(4))
RETURNS smalldatetime
as
--Start date: We evaluate the day of the week corresponding to the first day of the month and find the second Sunday of March using a Case statement
begin
declare #DTSStartWeek smalldatetime, #DTSEndWeek smalldatetime
set #DTSStartWeek = '03/01/' + convert(varchar,#Year)
return case datepart(dw,#DTSStartWeek)
when 1 then
dateadd(hour,170,#DTSStartWeek)
when 2 then
dateadd(hour,314,#DTSStartWeek)
when 3 then
dateadd(hour,290,#DTSStartWeek)
when 4 then
dateadd(hour,266,#DTSStartWeek)
when 5 then
dateadd(hour,242,#DTSStartWeek)
when 6 then
dateadd(hour,218,#DTSStartWeek)
when 7 then
dateadd(hour,194,#DTSStartWeek)
end
end
GO
CREATE function [dbo].[DC_GetDaylightSavingsTimeEnd]
(#Year varchar(4))
RETURNS smalldatetime
as
-- End date: We evaluate the day of the week corresponding to the first day of the month and find the first Sunday of March using a Case statement
begin
declare #DTSEndWeek smalldatetime
set #DTSEndWeek = '11/01/' + convert(varchar,#Year)
return case datepart(dw,dateadd(week,1,#DTSEndWeek))
when 1 then
dateadd(hour,2,#DTSEndWeek)
when 2 then
dateadd(hour,146,#DTSEndWeek)
when 3 then
dateadd(hour,122,#DTSEndWeek)
when 4 then
dateadd(hour,98,#DTSEndWeek)
when 5 then
dateadd(hour,74,#DTSEndWeek)
when 6 then
dateadd(hour,50,#DTSEndWeek)
when 7 then
dateadd(hour,26,#DTSEndWeek)
end
end
GO
I am storing all my dates in SQL Server Datetime fields in UTC Date.
There is a requirement where by I have to calculate local Datetime in a procedure from the UTC Date field, and for that i have the Time zone offset of the local datetime.
For ex. my Timezone offset is:'05:30:00'
and UTC Date is: 2013-02-09 08:34:12.037
Desired output: 2013-02-09 14:04:12.037
Now is there a simple way where of doing this without DateAdd and splitting the offset in hours and minutes.
You should be able to use the SWITCHOFFSET function. Here is an example:
declare #dt datetime;
set #dt = '2013-02-09 08:34:12.037';
select SWITCHOFFSET(CONVERT(datetimeoffset, #dt), '+05:30') as 'DATETIMEOFFSET',
CAST(SWITCHOFFSET(CONVERT(datetimeoffset, #dt), '+05:30') as datetime) as 'DATETIME'
-- Outputs:
--
-- 2013-02-09 14:04:12.0370000 +05:30 2013-02-09 14:04:12.037
Use the convert with 112:
declare #d datetime
set #d = getdate()
declare #s nvarchar(20)
set #s = convert(varchar, #d, 112)
print #s
That string will have the year, month, seconds etc.. always on the same position
Extract the desired part with substring:
print substring(#s, 1, 4) -- the year
Now recalculate the entire thing to minutes, by multiplying the hours by 60 and adding the minutes. Now substract your minutes delta from that number. Build a new string with the adjusted date-time, and convert that back to datetime. But... if you need to know the date as well, and you want to do it correct.... there is some coding left.
My advice: do use dateadd it's simple and correct (substract minutes is my advice).
I need to calculate difference in workdays between two dates. Is there a built in function for this in SQL Server? Can someone please provide an example on how to do this?
Here is something I wrote quickly. Just encapsulate it into a function or whatever you need.
declare #StartDate datetime
declare #EndDate datetime
declare #TotalDiff int
declare #NumberOfWeekends int
SET #StartDate = '3/12/2013'
SET #EndDate = '3/22/2013'
SET #NumberOfWeekends = 0
SET #TotalDiff = DATEDIFF(d,#StartDate, #EndDate)
If #TotalDiff > 7
SET #NumberOfWeekends = #TotalDiff / 7
else if DATEPART(dd, #EndDate) < DATEPART(DD, #StartDate)
SET #NumberOfWeekends = 1
select (#TotalDiff - 2*#NumberOfWeekends) as TotalWorkDays
No, there is nothing built in to SQL Server to directly give you number of working days between two dates, however there are a few built-in functions which will enable you to write one.
Firstly, a few caveats
The world cannot agree what a "Working Day" is. For most of us it's Saturday and Sunday. For most of the Middle East it's Friday & Saturday (with Sunday being a normal working day)
The world most certainly cannot agree on what constitutes a public holiday, which are almost always considered non-working days.
You have not specified how you would like to handle these cases so lets make some assumptions:
Saturday and Sunday will be non-working days
Public holidays will not be taken into acount
Now, determining if a particular days is saturday or sunday in sql is easy, given a #date of type DateTime:
IF DATENAME(dw,#date) IN ('Saturday','Sunday')
With that in mind, given a start and end date, you can just count incrementally from #startDate to #endDate
DECLARE #startDate DATETIME = '2013-01-01'
DECLARE #endDate DATETIME = '2013-01-20'
DECLARE #currDate DATETIME = #startDate
DECLARE #numDays INT = 0
WHILE #currDate<#endDate
BEGIN
IF DATENAME(dw,#currDate) NOT IN ('Saturday','Sunday')
SET #numDays = #numDays + 1
SET #currDate = DATEADD(day,1,#currDate)
END
SELECT #numDays
Note: This is non-inclsive so wont count #endDate. You could change it to be inclusive by changing WHILE #currDate<#endDate to WHILE #currDate<=#endDate
My solution does not count the #EndDate, so if you need to change that, just add 1 to #d2.
First, I calculate the number of days from an "initial" day (which happens to be 1/1/1900, a Monday) to #StartDate and #EndDate:
DECLARE #d1 int = DATEDIFF(Day, 0, #StartDate);
DECLARE #d2 int = DATEDIFF(Day, 0, #EndDate);
Then, the total number of days between #StartDate and #EndDate is:
#d2 - #d1
From this, I substract the number of Sundays and the number of Saturdays in the interval, each calculated as a difference simlar to the total days, but now for whole weeks (7 days). To get the number of whole weeks, I use integer division by 7 and the fact that the "initial" day (0) is a Monday. The number of Sundays in the interval is
#d2/7 - #d1/7
and the number of Saturdays is
(#d2+1)/7 - (#d1+1)/7
Putting all together, my solution is:
DECLARE #StartDate DATETIME = '20180101'
DECLARE #EndDate DATETIME = '20180201'
DECLARE #d1 int = DATEDIFF(Day, 0, #StartDate)
DECLARE #d2 int = DATEDIFF(Day, 0, #EndDate)
SELECT #d2 - #d1 - (#d2/7 - #d1/7) - ((#d2+1)/7 - (#d1+1)/7) AS workdays