在select语句中将UTC的Datetime列转换为本地时间

我正在做一些SQLselect查询,并希望将我的UTCdate时间列转换为本地时间显示为当地时间在我的查询结果。 请注意,我不想通过代码来做这种转换,而是当我正在对我的数据库进行手动和随机SQL查询时。

您可以在SQL Server 2008或更高版本上执行以下操作:

SELECT CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset, MyTable.UtcColumn), DATENAME(TzOffset, SYSDATETIMEOFFSET()))) AS ColumnInLocalTime FROM MyTable 

你也可以做更详细的:

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

无论你做什么, 都不要使用-减去date,因为操作不是primefaces的,并且由于系统date时间和本地date时间之间的竞争条件在不同的时间被检查,你有时会得到不确定的结果(即非自动的)。

请注意,这个答案不考虑DST。 如果你想包括一个DST调整,请参阅下面的SO问题:

如何在SQL Server中创build夏令时开始和结束function

如果您需要转换而不是您的服务器的位置,这是一个function,允许您传递标准偏移量和美国夏令时帐户:

 -- ============================================= -- Author: Ron Smith -- Create date: 2013-10-23 -- Description: Converts UTC to DST -- based on passed Standard offset -- ============================================= CREATE FUNCTION [dbo].[fn_UTC_to_DST] ( @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 = datename(year,@UTC) + '0314' set @SSM = dateadd(hour,2,dateadd(day,datepart(dw,@SSM)*-1+1,@SSM)) set @FSN = datename(year,@UTC) + '1107' set @FSN = dateadd(second,-1,dateadd(hour,2,dateadd(day,datepart(dw,@FSN)*-1+1,@FSN))) -- 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 GO 

如果在你的数据库上启用CLR是一个选项,并使用SQL服务器的时区,它可以用.Net很容易地编写。

 public partial class UserDefinedFunctions { [Microsoft.SqlServer.Server.SqlFunction] public static SqlDateTime fn_GetLocalFromUTC(SqlDateTime UTC) { if (UTC.IsNull) return UTC; return new SqlDateTime(UTC.Value.ToLocalTime()); } } 

UTCdate时间值进入,相对于服务器的本地date时间值出来。 空值返回null。

当有大量的数据时,我发现一个closures函数的方式太慢了。 所以我通过join一个表格函数来做到这一点,它可以计算小时差。 基本上是具有小时偏移的date时间段。 一年将是4行。 所以表function

 dbo.fn_getTimeZoneOffsets('3/1/2007 7:00am', '11/5/2007 9:00am', 'EPT') 

会返回这个表格:

 startTime endTime offset isHr2 3/1/07 7:00 3/11/07 6:59 -5 0 3/11/07 7:00 11/4/07 6:59 -4 0 11/4/07 7:00 11/4/07 7:59 -5 1 11/4/07 8:00 11/5/07 9:00 -5 0 

它确实占了夏令时。 下面是一个如何使用的示例,完整的博客文章就在这里 。

 select mt.startTime as startUTC, dateadd(hh, tzStart.offset, mt.startTime) as startLocal, tzStart.isHr2 from MyTable mt inner join dbo.fn_getTimeZoneOffsets(@startViewUTC, @endViewUTC, @timeZone) tzStart on mt.startTime between tzStart.startTime and tzStart.endTime 

没有简单的方法来以正确和通用的方式来做到这一点。

首先必须明白,偏移取决于所涉及的date,时区和DST。 GetDate()-GetUTCDate只给你今天在服务器的TZ,这是不相关的偏移量。

我看到只有两个工作解决scheme,我有很多search。

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

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

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

这里有一个版本,夏令时,UTC抵消,并没有locking在一个特定的一年。

 --------------------------------------------------------------------------------------------------- --Name: udfToLocalTime.sql --Purpose: To convert UTC to local US time accounting for DST --Author: Patrick Slesicki --Date: 3/25/2014 --Notes: Works on SQL Server 2008R2 and later, maybe SQL Server 2008 as well. -- Good only for US States observing the Energy Policy Act of 2005. -- Function doesn't apply for years prior to 2007. -- Function assumes that the 1st day of the week is Sunday. --Tests: -- SELECT dbo.udfToLocalTime('2014-03-09 9:00', DEFAULT) -- SELECT dbo.udfToLocalTime('2014-03-09 10:00', DEFAULT) -- SELECT dbo.udfToLocalTime('2014-11-02 8:00', DEFAULT) -- SELECT dbo.udfToLocalTime('2014-11-02 9:00', DEFAULT) --------------------------------------------------------------------------------------------------- ALTER FUNCTION udfToLocalTime ( @UtcDateTime AS DATETIME ,@UtcOffset AS INT = -8 --PST ) RETURNS DATETIME AS BEGIN DECLARE @PstDateTime AS DATETIME ,@Year AS CHAR(4) ,@DstStart AS DATETIME ,@DstEnd AS DATETIME ,@Mar1 AS DATETIME ,@Nov1 AS DATETIME ,@MarTime AS TIME ,@NovTime AS TIME ,@Mar1Day AS INT ,@Nov1Day AS INT ,@MarDiff AS INT ,@NovDiff AS INT SELECT @Year = YEAR(@UtcDateTime) ,@MarTime = CONVERT(TIME, DATEADD(HOUR, -@UtcOffset, '1900-01-01 02:00')) ,@NovTime = CONVERT(TIME, DATEADD(HOUR, -@UtcOffset - 1, '1900-01-01 02:00')) ,@Mar1 = CONVERT(CHAR(16), @Year + '-03-01 ' + CONVERT(CHAR(5), @MarTime), 126) ,@Nov1 = CONVERT(CHAR(16), @Year + '-11-01 ' + CONVERT(CHAR(5), @NovTime), 126) ,@Mar1Day = DATEPART(WEEKDAY, @Mar1) ,@Nov1Day = DATEPART(WEEKDAY, @Nov1) --Get number of days between Mar 1 and DST start date IF @Mar1Day = 1 SET @MarDiff = 7 ELSE SET @MarDiff = 15 - @Mar1Day --Get number of days between Nov 1 and DST end date IF @Nov1Day = 1 SET @NovDiff = 0 ELSE SET @NovDiff = 8 - @Nov1Day --Get DST start and end dates SELECT @DstStart = DATEADD(DAY, @MarDiff, @Mar1) ,@DstEnd = DATEADD(DAY, @NovDiff, @Nov1) --Change UTC offset if @UtcDateTime is in DST Range IF @UtcDateTime >= @DstStart AND @UtcDateTime < @DstEnd SET @UtcOffset = @UtcOffset + 1 --Get Conversion SET @PstDateTime = DATEADD(HOUR, @UtcOffset, @UtcDateTime) RETURN @PstDateTime END GO 

使用新的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数据库以UTC运行)以UTC格式存储date时间。 这是我如何处理它。 这不是优雅的,但它很简单,给你正确的答案,无需维护其他表:

 select CONVERT(datetime, SWITCHOFFSET(dateTimeField, DATEPART(TZOFFSET, dateTimeField AT TIME ZONE 'Eastern Standard Time'))) 

