将UTC日期时间转换为本地日期时间的最佳方法是什么。 它不像getutcdate()和getdate()那样简单,因为差异会根据日期而变化。

CLR集成对我来说也不是一个选择。

几个月前我提出解决这个问题的解决方案就是有一个夏令时表,存储了接下来100年左右的开始和结束夏令时,这个解决方案似乎不太优雅,但转换很快(简单)表查找)

===============>>#1 票数:27 已采纳

创建两个表,然后加入它们以将存储的GMT日期转换为本地时间:

TimeZones     e.g.
---------     ----
TimeZoneId    19
Name          Eastern (GMT -5)
Offset        -5

创建夏令时表并尽可能多地填充信息(当地法律一直在变化,因此无法预测未来几年的数据)

DaylightSavings
---------------
TimeZoneId    19
BeginDst      3/9/2008 2:00 AM
EndDst        11/2/2008 2:00 AM

像这样加入他们:

inner join  TimeZones       tz on x.TimeZoneId=tz.TimeZoneId
left join   DaylightSavings ds on tz.TimeZoneId=ds.LocalTimeZone 
    and x.TheDateToConvert between ds.BeginDst and ds.EndDst

转换这样的日期:

dateadd(hh, tz.Offset + 
    case when ds.LocalTimeZone is not null 
    then 1 else 0 end, TheDateToConvert)

===============>>#2 票数:15

如果您在美国并且只对从UTC / GMT到固定时区(例如EDT)感兴趣,则此代码应该足够了。 我今天掀起了它,并认为这是正确的,但使用风险自负。

假设您的日期位于“日期”列中,则将计算列添加到表'myTable'。 希望别人觉得这很有用。

ALTER TABLE myTable ADD date_edt AS 
  dateadd(hh, 
        -- The schedule through 2006 in the United States was that DST began on the first Sunday in April 
        -- (April 2, 2006), and changed back to standard time on the last Sunday in October (October 29, 2006). 
        -- The time is adjusted at 02:00 local time.
              CASE WHEN YEAR(date) <= 2006 THEN  
                    CASE WHEN 
                              date >=  '4/' + CAST(abs(8-DATEPART(dw,'4/1/' + CAST(YEAR(date) as varchar)))%7 + 1 as varchar) +  '/' + CAST(YEAR(date) as varchar) + ' 2:00' 
                          AND 
                              date < '10/' + CAST(32-DATEPART(dw,'10/31/' + CAST(YEAR(date) as varchar)) as varchar) +  '/' + CAST(YEAR(date) as varchar) + ' 2:00' 
                    THEN -4 ELSE -5 END
              ELSE
        -- By the Energy Policy Act of 2005, daylight saving time (DST) was extended in the United States in 2007. 
        -- DST starts on the second Sunday of March, which is three weeks earlier than in the past, and it ends on 
        -- the first Sunday of November, one week later than in years past. This change resulted in a new DST period 
        -- that is four weeks (five in years when March has five Sundays) longer than in previous years.[35] In 2008 
        -- daylight saving time ended at 02:00 on Sunday, November 2, and in 2009 it began at 02:00 on Sunday, March 8.[36]
                    CASE WHEN 
                              date >= '3/' + CAST(abs(8-DATEPART(dw,'3/1/' + CAST(YEAR(date) as varchar)))%7 + 8 as varchar) +  '/' + CAST(YEAR(date) as varchar) + ' 2:00' 
                          AND 
                              date < 
                                '11/' + CAST(abs(8-DATEPART(dw,'11/1/' + CAST(YEAR(date) as varchar)))%7 + 1 as varchar) +  '/' + CAST(YEAR(date) as varchar) + ' 2:00' 
                    THEN -4 ELSE -5 END
              END
  ,date)

===============>>#3 票数:7

FOR READ-ONLY使用此(受Bob Albright错误解决方案的启发):

