如何在 SQL Server 中创建夏令时开始和结束函数

Posted

技术标签:

【中文标题】如何在 SQL Server 中创建夏令时开始和结束函数【英文标题】:How to create Daylight Savings time Start and End function in SQL Server 【发布时间】:2013-11-13 00:22:57 【问题描述】:

我需要在 SQL Server 中创建一个函数,返回夏令时开始日期时间和夏令时结束日期时间。

我在网上遇到过一些示例,但它们都使用 3 月 1 日和 11 月 1 日,这在技术上并不正确。

夏令时从 3 月第 2 个星期日凌晨 2 点开始,到 11 月第一个星期日凌晨 2 点结束。

我从下面的代码开始,但我确定它是错误的。任何帮助表示赞赏! :)

DECLARE @DSTSTART DATETIME

SELECT @DSTSTART = CASE WHEN 
DATEPART(MONTH, SYSDATETIME()) = 3
AND DATEPART(weekday, SYSDATETIME()) = 1
AND DATEDIFF(week,dateadd(week, datediff(week, 0, dateadd(month, datediff(month, 0, SYSDATETIME()), 0)), 0), SYSDATETIME() - 1) = 2
AND DATEPART(HOUR, SYSDATETIME()) = 2
THEN SYSDATETIME()
END
RETURN (@DSTSTART)
END
GO

【问题讨论】:

我建议您在周日醒来时运行 select getdate() 查询。如果它返回您居住地的正确时间,则可能无需执行任何操作。 谢谢,我要澄清一下……我们数据库中存储的日期是 UTC 时间,并且是在每次交易时捕获的。我们只收集来自美国的数据,但在不同的时区。出于报告目的,我需要将日期转换为 EST 时间。因此,当我运行查询时,我一直在手动计算 est 时间。但是,由于 dst 将于本周日结束,并且我有大量报告要修改,因此我希望创建一个函数,这样我就不必在 3 月份再次这样做了 :) 【参考方案1】:

不要忘记,夏令时时间表因国家/地区而异,并且随着时间的推移也会发生变化:例如,当前的美国系统于 2007 年生效。

假设您想要美国当前的系统,这是任何给定年份的一种答案。

SET DATEFIRST 7

DECLARE @year INT = 2013
DECLARE
    @StartOfMarch DATETIME ,
    @StartOfNovember DATETIME ,
    @DstStart DATETIME ,
    @DstEnd DATETIME



SET @StartOfMarch = DATEADD(MONTH, 2, DATEADD(YEAR, @year - 1900, 0))
SET @StartOfNovember = DATEADD(MONTH, 10, DATEADD(YEAR, @year - 1900, 0));
SET @DstStart = DATEADD(HOUR, 2,
                        DATEADD(day,
                                ( ( 15 - DATEPART(dw, @StartOfMarch) ) % 7 )
                                + 7, @StartOfMarch))
SET @DstEnd = DATEADD(HOUR, 2,
                      DATEADD(day,
                              ( ( 8 - DATEPART(dw, @StartOfNovember) ) % 7 ),
                              @StartOfNovember))


SELECT
    @DstStart AS DstStartInUS ,
    @DstEnd AS DstEndInUS

或作为函数,但您必须知道 DateFirst 设置为 7,否则数学将关闭。

CREATE FUNCTION GetDstStart ( @Year AS INT )
RETURNS DATETIME
AS
    BEGIN

        DECLARE
            @StartOfMarch DATETIME ,
            @DstStart DATETIME 

        SET @StartOfMarch = DATEADD(MONTH, 2,
                                    DATEADD(YEAR, @year - 1900, 0))
        SET @DstStart = DATEADD(HOUR, 2,
                                DATEADD(day,
                                        ( ( 15 - DATEPART(dw,
                                                          @StartOfMarch) )
                                          % 7 ) + 7, @StartOfMarch))
        RETURN @DstStart
    END

GO;


CREATE FUNCTION GetDstEnd ( @Year AS INT )
RETURNS DATETIME
AS
    BEGIN
        DECLARE
            @StartOfNovember DATETIME ,
            @DstEnd DATETIME

        SET @StartOfNovember = DATEADD(MONTH, 10,
                                       DATEADD(YEAR, @year - 1900, 0))
        SET @DstEnd = DATEADD(HOUR, 2,
                              DATEADD(day,
                                      ( ( 8 - DATEPART(dw,
                                                       @StartOfNovember) )
                                        % 7 ), @StartOfNovember))
        RETURN @DstEnd
    END

