Sql server 在SQL Server中将历史本地时间转换为UTC时间

Sql server 在SQL Server中将历史本地时间转换为UTC时间,sql-server,datetime,utc,Sql Server,Datetime,Utc,我面对的是一个SQL Server数据库,其中包含许多存储在本地时间的DateTime值。(是的,这是不幸的!)我们可能有5-10年的数据,这意味着如果相关区域遵守夏令时,该位置的UTC偏移量将根据一年中的时间而变化,当然,发生变化的时间表也可能会发生变化,例如在美国(这些数据的大部分来源地)追溯到2007年 我的目标是在SQL级别将这些DateTimes转换为UTC时间。除了加载并查询整个时间之外,是否有人有将历史本地时间戳转换为UTC时间的技术?[如果有帮助,方便的话,我们正好有每行的纬度和

我面对的是一个SQL Server数据库,其中包含许多存储在本地时间的
DateTime
值。(是的,这是不幸的!)我们可能有5-10年的数据,这意味着如果相关区域遵守夏令时,该位置的UTC偏移量将根据一年中的时间而变化,当然,发生变化的时间表也可能会发生变化,例如在美国(这些数据的大部分来源地)追溯到2007年

我的目标是在SQL级别将这些
DateTime
s转换为UTC时间。除了加载并查询整个时间之外,是否有人有将历史本地时间戳转换为UTC时间的技术?[如果有帮助,方便的话,我们正好有每行的纬度和经度(可用于识别时区。]


注意:对于实时写入的行,
DATEDIFF(Hour,Getutcdate(),GETDATE())作为UtcOffset
的技巧当然可以很好地工作。问题是将此追溯到夏令时“屏障”两侧发生的日期。

这是基于Chris Barlow在

这是一个SQL Server 2008视图形式的解决方案组件,其中包括用于历史数据转换的夏令时(DST)规则方法

(不需要横向/纵向数据。)

您可以使用此视图创建自定义解决方案引用,以更新可能需要转换的本地表列,如dbo.mytable.created\u date

下面引用了有关使用该视图的一些注释,感兴趣的是“示例用法-用于历史数据转换”一节:


我使用以下方法将本地东部时间转换为UTC(因此函数中的固定值为4和5)。如果您有2007年以前的值,那么您实际上需要修改下面的udf IsInDST以适应这一情况

CREATE FUNCTION [dbo].[udf_ConvertTimeLocalToUTC](@dt DATETIME)
RETURNS DATETIME
AS
BEGIN

    SET @dt = DATEADD(HOUR, CASE WHEN [dbo].udf_IsInDST(@dt) = 1 THEN 4 ELSE 5 END, @dt)
    RETURN @dt
END
GO


CREATE FUNCTION [dbo].[udf_IsInDST](@dt DATETIME)
RETURNS BIT
AS
BEGIN

    DECLARE @returnValue BIT = 0
    DECLARE @mm INT = DATEPART(MONTH, @dt)
    DECLARE @dd INT = DATEPART(DAY, @dt)
    DECLARE @dow INT = DATEPART(dw, @dt)   -- 1 = sun
    DECLARE @hr INT = DATEPART(HOUR, @dt)

    SET @returnValue = 
    CASE WHEN @mm > 3 AND @mm < 11 THEN 1
         WHEN @mm = 3 THEN
            CASE WHEN @dd < 8 THEN 0
                 WHEN @dd >= 8 AND @dd <= 14 THEN (CASE WHEN @dow = 1 THEN (CASE WHEN @hr >= 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN @dd - @dow >= 7 THEN 1 ELSE 0 END) END)
                 ELSE 1
            END

         WHEN @mm = 11 THEN
            CASE WHEN @dd < 7 THEN (CASE WHEN @dow = 1 THEN (CASE WHEN @hr < 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN @dow > @dd THEN 1 ELSE 0 END) END)
                 ELSE 0
            END

         ELSE 0
    END;

    RETURN @returnValue
END
GO
CREATE FUNCTION[dbo]。[udf_ConvertTimeLocalToUTC](@dt DATETIME)
返回日期时间
作为
开始
设置@dt=DATEADD(小时,当[dbo].udf_为indst(@dt)=1,然后4,否则5结束,@dt)
返回@dt
结束
去
创建函数[dbo]。[udf\U IsInDST](@dt DATETIME)
返回位
作为
开始
声明@returnValue位=0
声明@mm INT=DATEPART(月,@dt)
声明@dd INT=DATEPART(日,@dt)
声明@dow INT=DATEPART(dw,@dt)--1=sun
声明@hr INT=DATEPART(小时,@dt)
设置@returnValue=
当@mm>3且@mm<11时,则为1
当@mm=3时,则
当@dd<8时,则为0
当@dd>=8和@dd=2时,则为1 ELSE 0 END)ELSE(当@dd-@dow>=7时,则为1 ELSE 0 END)END)
其他1
结束
当@mm=11时,则
当@dd<7时的情况(当@dow=1时的情况(当@hr<2时的情况,则1否则0结束)其他(当@dow>@dd时的情况,则1否则0结束)结束)
其他0
结束
其他0
结束;
RETURN@returnValue
结束
去

