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.