【讨论】:

谢谢,我想创建函数,因为我使用 Excel 检索数据透视报表的数据,并且由于某种原因在使用 Microsfot Query 函数时无法声明变量。 ..当您提到“您必须知道 DateFirst 设置为 7,否则数学将关闭。”那将在函数中分配到哪里?还是我在以这种方式进行计算时只需要知道一般知识是什么?我以前从未使用过 DATEFIRST 函数。 :) DATEFIRST 是一种 SQL 服务器设置,用于确定服务器认为一周的第一天是什么。 US 和 SQL Server 默认为 7 或星期日,但有时使用 1(星期一)。您不能在函数中更改它,但可以在存储过程中更改。 @@DATEFIRST 将检索该值。 technet.microsoft.com/en-us/library/ms181598.aspx 太棒了,完美运行!我花了一些时间试图理解它背后的数学,我得出的结论是你不需要从 15 中减去星期几 (dw),你可以使用 8,因为你是 %7,对吧?不假装比你聪明或任何事情,只是确保我做对了! TY @Helvio 是的,你完全正确。我不记得 15 对 8 的推理,但结果是一样的。【参考方案2】:

就我个人而言,我认为找到 11 月的第一个星期日比找到 3 月的第二个星期日更容易。幸运的是,如果你找到了一个,你可以找到另一个,因为它们之间总是有 238 天。所以这里有一个方便的函数来查找 Dst 的结尾:

create function GetDstEnd (
                           @Year int
                          )
returns datetime
as
begin

   declare @DstEnd datetime;

   ;with FirstWeekOfNovember
   as (
       select top(7)
              cast(@Year as char(4))
            + '-11-0'
            + cast(row_number() over(order by object_id) as char(1))
            + ' 02:00:00'
              'DST_Stops'
         from sys.columns
      )
   select @DstEnd = DST_Stops
     from FirstWeekOfNovember
    where datepart(weekday,DST_Stops) = 1

   return @DstEnd;

end;

现在 Dst 的开始是相同的功能,只是提前了 238 天。

create function GetDstStart (
                             @Year int
                            )
returns datetime
as
begin;

   declare @DstStart datetime;

   ;with FirstWeekOfNovember
   as (
       select top(7)
              cast(@Year as char(4))
            + '-11-0'
            + cast(row_number() over(order by object_id) as char(1))
            + ' 02:00:00'
              'DST_Stops'
         from sys.columns
      )
   select @DstStart = dateadd(day,-238,DST_Stops)
     from FirstWeekOfNovember
    where datepart(weekday,DST_Stops) = 1

   return @DstStart;

end;
go

【讨论】:

你不应该找到三月并转到十一月吗?从 11 月到 3 月,介绍闰年计算。虽然并不难,但增加的复杂性超出了所需。 我想你误会了,@GoldBishop - 在任何一年中,3 月的第二个星期日和 11 月的第一个星期日之间总是有 238 天。闰年在三月之前,所以无论是28天还是29天都没有关系。我发现得到 11 月的第一个星期日然后减去​​ 238 天比找到 3 月的第二个星期日并加上 238 天更容易。 正是...您的声明 I think it's easier to find the first Sunday in November than it is to find the second Sunday in March...我解释为从 11 月开始并转到 3 月...但随后您使用 ...because there's always 238 days between them... 进行自我纠正,这也令人困惑您不确定从哪个日期开始和结束。虽然assumption 将是 3 月至 11 月将是更大的窗口....我发现最好不要 assume 与 SO 社区;)【参考方案3】:

SQL Server 2016 版将解决此问题once and for all。对于早期版本,CLR 解决方案可能是最简单的。或者对于特定的 DST 规则(仅限美国),T-SQL 函数可以相对简单。

但是,我认为通用的 T-SQL 解决方案可能是可行的。只要xp_regread 有效,试试这个:

