SQL Server - 将日期字段转换为UTC

时间:2010-04-23 16:20:10

标签: sql-server datetime utc

我最近更新了我的系统,将日期/时间记录为以前以当地时间存储的UTC。

我现在需要将所有本地存储的日期/时间转换为UTC。我想知道是否有内置函数,类似于.NET的ConvertTime方法?

我正在努力避免编写实用程序应用程序来为我执行此操作。

有什么建议吗?

14 个答案:

答案 0 :(得分:74)

我不相信上面的代码会起作用。原因是它取决于当地日期和UTC时间之间的差异。例如,在加利福尼亚,我们现在在PDT(太平洋夏令时);此时间与UTC之间的差异为7小时。如果现在运行,所提供的代码将在每个希望转换的日期添加7个小时。但是,如果转换了历史存储日期或将来某个日期,并且该日期不是夏令时期间,则当正确的偏移量为8时,它仍会添加7。底线:您无法正确转换日期/时间通过仅查看当前日期,在时区之间(包括不遵守夏令时的UTC)。您必须考虑要转换的日期本身,以及日期时间是否在该日期生效。此外,白天和标准时间自身发生变化的日期也发生了变化(乔治布什在美国执政期间更改了日期!)。换句话说,任何甚至引用getdate()或getutcdate()的解决方案都不起作用。它必须解析要转换的实际日期。

答案 1 :(得分:37)

使用SQL Server 2016,现在可以使用AT TIME ZONE语句内置对时区的支持。您可以链接这些进行转换:

SELECT YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time' AT TIME ZONE 'UTC'

或者,这也可行:

SELECT SWITCHOFFSET(YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time', '+00:00')

其中任何一个都将解释太平洋时间的输入,正确说明DST是否生效,然后转换为UTC。结果将是datetimeoffset,零偏移。

More examples in the CTP announcement.

答案 2 :(得分:16)

如前所述,没有内置方法可以在SQL Server中执行时区规则感知日期转换(至少从SQL Server 2012开始)。

您基本上有三种选择:

  1. 在SQL Server外部执行转换并将结果存储在数据库中
  2. 在独立表中引入时区偏移规则,并创建存储过程或UDF以引用规则表以执行转换。您可以找到一种方法over at SQL Server Central(需要注册)
  3. 您可以创建SQL CLR UDF;我将在这里描述方法
  4. 虽然SQL Server不提供执行时区规则感知日期转换的工具,但.NET框架确实如此,只要您可以使用SQL CLR,就可以利用它。

    在Visual Studio 2012中,确保安装了数据工具(否则,SQL Server项目不会显示为选项),并创建新的SQL Server项目。

    然后,添加一个新的SQL CLR C#用户定义函数,将其命名为“ConvertToUtc”。 VS将为您生成样板,看起来像这样:

    public partial class UserDefinedFunctions
    {
        [Microsoft.SqlServer.Server.SqlFunction]
        public static SqlString ConvertToUtc()
        {
            // Put your code here
            return new SqlString (string.Empty);
        }
    }
    

    我们想在这里做一些改变。首先,我们想要返回SqlDateTime而不是SqlString。其次,我们想做一些有用的事情。 :)

    您修改后的代码应如下所示:

    public partial class UserDefinedFunctions
    {
        [Microsoft.SqlServer.Server.SqlFunction]
        public static SqlDateTime ConvertToUtc(SqlDateTime sqlLocalDate)
        {
            // convert to UTC and use explicit conversion
            // to return a SqlDateTime
            return TimeZone.CurrentTimeZone.ToUniversalTime(sqlLocalDate.Value);
        }
    }
    

    此时,我们已准备好尝试一下。最简单的方法是在Visual Studio中使用内置的Publish工具。右键单击数据库项目,然后选择“发布”。设置数据库连接和名称,然后单击“发布”将代码推送到数据库中或单击“生成脚本”,如果您要为后代存储脚本(或将位推送到生产中)。 / p>

    在数据库中安装UDF后,您可以看到它的实际效果:

    declare @dt as datetime
    set @dt = '12/1/2013 1:00 pm'
    select dbo.ConvertToUtc(@dt)
    

答案 3 :(得分:11)

如果他们都是你的本地人,那么这里是偏移量:

