If you’ve read my various posts on dealing with datetimes and timezones lately, I discovered a handy little mechanism for dealing with timezones in Microsoft SQL.
Windows stores all Timezone information in the Registry. Fortunately, Microsoft SQL, through T-SQL, can access and query the registry. Below is a simple query that will pull all of the timezone information from the registry and put it all into a temporary table. The timezone offset is determined by performing a split/parse of the offset string from the registry. This information can be used to convert between zones.
SET NOCOUNT ON; DECLARE @root varchar(1000) = 'HKEY_LOCAL_MACHINE'; DECLARE @key varchar(1000) = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones'; DECLARE @tzName varchar(1000); DECLARE @tzKey varchar(1000); DECLARE @offset varchar(1000); DECLARE @tzi binary(56); DECLARE @substr varchar(1000); DECLARE @pos int; DECLARE @hours int; DECLARE @minutes int; DECLARE @start int; DECLARE @end int; DECLARE @isNegative bit; DECLARE @utcOffset INT = DATEDIFF(minute, GETDATE(), GETUTCDATE()); DECLARE @parsedOffset INT; DECLARE @midnight datetime2 = CAST(DATEFROMPARTS(DATEPART(year, GETDATE()), DATEPART(month, GETDATE()), DATEPART(day, GETDATE())) AS datetime2); DECLARE @testdate datetime2 = CAST(DATEFROMPARTS(DATEPART(year, GETDATE()), DATEPART(month, GETDATE()), DATEPART(day, GETDATE())) AS datetime2); DECLARE @timeZoneNames TABLE ( SubKeyName varchar(1000) ) DECLARE @timeZones TABLE ( Name varchar(1000), Offset varchar(1000), ParsedOffset int, BiasMinutes int, ExtraBias_Std int, ExtraBias_DST int ) INSERT INTO @timeZoneNames EXEC master..xp_regenumkeys @root, @key DECLARE tz_cursor CURSOR FOR SELECT SubKeyName FROM @timeZoneNames OPEN tz_cursor FETCH NEXT FROM tz_cursor INTO @tzName WHILE @@FETCH_STATUS = 0 BEGIN SET @tzKey = @key + '\' + @tzName exec master.dbo.xp_regread @root, @tzKey, 'Display', @offset OUTPUT SET @pos = CHARINDEX(':', @offset); IF @pos > 0 BEGIN SET @substr = SUBSTRING(@offset, 1, @pos); SET @isNegative = CASE WHEN PATINDEX('%-%', @substr) > 0 THEN 1 ELSE 0 END; SET @start = CASE WHEN @isNegative = 1 THEN PATINDEX('%-%', @substr) ELSE PATINDEX('%+%', @substr) END + 1; SET @end = PATINDEX('%[^0-9]%', SUBSTRING(@substr, @start, LEN(@substr)))-1 SET @hours = CAST(SUBSTRING(@substr, @start, @end) AS int) * CASE WHEN @isNegative = 1 THEN -1 ELSE 1 END; SET @substr = SUBSTRING(@offset, @pos, LEN(@offset)); SET @start = PATINDEX('%[0-9]%', @substr); SET @end = PATINDEX('%[^0-9]%',SUBSTRING(@substr, @start, LEN(@substr)))-1 SET @minutes = SUBSTRING(@substr, @start, @end); exec master.dbo.xp_regread @root, @tzKey, 'TZI', @tzi OUTPUT INSERT INTO @timeZones SELECT @tzName, @offset, @hours * 60 + @minutes, -- See http://msdn.microsoft.com/ms725481 CAST(CAST(REVERSE(SUBSTRING(@tzi, 1, 4)) AS binary(4)) AS int) AS BiasMinutes, -- UTC = local + bias: > 0 in US, < 0 in Europe! CAST(CAST(REVERSE(SUBSTRING(@tzi, 5, 4)) AS binary(4)) AS int) AS ExtraBias_Std, -- 0 for most timezones CAST(CAST(REVERSE(SUBSTRING(@tzi, 9, 4)) AS binary(4)) AS int) AS ExtraBias_DST; END FETCH NEXT FROM tz_cursor INTO @tzName END CLOSE tz_cursor DEALLOCATE tz_cursor -- Let's say the dealer makes a sale at 8PM PST SET @testdate = DATEADD(hour, 18, CAST(DATEFROMPARTS(DATEPART(year, GETDATE()), DATEPART(month, GETDATE()), DATEPART(day, GETDATE())) AS datetime2)); -- What does that look like in UTC time? What does our configured date at midnight look like? What does midnight in Knoxville look like in UTC? -- This shows that we have to have a point of reference to compare the configured dates. They either need a fixed offset for the region or by sale. SELECT TOP 1 @parsedOffset = ParsedOffset FROM @timeZones WHERE Name LIKE 'Pacific Standard Time'; SELECT DATEADD(minute, @parsedOffset, @testdate) AS ConvertPSTToUTC, DATEADD(minute, @parsedOffset, DATEADD(day, 1, @midnight)) AS MidnightUTC, DATEADD(minute, @utcOffset, @midnight) AS LocalMidnightUTC SELECT Name, ParsedOffset, DATEADD(minute, @utcOffset, @midnight) AS LocalMidnight, DATEADD(minute, ParsedOffset, @midnight) AS TimeZoneMidnight FROM @timeZones