SELECT
  date1, 
  dateadd(hh,
    -- The schedule through 2006 in the United States was that DST began on the first Sunday in April 
    -- (April 2, 2006), and changed back to standard time on the last Sunday in October (October 29, 2006). 
    -- The time is adjusted at 02:00 local time (which, for edt, is 07:00 UTC at the start, and 06:00 GMT at the end).
    CASE WHEN YEAR(date1) <= 2006 THEN
         CASE WHEN 
                  date1 >=  '4/' + CAST((8-DATEPART(dw,'4/1/' + CAST(YEAR(date1) as varchar)))%7 + 1 as varchar) +  '/' + CAST(YEAR(date1) as varchar) + ' 7:00' 
                AND 
                  date1 < '10/' + CAST(32-DATEPART(dw,'10/31/' + CAST(YEAR(date1) as varchar)) as varchar) +  '/' + CAST(YEAR(date1) as varchar) + ' 6:00' 
              THEN -4 ELSE -5 END
    ELSE
        -- By the Energy Policy Act of 2005, daylight saving time (DST) was extended in the United States in 2007. 
        -- DST starts on the second Sunday of March, which is three weeks earlier than in the past, and it ends on 
        -- the first Sunday of November, one week later than in years past. This change resulted in a new DST period 
        -- that is four weeks (five in years when March has five Sundays) longer than in previous years. In 2008 
        -- daylight saving time ended at 02:00 edt (06:00 UTC) on Sunday, November 2, and in 2009 it began at 02:00 edt (07:00 UTC) on Sunday, March 8
        CASE WHEN 
                 date1 >= '3/' + CAST((8-DATEPART(dw,'3/1/' + CAST(YEAR(date1) as varchar)))%7 + 8 as varchar) +  '/' + CAST(YEAR(date1) as varchar) + ' 7:00' 
               AND 
                 date1 < '11/' + CAST((8-DATEPART(dw,'11/1/' + CAST(YEAR(date1) as varchar)))%7 + 1 as varchar) +  '/' + CAST(YEAR(date1) as varchar) + ' 6:00' 
             THEN -4 ELSE -5 END
    END
   , date1) as date1Edt
  from MyTbl

在我尝试编辑Bob Albright的错误答案后,我发布了这个答案 我纠正了时间并删除了多余的abs(),但我的编辑被多次拒绝了。 我尝试解释,但被解雇为一个菜鸟。 他是解决这个问题的绝佳方法! 它让我开始朝着正确的方向前进。 当他只是需要一个小小的调整时,我讨厌创建这个单独的答案,但我试过了¯\\ _(ツ)_ /¯

===============>>#4 票数:7

考虑夏令时的更简单通用的解决方案。 给定“YourDateHere”中的UTC日期:

--Use Minutes ("MI") here instead of hours because sometimes
--  the UTC offset may be half an hour (e.g. 9.5 hours).
SELECT DATEADD(MI,
               DATEDIFF(MI, SYSUTCDATETIME(),SYSDATETIME()),
               YourUtcDateHere)[LocalDateTime]

===============>>#5 票数:5

如果这些问题中的任何一个对您有影响,则不应将本地时间存储在数据库中:

  1. DST表示在回落期间存在“不确定时数”,当地时间无法明确转换。 如果需要确切的日期和时间,则以UTC格式存储。
  2. 如果您希望向用户显示他们自己时区的日期和时间,而不是发生操作的时区,请以UTC格式存储。

===============>>#6 票数:5

Eric Z Beard的回答中 ,以下是SQL

inner join  TimeZones       tz on x.TimeZoneId=tz.TimeZoneId 
left join   DaylightSavings ds on tz.TimeZoneId=ds.LocalTimeZone  
    and x.TheDateToConvert between ds.BeginDst and ds.EndDst 

可能更准确地说是:

inner join  TimeZones       tz on x.TimeZoneId=tz.TimeZoneId 
left join   DaylightSavings ds on tz.TimeZoneId=ds.LocalTimeZone  
    and x.TheDateToConvert >= ds.BeginDst and x.TheDateToConvert < ds.EndDst 

(以上代码未经测试)

原因是sql“between”语句是包含的。 在DST的后端,这将导致2AM时间未转换为1AM。 当然,时间2AM的可能性恰好很小,但它可能发生,并且会导致无效的转换。

===============>>#7 票数:0

我喜欢@Eric Z Beard提供的答案。

但是,为了避免每次都进行连接,这个怎么办?

TimeZoneOffsets
---------------
TimeZoneId    19
Begin         1/4/2008 2:00 AM
End           1/9/2008 2:00 AM
Offset        -5
TimeZoneId    19
Begin         1/9/2008 2:00 AM
End           1/4/2009 2:00 AM
Offset        -6
TimeZoneId    20 --Hong Kong for example - no DST
Begin         1/1/1900
End           31/12/9999
Offset        +8

然后

 Declare @offset INT = (Select IsNull(tz.Offset,0) from YourTable ds
 join   TimeZoneOffsets tz on tz.TimeZoneId=ds.LocalTimeZoneId  
 and x.TheDateToConvert >= ds.Begin and x.TheDateToConvert < ds.End)

终于成了

 dateadd(hh, @offset, TheDateToConvert)

===============>>#8 票数:0