CREATE TABLE #tztable (Value varchar(50), Data binary(56));
DECLARE @tzname varchar(150) = 'SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE', @tzname, 'TimeZoneKeyName', @tzname OUT;
SELECT @tzname = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\' + @tzname
INSERT INTO #tztable
EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE', @tzname, 'TZI';
SELECT                                                                                  -- See http://msdn.microsoft.com/ms725481
 CAST(CAST(REVERSE(SUBSTRING(Data,  1, 4)) AS binary(4))      AS int) AS BiasMinutes,   -- UTC = local + bias: > 0 in US, < 0 in Europe!
 CAST(CAST(REVERSE(SUBSTRING(Data,  5, 4)) AS binary(4))      AS int) AS ExtraBias_Std, --   0 for most timezones
 CAST(CAST(REVERSE(SUBSTRING(Data,  9, 4)) AS binary(4))      AS int) AS ExtraBias_DST, -- -60 for most timezones: DST makes UTC 1 hour earlier
 -- When DST ends:
 CAST(CAST(REVERSE(SUBSTRING(Data, 13, 2)) AS binary(2)) AS smallint) AS StdYear,       -- 0 = yearly (else once)
 CAST(CAST(REVERSE(SUBSTRING(Data, 15, 2)) AS binary(2)) AS smallint) AS StdMonth,      -- 0 = no DST
 CAST(CAST(REVERSE(SUBSTRING(Data, 17, 2)) AS binary(2)) AS smallint) AS StdDayOfWeek,  -- 0 = Sunday to 6 = Saturday
 CAST(CAST(REVERSE(SUBSTRING(Data, 19, 2)) AS binary(2)) AS smallint) AS StdWeek,       -- 1 to 4, or 5 = last <DayOfWeek> of <Month>
 CAST(CAST(REVERSE(SUBSTRING(Data, 21, 2)) AS binary(2)) AS smallint) AS StdHour,       -- Local time
 CAST(CAST(REVERSE(SUBSTRING(Data, 23, 2)) AS binary(2)) AS smallint) AS StdMinute,
 CAST(CAST(REVERSE(SUBSTRING(Data, 25, 2)) AS binary(2)) AS smallint) AS StdSecond,
 CAST(CAST(REVERSE(SUBSTRING(Data, 27, 2)) AS binary(2)) AS smallint) AS StdMillisec,
 -- When DST starts:
 CAST(CAST(REVERSE(SUBSTRING(Data, 29, 2)) AS binary(2)) AS smallint) AS DSTYear,       -- See above
 CAST(CAST(REVERSE(SUBSTRING(Data, 31, 2)) AS binary(2)) AS smallint) AS DSTMonth,
 CAST(CAST(REVERSE(SUBSTRING(Data, 33, 2)) AS binary(2)) AS smallint) AS DSTDayOfWeek,
 CAST(CAST(REVERSE(SUBSTRING(Data, 35, 2)) AS binary(2)) AS smallint) AS DSTWeek,
 CAST(CAST(REVERSE(SUBSTRING(Data, 37, 2)) AS binary(2)) AS smallint) AS DSTHour,
 CAST(CAST(REVERSE(SUBSTRING(Data, 39, 2)) AS binary(2)) AS smallint) AS DSTMinute,
 CAST(CAST(REVERSE(SUBSTRING(Data, 41, 2)) AS binary(2)) AS smallint) AS DSTSecond,
 CAST(CAST(REVERSE(SUBSTRING(Data, 43, 2)) AS binary(2)) AS smallint) AS DSTMillisec
FROM #tztable;
DROP TABLE #tztable

一个(复杂的)T-SQL 函数可以使用this data 来确定当前 DST 规则期间所有日期的确切偏移量。

【讨论】:

我尽量远离 CLR...这是在就地升级或 RC 补丁之前验证的另一个“设置”。试探性地,客户推迟了在 SQL 中使用此功能的必要性,但我感觉它会再次弹出“丑陋”的头,在路上。 第一句话!我不能足够支持这个答案!我希望我很久以前就知道at time zone【参考方案4】:

我对在网上找到的将 UTC 转换为当地时间的任何解决方案都不满意,所以我想出了这个功能。 Have a look at my SO answer here

其中有一些逻辑可以根据 DST 使用的标准日期范围计算夏令时是否有效(3 月的第二个星期日凌晨 2 点,时钟向前移动;11 月的第一个星期日恢复到标准时间)

【讨论】:

以上是关于如何在 SQL Server 中创建夏令时开始和结束函数的主要内容,如果未能解决你的问题,请参考以下文章

在 CTE 中创建块 - SQL Server

如何使用 SQL Server Management Studio (2008) 在 SQL Server Compact Edition 中创建列

如何在 SQL Server 中创建维护计划?

如何在 SQL Server 中创建“脚本生成器选项”脚本?

用于查找下一个欧洲夏令时日期的 SQL Server

如何在 SQL Server 2008 表中创建计算列