【问题标题】:Need a SQL Server function to convert local datetime to UTC that supports DST需要 SQL Server 函数将本地日期时间转换为支持 DST 的 UTC
【发布时间】:2014-02-24 19:50:51
【问题描述】:

我们正在将应用程序迁移到 Azure,但目前我们所有的日期都存储为东部标准时间。由于 SQL Azure 使用 UTC 并且我们的许多日期都是通过 getdate() 调用生成的,如果我们保持原样,我们将会遇到问题。似乎最不让人头疼的选项是将我们所有存储的日期转换为 UTC,并在前端将它们转换为本地时间。

不幸的是,似乎没有一种内置方法可以将 SQL 日期时间从一个时区转换为另一个时区,而且我发现的许多解决方案都没有考虑夏令时。我发现考虑 DST 的解决方案涉及导入日历和时区表,并且非常复杂。

我只是在寻找适合单次转换的东西,而且它不必很漂亮。有什么想法吗?

更新:我编写了以下函数,我相信它会完成我所需要的。有人看到这个有什么漏洞吗?我所有的日期目前都是东部标准时间,但我的日期是从 2002 年左右开始的,所以我不得不在 2007 年处理法律变更。

我想我会像这样使用这个函数:

UPDATE myTable SET myDate = dbo.fnESTtoUTC(myDate)

函数如下:

CREATE FUNCTION [dbo].[fnESTtoUTC]
    (@pESTDate  DATETIME)
    RETURNS DATETIME
AS
BEGIN

DECLARE @TimeZoneOffset INT
DECLARE @Year INT
DECLARE @Day INT
DECLARE @DSTStart DATETIME
DECLARE @DSTEnd DATETIME

SELECT @Year = DATEPART(year, @pESTDate)

IF @Year >= 2007
BEGIN
    SELECT @Day = 8
    WHILE @DSTStart IS NULL
    BEGIN
        -- Second Sunday in March
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 3, @Day)) = 1
            SELECT @DSTStart = DATETIMEFROMPARTS(@Year, 3, @Day, 2, 0, 0, 0)
        SELECT @Day = @Day + 1
    END

    SELECT @Day = 1
    WHILE @DSTEnd IS NULL
    BEGIN
        -- First Sunday in November
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 11, @Day)) = 1
            SELECT @DSTEnd = DATETIMEFROMPARTS(@Year, 11, @Day, 1, 0, 0, 0)
        SELECT @Day = @Day + 1
    END
END
ELSE
BEGIN
    SELECT @Day = 1
    WHILE @DSTStart IS NULL
    BEGIN
        -- First Sunday in April
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 4, @Day)) = 1
            SELECT @DSTStart = DATETIMEFROMPARTS(@Year, 4, @Day, 2, 0, 0, 0)
        SELECT @Day = @Day + 1
    END

    SELECT @Day = 31
    WHILE @DSTEnd IS NULL
    BEGIN
        -- Last Sunday in October
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 10, @Day)) = 1
            SELECT @DSTEnd = DATETIMEFROMPARTS(@Year, 10, @Day, 1, 0, 0, 0)
        SELECT @Day = @Day - 1
    END
END

IF @pESTDate >= @DSTStart AND @pESTDate < @DSTEnd
BEGIN
    -- Date is in DST
    SELECT @TimeZoneOffset = 4
END
ELSE
BEGIN
    -- Not DST
    SELECT @TimeZoneOffset = 5
END

RETURN ( dateadd(hh, @TimeZoneOffset, @pESTDate) )
END

【问题讨论】:

  • 涉及表格的原因是因为时区本身的定义会随着时间而变化,因此不可能可靠地更改时区之间的日期(地理也很重要,例如,就在几年前,印第安纳州没有'不支持 DST,但现在支持)。您将如何编写准确转换 1996 年 11 月 20 日 2013 年 11 月 20 日的“单次转换”?我不知道,但我认为其中一个在 DST 中,另一个不在。不要害怕额外的桌子。我已经做到了这一点,并且在有限的 # 个时区中运行良好。
  • 这不是对您问题的回答,而是一个建议。将您的设计拆分为 n 层/MVC。所以数据、业务、用户界面。现在,在您的数据层中,以 UTC 格式存储日期/时间以及源日期/时间的时区指示符。这意味着您的所有日期/时间都已标准化。现在,在业务层中,对所需的标准化“本地”日期/时间(再次基于所需的 TZ)进行必要的对话,以在您的 UI 层中显示。恕我直言,不要使用 SQL 作为您的业务引擎 - 在您的业务层中完成大部分工作。我希望这会有所帮助。
  • @robnick 但仍然需要注意时区当时是否处于 DST 中。表示层可以判断 today 是否在 DST 中,但不能判断某个任意日期(不要只考虑过去的日期,也要考虑未来)。我完全同意日期时间应存储为 UTC(实际上将我所有的服务器都设置为 UTC),但即使您将 TZ + DST 信息与日期一起存储,对于未来的日期,您也需要能够在某些时间进行调整总统决定再次更改 DST 的范围(或完全取消它)。
  • 没有很多软件能正确处理这个问题是有原因的——这是一个很难解决的问题。在 Outlook 中安排从现在起 16 个月后的会议,并将其发送给遵守和不遵守 DST 的时区的人,看看他们是否都希望同时出现(不要忘记包括来自纽芬兰位于其中一个有趣的时区:30 个时区)。甚至 SQL Server 也添加了DATETIMEOFFSET,但你猜怎么着?它不支持 DST。
  • @AaronBertrand 感谢您的回复和建议。幸运的是,我所有的日期都来自一个时区,所以对于一次性转换,我认为我不必考虑所有地区的所有可能性。我用我拼凑的快速功能更新了我的问题。您认为它不起作用的任何原因吗?

标签: sql-server timezone azure-sql-database utc dst


【解决方案1】:

有几个问题:

  • 您将根据当前日期使用哪个规则,而不是根据输入中提供的日期。你肯定应该改变它。

  • 如果我传递诸如 2013-03-10 02:30 之类的值,您的函数将假定它是 EDT,但实际上该时间无效并且不应该存在于您的数据中。您可能应该提出一个错误。

  • 如果我传递一个值,例如 2013-11-03 01:30,您的函数将假定它是 EDT,但实际上它可能在 EDT 或 EST .您需要存储偏移量或 dst 标志来消除歧义。如果它不在数据中,你别无选择,只能假设其中之一。

  • 此函数不考虑 1987 年之前的日期,该日期在美国也发生了 DST 规则更改。如果您有在那之前的数据,您也应该考虑到这一点。

除此之外,它看起来还不错。尽管如此,cmets 中的点还是正确的。这仅适用于这个时区,并且您无法保证该时区的规则将来不会改变。我建议您使用 convert your data 来使用 UTC 。如果您愿意,可以使用此函数进行转换,也可以在应用程序级代码中轻松完成。

哦,还有一件事。 “东部标准时间”或“EST”字面意思是 UTC-5,根本不考虑夏令时。就像“东部夏令时间”或“EDT”总是意味着 UTC-4。我假设您的意思是在您的问题中说您的数据在“东部时间”,这两者都适用。

如果您实际上指的是 EST,那么您的工作会简单得多 - 只需增加 5 小时并完成。我提出这一点是因为确实存在不考虑夏令时记录数据的情况。 (我相信金融领域的一些用例就是这样工作的。)

【讨论】:

  • 感谢马特的回复,感谢您的反馈。很好地抓住了当前日期错误(我在我的问题中纠正了这一点)。这个功能不会成为我们日常使用的东西。我只是在寻找一种方法,在我们切换到 Azure 时将我当前存储的日期转换为 UTC。在运行此程序的同时,将生成永久代码以最初以 UTC 格式保存所有日期。
【解决方案2】:

我刚刚创建了自己的,将来需要一个日期时间变量。它遵循以下 NIST 规则 但是,我的要求是将其作为文本返回,而不是日期时间,并且我必须使用 MS SQL 2005。随意调整。 :)

基于www.nist.gov的规则

  • 3 月的第二个星期日 @ 02:00 开始
  • 11 月的第一个星期日 @ 02:00 结束

我的时区:西海岸 (-8)

CREATE FUNCTION [dbo].[Local_Time]
(
    @dt     datetime    --  In UTC
)
RETURNS varchar(40)
AS
BEGIN

declare @local as datetime
declare @dw as int
declare @startDLS as datetime
declare @stopDLS as datetime
declare @tz as int
set @tz = -8        -- Time Zone - AKA sunny (and expensive) California

set @startDLS = 0  -- init
set @startDLS = cast (dateadd (yy, datepart(yy,@dt)-1900, @startDLS) as datetime)       --  Current Year
set @stopDLS = @startDLS                                                            --     Current Year
set @startDLS = cast (dateadd (mm, 2, @startDLS) as datetime)   -- Make date March 1st
set @stopDLS  = cast (dateadd (mm, 10, @stopDLS) as datetime)   -- Make date November 1st
set @dw = datepart(dw, @startDLS)

IF @dw = 1  
    BEGIN
        set @startDLS = cast (dateadd (dd, 7 , @startDLS) as datetime)
    END
ELSE 
    BEGIN
        set @startDLS = cast (dateadd (dd, 15-@dw, @startDLS) as datetime)
    END
SET @startDLS = cast (dateadd (hh, 2, @startDLS) as datetime)

IF @dw = 1 
    BEGIN
        set @stopDLS = cast (dateadd (dd, 7 , @stopDLS) as datetime)
    END
ELSE 
    BEGIN
        set @stopDLS = cast (dateadd (dd, 8-@dw, @stopDLS) as datetime)
    END
SET @stopDLS = cast (dateadd (hh, 2, @stopDLS) as datetime)

IF (@dt >= @startDLS ) and 
    (@dt < @stopDLS )
    BEGIN  --  @dt is within Daylight Savings Time Rules - Add an hour
        set @local = dateadd(hh, -7, @dt)
    END
ELSE    --  @dt is outside Daylight Savings Time Rules - No Adjustment
    BEGIN
        set @local = dateadd(hh, -8, @dt)
    END
return cast(@local as varchar(40) )

END

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-30
    • 2011-08-29
    • 2012-10-07
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多