对于那些被困在SQL Server 2005中且不想或无法使用UDF的人 - 特别是在美国以外的地区 - 我采用了@Bobman的方法并将其泛化。以下内容适用于美国、欧洲、新西兰和澳大利亚,但需要注意的是,即使处于同一“基本”时区的澳大利亚州也不是所有州都遵守夏令时。添加尚未支持的DST规则也很容易,只需在@calculation
值中添加一行即可。
-- =============================================
-- 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
此函数在运行时查看本地时间和协调世界时之间的差异,以确定应用哪些夏令时规则。然后它知道是否包含夏令时小时并将其减去。然后它确定输入UTC日期时间的日期是否为夏令时,并在此情况下添加夏令时小时。
这带来一个小问题,即在夏令时的最后一小时和非夏令时的第一小时,该函数无法确定是哪个,并假定是后者。因此,无论输入日期时间如何,如果此代码在夏令时的最后一小时运行,则会给出错误的结果。这意味着它99.9886%的时间可以正常工作。