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 i
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
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.
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.
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