我过去使用过两种方法。 第一个是创建一个.Net CLR,该CLR接受日期时间和时区,并返回与数据一起存储的UTC日期时间值。
第二种解决方案只需要在有限数量的时区中工作,并涉及创建一个由时区ID、日期开始、日期结束以及过去和未来20年的正确UTC偏移组成的表。从那里可以简单地连接和应用正确的偏移。

您可以使用AT时区转换为UTC.SQ我知道夏令时的转换,所以它会解释的。你只需要计算出时区(使用纬度和经度,如你所说)

您可以从这里获取所有时区: 从sys.time\u zone\u info中选择*

因此,解决方案如下: 首先,在表中添加一个带有时区的列(使用纬度和经度来查找)。 然后使用时区更新(新添加的)UTC日期列,例如:

-- some sample data to play with
CREATE TABLE #YourTable
(
    LocalDateTime DATETIME,
    [UtcDateTime] DATETIMEOFFSET,
    TimeZoneName VARCHAR(100)
);

INSERT INTO #YourTable
(
    LocalDateTime,
    TimeZoneName
)
VALUES
('20150101', 'Alaskan Standard Time'),
('20150101', 'US Mountain Standard Time'),
('20190701', 'Alaskan Standard Time'),
('20190701', 'US Mountain Standard Time');


-- convert to UTC
UPDATE #YourTable
SET [UtcDateTime] = LocalDateTime AT TIME ZONE TimeZoneName AT TIME ZONE 'UTC';

-- check results
SELECT * FROM #YourTable;

编写一个与接口的CLR?即使是在2007年之后,印第安纳州的DST地图上到处都是。这个问题可能会很快变得棘手。谢谢!CLR函数当然是一个选项——这很好。对于AskGEO,这需要两个查询:一个是获取奥尔森时区字符串,然后是第二个(事实证明不是在AskGEO)要计算UTC,我将对此进行研究。如果唯一地理位置的数量较少,您可以首先将其汇总到转换表中。手动查找每个位置的DST开始和结束时间,并输入基本的TZ偏移量。然后您可以相当轻松地将转换表应用于批量数据。另外,请注意,一般情况下l UTC偏移量不一定是一整小时。有些地方有UTC偏移量:一些小时30分钟或45分钟。我在这里的答案将帮助您:
CREATE FUNCTION [dbo].[udf_ConvertTimeLocalToUTC](@dt DATETIME)
RETURNS DATETIME
AS
BEGIN

    SET @dt = DATEADD(HOUR, CASE WHEN [dbo].udf_IsInDST(@dt) = 1 THEN 4 ELSE 5 END, @dt)
    RETURN @dt
END
GO


CREATE FUNCTION [dbo].[udf_IsInDST](@dt DATETIME)
RETURNS BIT
AS
BEGIN

    DECLARE @returnValue BIT = 0
    DECLARE @mm INT = DATEPART(MONTH, @dt)
    DECLARE @dd INT = DATEPART(DAY, @dt)
    DECLARE @dow INT = DATEPART(dw, @dt)   -- 1 = sun
    DECLARE @hr INT = DATEPART(HOUR, @dt)

    SET @returnValue = 
    CASE WHEN @mm > 3 AND @mm < 11 THEN 1
         WHEN @mm = 3 THEN
            CASE WHEN @dd < 8 THEN 0
                 WHEN @dd >= 8 AND @dd <= 14 THEN (CASE WHEN @dow = 1 THEN (CASE WHEN @hr >= 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN @dd - @dow >= 7 THEN 1 ELSE 0 END) END)
                 ELSE 1
            END

         WHEN @mm = 11 THEN
            CASE WHEN @dd < 7 THEN (CASE WHEN @dow = 1 THEN (CASE WHEN @hr < 2 THEN 1 ELSE 0 END) ELSE (CASE WHEN @dow > @dd THEN 1 ELSE 0 END) END)
                 ELSE 0
            END

         ELSE 0
    END;

    RETURN @returnValue
END
GO
-- some sample data to play with
CREATE TABLE #YourTable
(
    LocalDateTime DATETIME,
    [UtcDateTime] DATETIMEOFFSET,
    TimeZoneName VARCHAR(100)
);

INSERT INTO #YourTable
(
    LocalDateTime,
    TimeZoneName
)
VALUES
('20150101', 'Alaskan Standard Time'),
('20150101', 'US Mountain Standard Time'),
('20190701', 'Alaskan Standard Time'),
('20190701', 'US Mountain Standard Time');


-- convert to UTC
UPDATE #YourTable
SET [UtcDateTime] = LocalDateTime AT TIME ZONE TimeZoneName AT TIME ZONE 'UTC';

-- check results
SELECT * FROM #YourTable;