SELECT GETDATE() AS CurrentTime, GETUTCDATE() AS UTCTime

您应该能够使用以下方式更新所有数据:

UPDATE SomeTable
   SET DateTimeStamp = DATEADD(hh, DATEDIFF(hh, GETDATE(), GETUTCDATE()), DateTimeStamp)

这会起作用,还是我错过了这个问题的另一个角度?

答案 4 :(得分:7)

这是一个经过测试的程序,将我的数据库从本地升级到utc时间。升级数据库所需的唯一输入是输入本地时间从utc时间偏移到@Offset的分钟数,以及通过设置@ApplyDaylightSavings来调整时区的夏令时。

例如,美国中部时间将输入@ Offset = -360和@ ApplyDaylightSavings = 1持续6小时,是的应用夏令时调整。

支持数据库功能

CREATE FUNCTION [dbo].[GetUtcDateTime](@LocalDateTime DATETIME, @Offset smallint, @ApplyDaylightSavings bit) 
RETURNS DATETIME AS BEGIN 

    --====================================================
    --Calculate the Offset Datetime
    --====================================================
    DECLARE @UtcDateTime AS DATETIME
    SET @UtcDateTime = DATEADD(MINUTE, @Offset * -1, @LocalDateTime)

    IF @ApplyDaylightSavings = 0 RETURN @UtcDateTime;

    --====================================================
    --Calculate the DST Offset for the UDT Datetime
    --====================================================
    DECLARE @Year as SMALLINT
    DECLARE @DSTStartDate AS DATETIME
    DECLARE @DSTEndDate AS DATETIME

    --Get Year
    SET @Year = YEAR(@LocalDateTime)

    --Get First Possible DST StartDay
    IF (@Year > 2006) SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-03-08 02:00:00'
    ELSE              SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-04-01 02:00:00'
    --Get DST StartDate 
    WHILE (DATENAME(dw, @DSTStartDate) <> 'sunday') SET @DSTStartDate = DATEADD(day, 1,@DSTStartDate)


    --Get First Possible DST EndDate
    IF (@Year > 2006) SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-11-01 02:00:00'
    ELSE              SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-10-25 02:00:00'

    --Get DST EndDate 
    WHILE (DATENAME(dw, @DSTEndDate) <> 'sunday') SET @DSTEndDate = DATEADD(day,1,@DSTEndDate)

    --Finally add the DST Offset if needed 
    RETURN CASE WHEN @LocalDateTime BETWEEN @DSTStartDate AND @DSTEndDate THEN 
        DATEADD(MINUTE, -60, @UtcDateTime) 
    ELSE 
        @UtcDateTime
    END

END
GO

升级脚本

  1. 在运行此脚本之前进行备份!
  2. 设置@Offset&amp; @ApplyDaylightSavings
  3. 只运行一次!
  4. begin try
        begin transaction;
    
        declare @sql nvarchar(max), @Offset smallint, @ApplyDaylightSavings bit;
    
        set @Offset = -360;             --US Central Time, -300 for US Eastern Time, -480 for US West Coast
        set @ApplyDaylightSavings = 1;  --1 for most US time zones except Arizona which doesn't observer daylight savings, 0 for most time zones outside the US
    
        declare rs cursor for
        select 'update [' + a.TABLE_SCHEMA + '].[' + a.TABLE_NAME + '] set [' + a.COLUMN_NAME + '] = dbo.GetUtcDateTime([' + a.COLUMN_NAME + '], ' + cast(@Offset as nvarchar) + ', ' + cast(@ApplyDaylightSavings as nvarchar) + ') ;'
        from INFORMATION_SCHEMA.COLUMNS a
            inner join INFORMATION_SCHEMA.TABLES b on a.TABLE_SCHEMA = b.TABLE_SCHEMA and a.TABLE_NAME = b.TABLE_NAME
        where a.DATA_TYPE = 'datetime' and b.TABLE_TYPE = 'BASE TABLE' ;
    
        open rs;
        fetch next from rs into @sql;
        while @@FETCH_STATUS = 0 begin
            exec sp_executesql @sql;
            print @sql;
            fetch next from rs into @sql;
        end
        close rs;
        deallocate rs;
    
        commit transaction;
    end try
    begin catch
        close rs;
        deallocate rs;
    
        declare @ErrorMessage nvarchar(max), @ErrorSeverity int, @ErrorState int;
        select @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5)), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE();
        rollback transaction;
        raiserror (@ErrorMessage, @ErrorSeverity, @ErrorState);
    end catch
    

答案 5 :(得分:5)

如果您必须将今天以外的日期转换为不同的时区,则必须处理夏令时。我想要一个可以在不担心数据库版本的情况下完成的解决方案,而无需使用存储的函数和可以轻松移植到Oracle的东西。

我认为Warren在正确的轨道上获得正确的白天时间日期,但是为了使它更适用于多个时区和国家的不同规则,甚至是2006年至2007年间在美国发生变化的规则,这里上述解决方案的变体。请注意,这不仅包括我们的时区,还包括中欧。中欧遵循四月的最后一个星期日和十月的最后一个星期日。你还会注意到美国在2006年是在十月的第一个星期天,也就是十月的最后一个星期天。

这个SQL代码可能看起来有点难看,但只需将其复制并粘贴到SQL Server中即可试用。请注意,有三年的时间段,时区和规则。如果您想要另一年,只需将其添加到年度工会即可。对于另一个时区或规则也是如此。

select yr, zone, standard, daylight, rulename, strule, edrule, yrstart, yrend,
    dateadd(day, (stdowref + stweekadd), stmonthref) dstlow,
    dateadd(day, (eddowref + edweekadd), edmonthref)  dsthigh
from (
  select yrs.yr, z.zone, z.standard, z.daylight, z.rulename, r.strule, r.edrule, 
    yrs.yr + '-01-01 00:00:00' yrstart,
    yrs.yr + '-12-31 23:59:59' yrend,
    yrs.yr + r.stdtpart + ' ' + r.cngtime stmonthref,
    yrs.yr + r.eddtpart + ' ' + r.cngtime edmonthref,
    case when r.strule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.stdtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.stdtpart) end
    else (datepart(dw, yrs.yr + r.stdtpart) - 1) * -1 end stdowref,
    case when r.edrule in ('1', '2', '3') then case when datepart(dw, yrs.yr + r.eddtpart) = '1' then 0 else 8 - datepart(dw, yrs.yr + r.eddtpart) end
    else (datepart(dw, yrs.yr + r.eddtpart) - 1) * -1 end eddowref,
    datename(dw, yrs.yr + r.stdtpart) stdow,
    datename(dw, yrs.yr + r.eddtpart) eddow,
    case when r.strule in ('1', '2', '3') then (7 * CAST(r.strule AS Integer)) - 7 else 0 end stweekadd,
    case when r.edrule in ('1', '2', '3') then (7 * CAST(r.edrule AS Integer)) - 7 else 0 end edweekadd
from (
    select '2005' yr union select '2006' yr -- old us rules
    UNION select '2007' yr UNION select '2008' yr UNION select '2009' yr UNION select '2010' yr UNION select '2011' yr
    UNION select '2012' yr UNION select '2013' yr UNION select '2014' yr UNION select '2015' yr UNION select '2016' yr
    UNION select '2017' yr UNION select '2018' yr UNION select '2018' yr UNION select '2020' yr UNION select '2021' yr
    UNION select '2022' yr UNION select '2023' yr UNION select '2024' yr UNION select '2025' yr UNION select '2026' yr
) yrs
cross join (
    SELECT 'ET' zone, '-05:00' standard, '-04:00' daylight, 'US' rulename
    UNION SELECT 'CT' zone, '-06:00' standard, '-05:00' daylight, 'US' rulename
    UNION SELECT 'MT' zone, '-07:00' standard, '-06:00' daylight, 'US' rulename
    UNION SELECT 'PT' zone, '-08:00' standard, '-07:00' daylight, 'US' rulename
    UNION SELECT 'CET' zone, '+01:00' standard, '+02:00' daylight, 'EU' rulename
) z
join (
    SELECT 'US' rulename, '2' strule, '-03-01' stdtpart, '1' edrule, '-11-01' eddtpart, 2007 firstyr, 2099 lastyr, '02:00:00' cngtime
    UNION SELECT 'US' rulename, '1' strule, '-04-01' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2006 lastyr, '02:00:00' cngtime
    UNION SELECT  'EU' rulename, 'L' strule, '-03-31' stdtpart, 'L' edrule, '-10-31' eddtpart, 1900 firstyr, 2099 lastyr, '01:00:00' cngtime
) r on r.rulename = z.rulename
    and datepart(year, yrs.yr) between firstyr and lastyr
) dstdates

对于规则,请使用1,2,3或L作为第一,第二,第三或最后一个星期日。日期部分给出月份,具体取决于规则类型L的规则,月份的第一天或月份的最后一天。

我将上述查询放入视图中。现在,只要我想要一个带有时区偏移或转换为UTC时间的日期,我只需加入此视图并选择以日期格式获取日期。而不是datetime,我将这些转换为datetimeoffset。

select createdon, dst.zone
    , case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end pacificoffsettime
    , TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end) pacifictime
    , SWITCHOFFSET(TODATETIMEOFFSET(createdon, case when createdon >= dstlow and createdon < dsthigh then dst.daylight else dst.standard end), '+00:00')  utctime
from (select '2014-01-01 12:00:00' createdon union select '2014-06-01 12:00:00' createdon) photos
left join US_DAYLIGHT_DATES dst on createdon between yrstart and yrend and zone = 'PT'

答案 6 :(得分:2)

这是我的快速和肮脏的版本。我知道我的所有日​​期都在使用美国东部时区。您可以根据需要更改偏移量或使其更智能。我正在进行一次性迁移,所以这很好。

CREATE FUNCTION [dbo].[ConvertToUtc]
(
    @date datetime
)
RETURNS DATETIME
AS
BEGIN
    -- Declare the return variable here
    DECLARE @utcDate datetime;
    DECLARE @offset int;

    SET @offset = (SELECT CASE WHEN 
                                    @date BETWEEN '1987-04-05 02:00 AM' AND '1987-10-25 02:00 AM'
                                 OR @date BETWEEN '1988-04-03 02:00 AM' AND '1988-10-30 02:00 AM'
                                 OR @date BETWEEN '1989-04-02 02:00 AM' AND '1989-10-29 02:00 AM'
                                 OR @date BETWEEN '1990-04-01 02:00 AM' AND '1990-10-28 02:00 AM'
                                 OR @date BETWEEN '1991-04-07 02:00 AM' AND '1991-10-27 02:00 AM'
                                 OR @date BETWEEN '1992-04-05 02:00 AM' AND '1992-10-25 02:00 AM'
                                 OR @date BETWEEN '1993-04-04 02:00 AM' AND '1993-10-31 02:00 AM'
                                 OR @date BETWEEN '1994-04-03 02:00 AM' AND '1994-10-30 02:00 AM'
                                 OR @date BETWEEN '1995-04-02 02:00 AM' AND '1995-10-29 02:00 AM'
                                 OR @date BETWEEN '1996-04-07 02:00 AM' AND '1996-10-27 02:00 AM'
                                 OR @date BETWEEN '1997-04-06 02:00 AM' AND '1997-10-26 02:00 AM'
                                 OR @date BETWEEN '1998-04-05 02:00 AM' AND '1998-10-25 02:00 AM'
                                 OR @date BETWEEN '1999-04-04 02:00 AM' AND '1999-10-31 02:00 AM'
                                 OR @date BETWEEN '2000-04-02 02:00 AM' AND '2000-10-29 02:00 AM'
                                 OR @date BETWEEN '2001-04-01 02:00 AM' AND '2001-10-28 02:00 AM'
                                 OR @date BETWEEN '2002-04-07 02:00 AM' AND '2002-10-27 02:00 AM'
                                 OR @date BETWEEN '2003-04-06 02:00 AM' AND '2003-10-26 02:00 AM'
                                 OR @date BETWEEN '2004-04-04 02:00 AM' AND '2004-10-31 02:00 AM'
                                 OR @date BETWEEN '2005-04-03 02:00 AM' AND '2005-10-30 02:00 AM'
                                 OR @date BETWEEN '2006-04-02 02:00 AM' AND '2006-10-29 02:00 AM'
                                 OR @date BETWEEN '2007-03-11 02:00 AM' AND '2007-11-04 02:00 AM'
                                 OR @date BETWEEN '2008-03-09 02:00 AM' AND '2008-11-02 02:00 AM'
                                 OR @date BETWEEN '2009-03-08 02:00 AM' AND '2009-11-01 02:00 AM'
                                 OR @date BETWEEN '2010-03-14 02:00 AM' AND '2010-11-07 02:00 AM'
                                 OR @date BETWEEN '2011-03-13 02:00 AM' AND '2011-11-06 02:00 AM'
                                 OR @date BETWEEN '2012-03-11 02:00 AM' AND '2012-11-04 02:00 AM'
                                 OR @date BETWEEN '2013-03-10 02:00 AM' AND '2013-11-03 02:00 AM'
                                 OR @date BETWEEN '2014-03-09 02:00 AM' AND '2014-11-02 02:00 AM'
                                 OR @date BETWEEN '2015-03-08 02:00 AM' AND '2015-11-01 02:00 AM'
                                 OR @date BETWEEN '2016-03-13 02:00 AM' AND '2016-11-06 02:00 AM'
                                 OR @date BETWEEN '2017-03-12 02:00 AM' AND '2017-11-05 02:00 AM'
                                 OR @date BETWEEN '2018-03-11 02:00 AM' AND '2018-11-04 02:00 AM'
                                 OR @date BETWEEN '2019-03-10 02:00 AM' AND '2019-11-03 02:00 AM'
                                 OR @date BETWEEN '2020-03-08 02:00 AM' AND '2020-11-01 02:00 AM'
                                 OR @date BETWEEN '2021-03-14 02:00 AM' AND '2021-11-07 02:00 AM'
                               THEN 4
                               ELSE 5 END);

    SELECT @utcDate = DATEADD(hh, @offset, @date)
    RETURN @utcDate;

END

答案 7 :(得分:1)

除非我遗漏了上述(可能)的内容,否则上述所有方法都存在缺陷,因为从夏令时(比如EDT)转换到标准时间(例如EST)时,它们不会重叠。一个(非常详细)的例子:

[1] EDT 2016-11-06 00:59 - UTC 2016-11-06 04:59
[2] EDT 2016-11-06 01:00 - UTC 2016-11-06 05:00
[3] EDT 2016-11-06 01:30 - UTC 2016-11-06 05:30
[4] EDT 2016-11-06 01:59 - UTC 2016-11-06 05:59
[5] EST 2016-11-06 01:00 - UTC 2016-11-06 06:00
[6] EST 2016-11-06 01:30 - UTC 2016-11-06 06:30
[7] EST 2016-11-06 01:59 - UTC 2016-11-06 06:59
[8] EST 2016-11-06 02:00 - UTC 2016-11-06 07:00

基于日期和时间的简单小时偏移不会削减它。如果您不知道当地时间是在01:00和01:59之间的EDT或EST中记录的,那么您将不会有任何线索!让我们以01:30为例:如果你在01:31到01:59的范围内找到以后的时间,那你就不知道你正在看的01:30是[3还是[6]。在这种情况下,您可以通过查看之前的条目来获得正确的UTC时间(在SQL中没有乐趣),这是最好的情况......

假设您记录了以下当地时间,并没有专门指出EDT或EST:

                     UTC time         UTC time         UTC time
                     if [2] and [3]   if [2] and [3]   if [2] before
local time           before switch    after switch     and [3] after
[1] 2016-11-06 00:43     04:43         04:43           04:43
[2] 2016-11-06 01:15     05:15         06:15           05:15
[3] 2016-11-06 01:45     05:45         06:45           06:45
[4] 2016-11-06 03:25     07:25         07:25           07:25

时间[2]和[3]可能在凌晨5点的时间帧,上午6点的时间帧,或者在凌晨5点的时间帧中,也可能在上午6点的时间帧中。 。 。换句话说:你被软管,必须在01:00:00和01:59:59之间扔掉所有读数。在这种情况下,绝对没有办法解决实际的UTC时间!

答案 8 :(得分:0)

以下内容应该可以正常运行,因为它会计算您正在运行的服务器的DATE和UTCDATE之间的差异,并使用该偏移量来计算传递给它的任何日期的UTC等效值。在我的例子中,我试图在澳大利亚阿德莱德转换UTC等效于'1-nov-2012 06:00',其中UTC偏移为-630分钟,当添加到任何日期时将导致UTC相当于任何本地日期。

选择DATEADD(MINUTE,DATEDIFF(MINUTE,GETDATE(),GETUTCDATE()),'1-nov-2012 06:00')

答案 9 :(得分:0)

根据您需要的距离,您可以构建一个夏令时表,然后加入表并进行dst敏感转换。这个特殊的从EST转换为GMT(即使用5和4的偏移)。

select createdon, dateadd(hour, case when dstlow is null then 5 else 4 end, createdon) as gmt
from photos
left outer join (
          SELECT {ts '2009-03-08 02:00:00'} as dstlow, {ts '2009-11-01 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2010-03-14 02:00:00'} as dstlow, {ts '2010-11-07 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2011-03-13 02:00:00'} as dstlow, {ts '2011-11-06 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2012-03-11 02:00:00'} as dstlow, {ts '2012-11-04 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2013-03-10 02:00:00'} as dstlow, {ts '2013-11-03 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2014-03-09 02:00:00'} as dstlow, {ts '2014-11-02 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2015-03-08 02:00:00'} as dstlow, {ts '2015-11-01 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2016-03-13 02:00:00'} as dstlow, {ts '2016-11-06 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2017-03-12 02:00:00'} as dstlow, {ts '2017-11-05 02:00:00'} as dsthigh
UNION ALL SELECT {ts '2018-03-11 02:00:00'} as dstlow, {ts '2018-11-04 02:00:00'} as dsthigh
    ) dst
    on createdon >= dstlow and createdon < dsthigh

答案 10 :(得分:0)

我们可以将ServerZone DateTime转换为UTC,将UTC转换为ServerZone DateTime

只需运行以下脚本即可了解转换,然后根据需要进行修改

--Get Server's TimeZone
DECLARE @ServerTimeZone VARCHAR(50)
EXEC MASTER.dbo.xp_regread 'HKEY_LOCAL_MACHINE',
'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
'TimeZoneKeyName',@ServerTimeZone OUT

-- ServerZone to UTC DATETIME
DECLARE @CurrentServerZoneDateTime DATETIME = GETDATE()
DECLARE @UTCDateTime  DATETIME =  @CurrentServerZoneDateTime AT TIME ZONE @ServerTimeZone AT TIME ZONE 'UTC' 
--(OR)
--DECLARE @UTCDateTime  DATETIME = GETUTCDATE()
SELECT @CurrentServerZoneDateTime AS CURRENTZONEDATE,@UTCDateTime AS UTCDATE

-- UTC to ServerZone DATETIME
SET @CurrentServerZoneDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE @ServerTimeZone
SELECT @UTCDateTime AS UTCDATE,@CurrentServerZoneDateTime AS CURRENTZONEDATE

注意:此(AT TIME ZONE仅处理SQL Server 2016 + ,此优势是在转换时自动考虑Daylight 到特定的时区

答案 11 :(得分:0)

Matt Johnson's answer绝对是Microsoft SQL Server 2016+中最好的方法。他关于使用“时区”的建议的确存在一个漏洞。 DST结束后的一小时内无法确定与UTC的正确偏移量。据我所知,没有解决方法。请看下面的例子。

--Assume that the value of input_date = '2018-11-04 01:00:00'
--Assume that dates in this field are written in Central Standard Time

select input_date at time zone 'Central Standard Time'

>> '2018-11-04 01:00:00 -05:00'

默认的Microsoft SQL Server行为是假设input_date值的偏移量是-05:00。但是,此时间实际上可能表示-05:00或-06:00偏移,具体取决于DST是否已经结束。没有伴随的偏移量就无法确定,所以Microsoft SQL Server会猜测可能正确还是错误。

答案 12 :(得分:0)

万一有人偶然发现它并使用克里斯·巴洛(Chris Barlow)的答案,有一个错别字,其中2018年被列出两次,第二次应该是2019年。

   UNION select '2017' yr UNION select '2018' yr UNION select '2018' yr UNION select '2020' yr UNION select '2021' yr

对不起,我对克里斯的回答没有足够的意见。除此之外,答案还不错!

答案 13 :(得分:0)

我玩游戏有些迟了,但是我需要在SQL 2012上做类似的事情,我还没有完全测试它,但这是我想出的。

CREATE FUNCTION SMS.fnConvertUTC
(
    @DateCST datetime
)
RETURNS DATETIME
AS
BEGIN
    RETURN 
        CASE 
        WHEN @DateCST 
            BETWEEN 
                CASE WHEN @DateCST > '2007-01-01' 
                THEN CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(@DateCST)) + '-MAR-14 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(@DateCST)) + '-MAR-14 02:00' ) + 1
                ELSE CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(@DateCST)) + '-APR-07 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(@DateCST)) + '-APR-07 02:00' ) + 1 END
            AND
                CASE WHEN @DateCST > '2007-01-01' 
                THEN CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(@DateCST)) + '-NOV-07 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(@DateCST)) + '-NOV-07 02:00' ) + 1
                ELSE CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(@DateCST)) + '-OCT-31 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(@DateCST)) + '-OCT-31 02:00' ) + 1 END
        THEN DATEADD(HOUR,4,@DateCST)
        ELSE DATEADD(HOUR,5,@DateCST) 
        END
END

在某人上发布了静态DST日期列表,因此我编写了以下查询,以将该代码的输出与该列表进行比较……到目前为止,它看起来是正确的。

;WITH DT AS 
( 
    SELECT MyDate = GETDATE() 
    UNION ALL 
    SELECT MyDate = DATEADD(YEAR,-1,MyDate) FROM DT
    WHERE DATEADD(YEAR,-1,MyDate) > DATEADD(YEAR, -30, GETDATE())
)
SELECT 
    SpringForward = CASE 
        WHEN MyDate > '2007-01-01' 
        THEN CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(MyDate)) + '-MAR-14 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(MyDate)) + '-MAR-14 02:00' ) + 1
        ELSE CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(MyDate)) + '-APR-07 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(MyDate)) + '-APR-07 02:00' ) + 1 END
,   FallBackward  = CASE 
        WHEN MyDate > '2007-01-01' 
        THEN CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(MyDate)) + '-NOV-07 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(MyDate)) + '-NOV-07 02:00' ) + 1
        ELSE CONVERT(DATETIME, CONVERT(VARCHAR,YEAR(MyDate)) + '-OCT-31 02:00') - DATEPART(DW,CONVERT(VARCHAR,YEAR(MyDate)) + '-OCT-31 02:00' ) + 1 END
