SQL Server – 将date字段转换为UTC

我最近更新了我的系统,将UTC的date/时间logging为之前存储的当地时间。

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

我试图避免必须编写一个实用程序来为我做这个。

有什么build议么?

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

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

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

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

这样做还是我错过了这个问题的另一个angular度?

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

在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') 

这两种方法都将解释太平洋时间的input,正确地考虑DST是否有效,然后转换为UTC。 结果将是一个零偏移的datetimeoffset

更多CTP公告的例子

如前所述,在SQL Server中不存在执行时区规则感知date转换的内置方法(至less从SQL Server 2012开始)。

你基本上有三个select来做到这一点:

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

尽pipeSQL Server不提供执行时区规则感知date转换的工具,但是.NET框架可以,只要您可以使用SQL CLR,就可以利用这一点。

在Visual Studio 2012中,确保安装了数据工具(否则,SQL Server项目将不会显示为选项),并创build一个新的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中使用内置的发布工具。 右键单击数据库项目并select“发布”。 设置数据库连接和名称,然后单击“发布”将代码推送到数据库中,或者如果想要为后代存储脚本(或将代码投入生产),请单击“生成脚本”。

一旦在数据库中有了UDF,就可以看到它的实际运行情况:

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

这是一个testing程序,我的数据库从本地升级到utc。 升级数据库所需的唯一input是input本地时间从utc时间偏移到@Offset所需的分钟数,如果通过设置@ApplyDaylightSavings将时区调整为夏令时调整。

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

支持数据库function


 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&@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 

如果您不得不将今天以外的date转换为不同的时区,则必须处理夏令时。 我想要一个解决scheme,可以不用担心数据库版本,不使用存储的函数和一些可以轻松移植到Oracle的东西。

我认为沃伦正处于正确的轨道上,为了获得正确的白天时间,但是为了使它对于多个时区和不同国家的不同规则更有用,甚至在2006年到2007年之间改变了美国的规则,这里有一个变化上面的解决scheme。 请注意,这不仅有我们的时区,而且还有中欧。 中欧沿着4月的最后一个星期日和10月的最后一个星期天。 你也会注意到,2006年的美国是在十月份的最后一个星期天,在四月份的旧的第一个星期天。

这个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 

对于规则,请在第一,二,三或最后一个星期日使用1,2,3或L. date部分给出月份,并根据规则,月份的第一天或规则typesL的月份的最后一天

我把上面的查询放入一个视图。 现在,任何时候我想要一个date的时区偏移或转换为UTC时间,我只是join到这个视图,并select以date格式获取date。 而不是date时间,我将它们转换为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' 

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

 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 

以下内容应该能够计算出您正在运行的服务器的DATE和UTCDATE之间的差异,并使用该偏移量来计算您传递给它的任何date的UTC等值。 在我的例子中,我尝试在UTC时间为-630分钟的澳大利亚阿德莱德为“1-nov-2012 06:00”转换UTC时间,当它被添加到任何date时将导致UTC等同于任何当地date。

selectDATEADD(MINUTE,DATEDIFF(MINUTE,GETDATE(),GETUTCDATE()),'1-nov-2012 06:00')

根据您需要走多远,您可以build立一个夏令时表,然后join表格并做一个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 

除非我错过了上面的(可能的),否则上述所有方法都是有缺陷的,因为它们在从夏令时(比如说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 

基于date和时间的简单小时偏移不会削减它。 如果您不知道当地时间是在01:00到01:59之间logging在EDT还是EST,那么您不会有任何线索! 我们以01:30为例:如果您在01:31到01:59之间find后面的时间,您将不知道01:30您正在看的是[3还是[6。 在这种情况下,你可以通过一些编码来获得正确的UTC时间,查看以前的条目(在SQL中不好玩),这是最好的例子。

假设您已经logging了以下当地时间,并没有花一点时间来指示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时间!

我们可以将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