web-dev-qa-db-fra.com

Comment créer des fonctions de début et de fin de l'heure d'été dans SQL Server

J'ai besoin de créer une fonction dans le serveur SQL qui renvoie l'heure avancée, l'heure de début et l'heure avancée, la date et l'heure.

Je suis tombé sur quelques exemples sur le Web, mais ils utilisent tous le 1er mars et le 1er novembre et ce n’est pas techniquement correct.

L'heure d'été commence à 02h00 le 2ème dimanche de mars et se termine à 02h00 le premier dimanche de novembre.

J'ai commencé avec le code ci-dessous mais je suis sûr que c'est faux. Toute aide est appréciée! :)

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
11
Amanda Brine

N'oubliez pas que les horaires de l'heure d'été changent en fonction des pays et qu'ils sont susceptibles de changer au fil des ans: le système américain actuel est entré en vigueur en 2007, par exemple.

En supposant que vous souhaitiez utiliser le système actuel pour les États-Unis, voici une forme de réponse pour une année donnée.

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

ou en tant que fonctions, mais vous devez savoir que DateFirst est défini sur 7, sinon le calcul sera désactivé.

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
15
Jamie F

Personnellement, je pense qu'il est plus facile de trouver le premier dimanche de novembre que le deuxième dimanche de mars. Heureusement, si vous en trouvez un, vous pouvez en trouver un autre car il y a toujours 238 jours entre eux. Alors, voici une fonction pratique pour trouver la fin de 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;

Maintenant, le début de Dst est la même fonction, seulement 238 jours plus tôt.

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
4
Brian Stork

SQL Server version 2016 résoudra ce problème une fois pour toutes . Pour les versions antérieures, une solution CLR est probablement la plus simple. Ou, pour une règle DST spécifique (comme US uniquement), une fonction T-SQL peut être relativement simple.

Cependant, je pense qu'une solution T-SQL générique pourrait être possible. Tant que xp_regread fonctionne, essayez ceci:

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

Une fonction T-SQL (complexe) pourrait utiliser ces données pour déterminer le décalage exact pour toutes les dates pendant la règle DST en cours.

3
Michel de Ruiter

Je n’étais vraiment pas satisfait des solutions que j’ai trouvées en ligne pour convertir l’UTC en heure locale; j’ai donc proposé cette fonction. Regardez ma SO réponse ici

Il existe une certaine logique qui calcule si l'heure avancée est active en fonction de la plage de dates standard utilisée par DST (deuxième dimanche de mars à 2 heures du matin, les horloges avancent; le premier dimanche de novembre revient à l'heure standard)

0
Adam Hey