维护一个TimeZone表,或者使用扩展存储过程(xp_cmdshell或COM组件,或您自己的)来shell,并要求操作系统执行此操作。 如果你去xp路线,你可能想要缓存一天的偏移量。

===============>>#9 票数:0

我已经阅读了很多关于这个问题的StackOverflow帖子,并发现了很多方法。 一些“有点”可以。 我还发现了我在我的脚本中尝试使用的MS参考( https://msdn.microsoft.com/en-us/library/mt612795.aspx )。 我已设法达到所需的结果但我不确定这是否会在2005版本上运行。 无论哪种方式,我希望这会有所帮助。

Fnc从系统UTC默认返回PST

CREATE FUNCTION dbo.GetPst()
RETURNS DATETIME
AS 
BEGIN

    RETURN  SYSDATETIMEOFFSET() AT TIME ZONE 'Pacific Standard Time'

END

SELECT dbo.GetPst()

Fnc从提供的时间戳返回PST

CREATE FUNCTION dbo.ConvertUtcToPst(@utcTime DATETIME)
RETURNS DATETIME
AS
BEGIN

    RETURN DATEADD(HOUR, 0 - DATEDIFF(HOUR, CAST(SYSDATETIMEOFFSET() AT TIME ZONE 'Pacific Standard Time' AS DATETIME), SYSDATETIME()), @utcTime)

END


SELECT dbo.ConvertUtcToPst('2016-04-25 22:50:01.900')

===============>>#10 票数:0

我正在使用这个,因为我的所有日​​期都是从现在开始的。

DATEADD(HH,(DATEPART(HOUR, GETUTCDATE())-DATEPART(HOUR, GETDATE()))*-1, GETDATE())

对于历史日期(或处理DST的未来变化,我猜测Bob Albright的解决方案将是最佳选择。

我对我的代码所做的修改是使用目标列:

DATEADD(HH,(DATEPART(HOUR, GETUTCDATE())-DATEPART(HOUR, GETDATE()))*-1, [MySourceColumn])

到目前为止,这似乎有效,但我很高兴收到反馈。

===============>>#11 票数:0

这是我用来制作时区表的代码。 这有点天真,但通常都足够好。

假设:

  1. 它假定仅限美国规则(DST在某些预定义的星期日是2AM,等等)。
  2. 它假设您没有1970年之前的日期
  3. 它假设您知道当地时区偏移(即:EST = -05:00,EDT = -04:00等)

这是SQL:

-- make a table (#dst) of years 1970-2101. Note that DST could change in the future and
-- everything was all custom and jacked before 1970 in the US.
declare @first_year varchar(4) = '1970'
declare @last_year varchar(4) = '2101'

-- make a table of all the years desired
if object_id('tempdb..#years') is not null drop table #years
;with cte as (
    select cast(@first_year as int) as int_year
          ,@first_year as str_year
          ,cast(@first_year + '-01-01' as datetime) as start_of_year
    union all
    select int_year + 1
          ,cast(int_year + 1 as varchar(4))
          ,dateadd(year, 1, start_of_year)
    from cte
    where int_year + 1 <= @last_year
)
select *
into #years
from cte
option (maxrecursion 500);

-- make a staging table of all the important DST dates each year
if object_id('tempdb..#dst_stage') is not null drop table #dst_stage
select dst_date
      ,time_period
      ,int_year
      ,row_number() over (order by dst_date) as ordinal
into #dst_stage
from (
    -- start of year
    select y.start_of_year as dst_date
          ,'start of year' as time_period
          ,int_year
    from #years y

    union all
    select dateadd(year, 1, y.start_of_year)
          ,'start of year' as time_period
          ,int_year
    from #years y
    where y.str_year = @last_year

    -- start of dst
    union all
    select
        case
            when y.int_year >= 2007 then
                -- second sunday in march
                dateadd(day, ((7 - datepart(weekday, y.str_year + '-03-08')) + 1) % 7, y.str_year + '-03-08')
            when y.int_year between 1987 and 2006 then
                -- first sunday in april
                dateadd(day, ((7 - datepart(weekday, y.str_year + '-04-01')) + 1) % 7, y.str_year + '-04-01')
            when y.int_year = 1974 then
                -- special case
                cast('1974-01-06' as datetime)
            when y.int_year = 1975 then
                -- special case
                cast('1975-02-23' as datetime)
            else
                -- last sunday in april
                dateadd(day, ((7 - datepart(weekday, y.str_year + '-04-24')) + 1) % 7, y.str_year + '-04-24')
        end
        ,'start of dst' as time_period
        ,int_year
    from #years y

    -- end of dst
    union all
    select
        case
            when y.int_year >= 2007 then
                -- first sunday in november
                dateadd(day, ((7 - datepart(weekday, y.str_year + '-11-01')) + 1) % 7, y.str_year + '-11-01')
            else
                -- last sunday in october
                dateadd(day, ((7 - datepart(weekday, y.str_year + '-10-25')) + 1) % 7, y.str_year + '-10-25')
        end
        ,'end of dst' as time_period
        ,int_year
    from #years y
) y
order by 1

-- assemble a final table
if object_id('tempdb..#dst') is not null drop table #dst
select a.dst_date +
          case
             when a.time_period = 'start of dst' then ' 03:00'
             when a.time_period = 'end of dst' then ' 02:00'
             else ' 00:00'
          end as start_date
      ,b.dst_date +
          case
             when b.time_period = 'start of dst' then ' 02:00'
             when b.time_period = 'end of dst' then ' 01:00'
             else ' 00:00'
          end as end_date
      ,cast(case when a.time_period = 'start of dst' then 1 else 0 end as bit) as is_dst
      ,cast(0 as bit) as is_ambiguous
      ,cast(0 as bit) as is_invalid
into #dst
from #dst_stage a
join #dst_stage b on a.ordinal + 1 = b.ordinal
union all
select a.dst_date + ' 02:00' as start_date
      ,a.dst_date + ' 03:00' as end_date
      ,cast(1 as bit) as is_dst
      ,cast(0 as bit) as is_ambiguous
      ,cast(1 as bit) as is_invalid
from #dst_stage a
where a.time_period = 'start of dst'
union all
select a.dst_date + ' 01:00' as start_date
      ,a.dst_date + ' 02:00' as end_date
      ,cast(0 as bit) as is_dst
      ,cast(1 as bit) as is_ambiguous
      ,cast(0 as bit) as is_invalid
from #dst_stage a
where a.time_period = 'end of dst'
order by 1

-------------------------------------------------------------------------------

-- Test Eastern
select
    the_date as eastern_local
    ,todatetimeoffset(the_date, case when b.is_dst = 1 then '-04:00' else '-05:00' end) as eastern_local_tz
    ,switchoffset(todatetimeoffset(the_date, case when b.is_dst = 1 then '-04:00' else '-05:00' end), '+00:00') as utc_tz
    --,b.*
from (
    select cast('2015-03-08' as datetime) as the_date
    union all select cast('2015-03-08 02:30' as datetime) as the_date
    union all select cast('2015-03-08 13:00' as datetime) as the_date
    union all select cast('2015-11-01 01:30' as datetime) as the_date
    union all select cast('2015-11-01 03:00' as datetime) as the_date
) a left join
#dst b on b.start_date <= a.the_date and a.the_date < b.end_date

===============>>#12 票数:0

--Adapted Bob Albright and WillDeStijl suggestions for SQL server 2014
--
--In this instance I had no dates prior to 2006, therefore I simplified the case example
--I had to add the variables for the assignment to allow trimming the timestamp from my resultset 

DECLARE @MARCH_DST as DATETIME
SET @MARCH_DST='3/' + CAST((8-DATEPART(dw,'3/1/' + CAST(YEAR(getdate()) as varchar)))%7 + 8 as varchar) +  '/' + CAST(YEAR(getdate()) as varchar) + ' 7:00'

DECLARE @NOV_DST as DATETIME
SET @NOV_DST='11/' + CAST((8-DATEPART(dw,'11/1/' + CAST(YEAR(getdate()) as varchar)))%7 + 1 as varchar) +  '/' + CAST(YEAR(getdate()) as varchar) + ' 6:00'

select cast(dateadd(HOUR,
-- By the Energy Policy Act of 2005, daylight saving time (DST) was extended in the United States in 2007. 
        -- DST starts on the second Sunday of March, which is three weeks earlier than in the past, and it ends on 
        -- the first Sunday of November, one week later than in years past. This change resulted in a new DST period 
        -- that is four weeks (five in years when March has five Sundays) longer than in previous years. In 2008 
        -- daylight saving time ended at 02:00 edt (06:00 UTC) on Sunday, November 2, and in 2009 it began at 02:00 edt (07:00 UTC) on Sunday, March 8
       CASE WHEN
                date1 >=@MARCH_DST
            AND
                date1< @NOV_DST
       THEN -4 ELSE -5 END
       , date1) as DATE) as date1_edited

  ask by Jon Erickson translate from so

未解决问题?本站智能推荐: