我正在做一些SQL选择查询,并希望将我的UTC日期时间列转换为本地时间,以便在我的查询结果中显示为本地时间。注意,我不希望通过代码进行这种转换,而是当我对我的数据库进行手动和随机SQL查询时。


当前回答

没有一种简单的方法能以正确而通用的方式做到这一点。

首先,必须理解偏移量取决于所讨论的日期、时区和夏令时。 GetDate()-GetUTCDate只提供今天服务器TZ的偏移量,这是不相关的。

我只见过两种有效的解决方案,我已经搜索了很多。

1)一个自定义SQL函数,包含几个基本数据表,如每个TZ的时区和夏令时规则。 工作,但不是很优雅。我不能发布,因为我没有代码。

编辑:下面是这个方法的一个例子 https://gist.github.com/drumsta/16b79cee6bc195cd89c8

2)添加一个.net程序集到db, .net可以很容易地做到这一点。这工作得很好,但缺点是你需要在服务器级配置几个参数,配置很容易被破坏,例如,如果你恢复数据库。 我使用这个方法,但我不能张贴它,因为我没有自己的代码。

其他回答

这些对我来说都没用,但下面这个100%有效。希望这能帮助其他像我一样尝试转换它的人。

CREATE FUNCTION [dbo].[fn_UTC_to_EST]
(
    @UTC datetime,
    @StandardOffset int
)
RETURNS datetime
AS
BEGIN

declare 
    @DST datetime,
    @SSM datetime, -- Second Sunday in March
    @FSN datetime  -- First Sunday in November
-- get DST Range
set @SSM = DATEADD(dd,7 + (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 2,0))+'02:00:00' 
set @FSN = DATEADD(dd, (6-(DATEDIFF(dd,0,DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0))%7)),DATEADD(mm,(YEAR(GETDATE())-1900) * 12 + 10,0)) +'02:00:00'

-- add an hour to @StandardOffset if @UTC is in DST range
if @UTC between @SSM and @FSN
    set @StandardOffset = @StandardOffset + 1

-- convert to DST
set @DST = dateadd(hour,@StandardOffset,@UTC)

-- return converted datetime
return @DST

END

第一个功能:配置为意大利时区(+1,+2),切换日期:3月和10月的最后一个星期天,返回当前时区与datetime的差值作为参数。

Returns:
current timezone < parameter timezone ==> +1
current timezone > parameter timezone ==> -1
else 0

代码是:

CREATE FUNCTION [dbo].[UF_ADJUST_OFFSET]
(
    @dt_utc datetime2(7)
)
RETURNS INT
AS
BEGIN


declare @month int,
        @year int,
        @current_offset int,
        @offset_since int,
        @offset int,
        @yearmonth varchar(8),
        @changeoffsetdate datetime2(7)

declare @lastweek table(giorno datetime2(7))

select @current_offset = DATEDIFF(hh, GETUTCDATE(), GETDATE())

select @month = datepart(month, @dt_utc)

if @month < 3 or @month > 10 Begin Set @offset_since = 1 Goto JMP End

if @month > 3 and @month < 10 Begin Set @offset_since = 2 Goto JMP End

--If i'm here is march or october
select @year = datepart(yyyy, @dt_utc)

if @month = 3
Begin

Set @yearmonth = cast(@year as varchar) + '-03-'

Insert Into @lastweek Values(@yearmonth + '31 03:00:00.000000'),(@yearmonth + '30 03:00:00.000000'),(@yearmonth + '29 03:00:00.000000'),(@yearmonth + '28 03:00:00.000000'),
                         (@yearmonth + '27 03:00:00.000000'),(@yearmonth + '26 03:00:00.000000'),(@yearmonth + '25 03:00:00.000000')

--Last week of march
Select @changeoffsetdate = giorno From @lastweek Where  datepart(weekday, giorno) = 1

    if @dt_utc < @changeoffsetdate 
    Begin 
        Set @offset_since = 1 
    End Else Begin
        Set @offset_since = 2
    End
End

if @month = 10
Begin

Set @yearmonth = cast(@year as varchar) + '-10-'

Insert Into @lastweek Values(@yearmonth + '31 03:00:00.000000'),(@yearmonth + '30 03:00:00.000000'),(@yearmonth + '29 03:00:00.000000'),(@yearmonth + '28 03:00:00.000000'),
                         (@yearmonth + '27 03:00:00.000000'),(@yearmonth + '26 03:00:00.000000'),(@yearmonth + '25 03:00:00.000000')

--Last week of october
Select @changeoffsetdate = giorno From @lastweek Where  datepart(weekday, giorno) = 1

    if @dt_utc > @changeoffsetdate 
    Begin 
        Set @offset_since = 1 
    End Else Begin
        Set @offset_since = 2
    End
End

JMP:

if @current_offset < @offset_since Begin
    Set @offset = 1
End Else if @current_offset > @offset_since Set @offset = -1 Else Set @offset = 0

Return @offset

END

然后是转换日期的函数

CREATE FUNCTION [dbo].[UF_CONVERT]
(
    @dt_utc datetime2(7)
)
RETURNS datetime
AS
BEGIN

    declare @offset int


    Select @offset = dbo.UF_ADJUST_OFFSET(@dt_utc)

    if @dt_utc >= '9999-12-31 22:59:59.9999999'
        set @dt_utc = '9999-12-31 23:59:59.9999999'
    Else
        set @dt_utc = (SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), GETDATE()), @dt_utc) )

    if @offset <> 0
        Set @dt_utc = dateadd(hh, @offset, @dt_utc)

    RETURN @dt_utc

END

作为一个警告-如果你要使用以下(注意毫秒而不是分钟):

    SELECT DATEADD(ms, DATEDIFF(ms, GETUTCDATE(), GETDATE()), MyTable.UtcColumn) 
    AS ColumnInLocalTime
    FROM MyTable

请记住,DATEDIFF部分并不总是返回相同的数字。所以不要用它来将DateTimes精确到毫秒。

使用新的SQL Server 2016机会:

CREATE FUNCTION ToLocalTime(@dtUtc datetime, @timezoneId nvarchar(256))
RETURNS datetime
AS BEGIN

return @dtUtc AT TIME ZONE 'UTC' AT TIME ZONE @timezoneId

/* -- second way, faster

return SWITCHOFFSET(@dtUtc , DATENAME(tz, @dtUtc AT TIME ZONE @timezoneId))

*/

/* -- third way

declare @dtLocal datetimeoffset
set @dtLocal = @dtUtc AT TIME ZONE @timezoneId
return dateadd(minute, DATEPART (TZoffset, @dtLocal), @dtUtc)

*/

END
GO

但clr程序的工作速度快5倍:'-(

请注意,一个时区的偏移量可以更改为冬季时间或夏季时间。例如

select cast('2017-02-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'
select cast('2017-08-08 09:00:00.000' as datetime) AT TIME ZONE 'Eastern Standard Time'

结果:

2017-02-08 09:00:00.000 -05:00
2017-08-08 09:00:00.000 -04:00

你不能只是添加常数偏移量。

对于Azure SQL和@@Version >= SQL Server 2016用户,下面是一个使用AT TIME ZONE的简单函数。

CREATE FUNCTION [dbo].[Global_Convert_UTCTimeTo_LocalTime]
(
   @LocalTimeZone        VARCHAR(50),
   @UTCDateTime          DATETIME
)
RETURNS DATETIME
AS
BEGIN
   DECLARE @ConvertedDateTime DATETIME;

   SELECT @ConvertedDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE @LocalTimeZone
   RETURN @ConvertedDateTime

END
GO

对于@LocalTimeZone可以采用的值类型,请转到此链接或转到KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones