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

Jam*_*mes 50 sql-server datetime utc

我最近更新了我的系统,将日期/时间记录为UTC,因为它们以前存储为本地时间.

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

我试图避免编写实用程序的应用程序为我这样做.

有什么建议?

小智 75

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

  • 您可以创建一个 CLR 函数,该函数将使用 C# 来转换考虑夏令时的时区 http://www.jitbit.com/maxblog/17-sql-server-how-to-convert-datetime-to-utc/ (2认同)

Mat*_*int 40

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

SELECT YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time' AT TIME ZONE 'UTC'
Run Code Online (Sandbox Code Playgroud)

或者,这也可以:

SELECT SWITCHOFFSET(YourOriginalDateTime AT TIME ZONE 'Pacific Standard Time', '+00:00')
Run Code Online (Sandbox Code Playgroud)

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

CTP公告中的更多示例.

  • 这种方法确实有一个需要注意的小限制。无法确定 DST 结束时与 UTC 的正确偏移量。考虑“2018-11-04 01:00:00”是从中部标准时间写入的日期时间。01:00:00 小时实际上可以代表与 UTC 的 -05:00 或 -06:00 偏移量,具体取决于它是在 DST 结束之前还是之后写入。如果没有伴随的偏移量,就无法确定。Microsoft SQL Server 的行为是使用 -05:00 偏移量,这可能正确也可能不正确。 (3认同)
  • 为什么首先是太平洋标准时间? (2认同)

Mic*_*per 16

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

您基本上有三种选择:

  1. 在SQL Server外部执行转换并将结果存储在数据库中
  2. 在独立表中引入时区偏移规则,并创建存储过程或UDF以引用规则表以执行转换.您可以在SQL Server Central上找到这种方法的一种方法(需要注册)
  3. 您可以创建SQL CLR UDF; 我将在这里描述这种方法

虽然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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们想在这里做一些改变.首先,我们希望返回一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

在数据库中安装UDF后,您可以看到它的运行情况:

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


Sql*_*yan 14

如果他们都是你的本地人,那么这是抵消:

SELECT GETDATE() AS CurrentTime, GETUTCDATE() AS UTCTime
Run Code Online (Sandbox Code Playgroud)

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

UPDATE SomeTable
   SET DateTimeStamp = DATEADD(hh, DATEDIFF(hh, GETDATE(), GETUTCDATE()), DateTimeStamp)
Run Code Online (Sandbox Code Playgroud)

那会有用吗,还是我错过了这个问题的另一个角度?

  • 没有!差异取决于确切的日期.这取决于夏令时. (45认同)
  • 这不能解释夏令时.请参阅Roderick Llewellyn的以下答案. (11认同)
  • @James:你需要在调用中反转第二个和第三个参数:`DateTimeStamp = DATEADD(hh,DATEDIFF(hh,GETDATE(),GETUTCDATE()),DateTimeStamp)` - 第二个参数是间隔(数字)增加或减少的小时数),第三个是应用该间隔的值 (2认同)

Ben*_*pka 8

这是一个测试过程,将我的数据库从本地升级到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
Run Code Online (Sandbox Code Playgroud)

升级脚本


  1. 在运行此脚本之前进行备份!
  2. 设置@Offset和@ApplyDaylightSavings
  3. 只运行一次!

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
Run Code Online (Sandbox Code Playgroud)


Chr*_*low 5

如果必须将除今天以外的日期转换为不同的时区,则必须处理夏令时。我想要一种无需担心数据库版本,不使用存储函数以及可以轻松移植到Oracle的解决方案。

我认为沃伦(Warren)可以正确选择夏令时,但是要使其在多个时区和国家/地区使用不同的规则(甚至是2006年至2007年间在美国发生变化的规则)中更加有用,上述解决方案。请注意,这不仅有我们的时区,而且还有中欧。中欧遵循4月的最后一个星期日和10月的最后一个星期日。您还会注意到,2006年美国遵循旧的第一个星期日(四月),即10月的最后一个星期日。

该SQL代码可能看起来有些丑陋,但是只需将其复制并粘贴到SQL Server中并进行尝试。请注意,年份,时区和规则共有3个部分。如果要再一年,只需将其添加到年份联盟中即可。相同的另一个时区或规则。

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
Run Code Online (Sandbox Code Playgroud)

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

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

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'
Run Code Online (Sandbox Code Playgroud)