FROM DT
ORDER BY 1 DESC
SpringForward      FallBackward
----------------   ----------------
2020-03-08 02:00   2020-11-01 02:00
2019-03-10 02:00   2019-11-03 02:00
2018-03-11 02:00   2018-11-04 02:00
2017-03-12 02:00   2017-11-05 02:00
2016-03-13 02:00   2016-11-06 02:00
2015-03-08 02:00   2015-11-01 02:00
2014-03-09 02:00   2014-11-02 02:00
2013-03-10 02:00   2013-11-03 02:00
2012-03-11 02:00   2012-11-04 02:00
2011-03-13 02:00   2011-11-06 02:00
2010-03-14 02:00   2010-11-07 02:00
2009-03-08 02:00   2009-11-01 02:00
2008-03-09 02:00   2008-11-02 02:00
2007-03-11 02:00   2007-11-04 02:00
2006-04-02 02:00   2006-10-29 02:00
2005-04-03 02:00   2005-10-30 02:00
2004-04-04 02:00   2004-10-31 02:00
2003-04-06 02:00   2003-10-26 02:00
2002-04-07 02:00   2002-10-27 02:00
2001-04-01 02:00   2001-10-28 02:00
2000-04-02 02:00   2000-10-29 02:00
1999-04-04 02:00   1999-10-31 02:00
1998-04-05 02:00   1998-10-25 02:00
1997-04-06 02:00   1997-10-26 02:00
1996-04-07 02:00   1996-10-27 02:00
1995-04-02 02:00   1995-10-29 02:00
1994-04-03 02:00   1994-10-30 02:00
1993-04-04 02:00   1993-10-31 02:00
1992-04-05 02:00   1992-10-25 02:00
1991-04-07 02:00   1991-10-27 02:00

(30 row(s) affected)