罗恩的答案包含一个错误。 它使用当地时间凌晨2:00,需要UTC等效时间。 我没有足够的评价点来评论Ron的回答,所以修正版本如下:

 -- ============================================= -- Author: Ron Smith -- Create date: 2013-10-23 -- Description: Converts UTC to DST -- based on passed Standard offset -- ============================================= CREATE FUNCTION [dbo].[fn_UTC_to_DST] ( @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 = datename(year,@UTC) + '0314' set @SSM = dateadd(hour,2 - @StandardOffset,dateadd(day,datepart(dw,@SSM)*-1+1,@SSM)) set @FSN = datename(year,@UTC) + '1107' set @FSN = dateadd(second,-1,dateadd(hour,2 - (@StandardOffset + 1),dateadd(day,datepart(dw,@FSN)*-1+1,@FSN))) -- 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 
  declare @mydate2 datetime set @mydate2=Getdate() select @mydate2 as mydate, dateadd(minute, datediff(minute,getdate(),@mydate2),getutcdate()) 

这是一个简单的,需要考虑的问题

 CREATE FUNCTION [dbo].[UtcToLocal] ( @p_utcDatetime DATETIME ) RETURNS DATETIME AS BEGIN RETURN DATEADD(MINUTE, DATEDIFF(MINUTE, GETUTCDATE(), @p_utcDatetime), GETDATE()) END 

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

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

请记住,DATEDIFF部分不会总是返回相同的数字。 所以不要用它来比较DateTimes下降到毫秒。

我发现这个函数比使用单独的表或循环的其他解决scheme更快。 这只是一个基本的案例陈述。 考虑到四月到十月之间的所有月份都有一个-4小时的偏移(东部时间),我们只需要在边缘时间再增加几个案例。 否则,偏移量是-5小时。

这是特定于从UTC到东部时间的转换,但可以根据需要添加额外的时区function。

 USE [YourDatabaseName] GO /****** Object: UserDefinedFunction [dbo].[ConvertUTCtoEastern] Script Date: 11/2/2016 5:21:52 PM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE FUNCTION [dbo].[ConvertUTCtoEastern] ( @dtStartDate DATETIME ) RETURNS DATETIME AS BEGIN DECLARE @Working DATETIME DECLARE @Returned DATETIME SET @Working = @dtStartDate SET @Working = case when month(@Working) between 4 and 10 then dateadd(HH,-4,@Working) when @Working between '2017-03-12' and '2017-11-05' then dateadd(HH,-4,@Working) when @Working between '2016-03-13' and '2016-11-06' then dateadd(HH,-4,@Working) when @Working between '2015-03-08' and '2015-11-01' then dateadd(HH,-4,@Working) when @Working between '2014-03-09' and '2014-11-02' then dateadd(HH,-4,@Working) when @Working between '2013-03-10' and '2013-11-03' then dateadd(HH,-4,@Working) when @Working between '2012-03-11' and '2012-11-04' then dateadd(HH,-4,@Working) else dateadd(HH,-5,@Working) end SET @Returned = @Working RETURN @Returned END GO 

这些都没有为我工作,但下面的工作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