web-dev-qa-db-fra.com

Comment calculer la date/heure locale à partir d'une date/heure utc dans tsql (sql 2005)?

je veux faire une boucle sur une période de temps dans tsql et imprimer les dates/heures utc et notre variante locale. Nous vivons en UTC +1, je pourrais donc facilement ajouter une heure, mais en été, nous vivons en UTC +2.

En C #, je peux créer une date-heure et utiliser une méthode pour demander la variante UTC et inversement.

Jusqu'à présent, j'ai ceci:

declare @counter int
declare @localdate datetime
declare @utcdate datetime
 set @counter = 0
 while @counter < 100
 begin
   set @counter = @counter + 1
   print 'The counter is ' + cast(@counter as char)
  set @utcdate  = DATEADD(day,@counter,GETUTCDATE())
  --set @localdate = ????

  print  @localdate  
  print @utcdate
 end
16
Michel

En supposant que vous utilisiez SQL 2005, vous pouvez développer une fonction CLR SQL pour prendre une date UTC et la convertir en date locale.

Ce lien est une procédure MSDN expliquant comment créer un fichier UDF scalaire en C #. 

Créez une fonction SQL le long des lignes de

[SqlFunction()]
public static SqlDateTime ConvertUtcToLocal(SqlDateTime utcDate) 
{
    // over to you to convert SqlDateTime to DateTime, specify Kind
    // as UTC, convert to local time, and convert back to SqlDateTime
}

Votre échantillon ci-dessus deviendrait alors 

set @localdate = dbo.ConvertUtcToLocal(@utcdate)

SQL CLR a ses frais généraux en termes de déploiement, mais j'estime que des cas comme celui-ci sont ceux où il convient le mieux. 

5
Neil Moss

J'attendais depuis 5 ans une solution plus élégante, mais comme aucune solution n'est apparue, je publierai ce que j'ai utilisé jusqu'à présent ...

CREATE FUNCTION [dbo].[UDTToLocalTime](@UDT AS DATETIME)  
RETURNS DATETIME
AS
BEGIN 
--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE @Offset AS SMALLINT
SET @Offset = -5

--====================================================
--Figure out the Offset Datetime
--====================================================
DECLARE @LocalDate AS DATETIME
SET @LocalDate = DATEADD(hh, @Offset, @UDT)

--====================================================
--Figure out the DST Offset for the UDT Datetime
--====================================================
DECLARE @DaylightSavingOffset AS SMALLINT
DECLARE @Year as SMALLINT
DECLARE @DSTStartDate AS DATETIME
DECLARE @DSTEndDate AS DATETIME
--Get Year
SET @Year = YEAR(@LocalDate)

--Get First Possible DST StartDay
IF (@Year > 2006) SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE              SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate 
WHILE (DATENAME(dw, @DSTStartDate) <> 'sunday') SET @DSTStartDate = DATEADD(day, 1,@DSTStartDate)


--Get First Possible DST EndDate
IF (@Year > 2006) SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE              SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate 
WHILE (DATENAME(dw, @DSTEndDate) <> 'sunday') SET @DSTEndDate = DATEADD(day,1,@DSTEndDate)

--Get DaylightSavingOffset
SET @DaylightSavingOffset = CASE WHEN @LocalDate BETWEEN @DSTStartDate AND @DSTEndDate THEN 1 ELSE 0 END

--====================================================
--Finally add the DST Offset 
--====================================================
RETURN DATEADD(hh, @DaylightSavingOffset, @LocalDate)
END



GO

Remarques:

Ceci est destiné aux serveurs nord-américains qui observent l'heure avancée. Remplacez la variable @Offest par le décalage du fuseau horaire du serveur exécutant la fonction SQL (sans respecter l'heure avancée) ...

--====================================================
--Set the Timezone Offset (NOT During DST [Daylight Saving Time])
--====================================================
DECLARE @Offset AS SMALLINT
SET @Offset = -5

À mesure que les règles de l'heure d'été changent, mettez-les à jour ici ...

--Get First Possible DST StartDay
IF (@Year > 2006) SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-03-08 02:00:00'
ELSE              SET @DSTStartDate = CAST(@Year AS CHAR(4)) + '-04-01 02:00:00'
--Get DST StartDate 
WHILE (DATENAME(dw, @DSTStartDate) <> 'sunday') SET @DSTStartDate = DATEADD(day, 1,@DSTStartDate)


--Get First Possible DST EndDate
IF (@Year > 2006) SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-11-01 02:00:00'
ELSE              SET @DSTEndDate = CAST(@Year AS CHAR(4)) + '-10-25 02:00:00'
--Get DST EndDate 
WHILE (DATENAME(dw, @DSTEndDate) <> 'sunday') SET @DSTEndDate = DATEADD(day,1,@DSTEndDate)

À votre santé,

23
Bobman

Cette solution semble trop évidente.

Si vous pouvez obtenir la date UTC avec GETUTCDATE () et que vous pouvez obtenir votre date locale avec GETDATE (), vous disposez d'un décalage que vous pouvez appliquer pour n'importe quelle date

SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, GETUTCDATE()) 

cela devrait retourner l'heure locale à laquelle vous avez exécuté la requête,

SELECT DATEADD(hh, DATEPART(hh, GETDATE() - GETUTCDATE()) - 24, N'1/14/2011 7:00:00'  ) 

cela retournera 2011-01-14 02: 00: 00.000 parce que je suis en UTC +5

À moins que je manque quelque chose?

3
KellySandwiches

Vous pouvez utiliser mon Prise en charge du fuseau horaire SQL Server project pour convertir entre les fuseaux horaires standard IANA, comme indiqué ici .

Exemple:

SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'America/Los_Angeles')
2
Matt Johnson

GETUTCDATE () vous indique simplement l'heure actuelle en UTC. Toute modification de DATEADD () avec cette valeur n'inclura pas de décalage de l'heure d'été.

Votre meilleur pari est de construire votre propre table de conversion UTC ou d’utiliser quelque chose comme ceci:

http://www.codeproject.com/KB/database/ConvertUTCToLocal.aspx

1
KM.

Voici une fonction (à nouveau US ONLY) mais elle est un peu plus flexible. Il convertira une date UTC en heure locale du serveur. Il commence par ajuster la date du rendez-vous en fonction du décalage actuel, puis en fonction de la différence entre le décalage actuel et le décalage de la date du rendez-vous.

CREATE FUNCTION [dbo].[fnGetServerTimeFromUTC]
(
    @AppointmentDate AS DATETIME, 
    @DateTimeOffset DATETIMEOFFSET
)
RETURNS DATETIME
AS
BEGIN
    --DECLARE @AppointmentDate DATETIME;
    --SET @AppointmentDate = '2016-12-01 12:00:00'; SELECT @AppointmentDate;

    --Get DateTimeOffset from Server
    --DECLARE @DateTimeOffset; SET @DateTimeOffset = SYSDATETIMEOFFSET();
    DECLARE @DateTimeOffsetStr NVARCHAR(34) = @DateTimeOffset;

    --Set a standard DatePart value for Sunday (server configuration agnostic)
    DECLARE @dp_Sunday INT = 7 - @@DATEFIRST + 1;

    --2006 DST Start First Sunday in April (earliest is 04-01) Ends Last Sunday in October (earliest is 10-25)
    --2007 DST Start Second Sunday March (earliest is 03-08) Ends First Sunday Nov (earliest is 11-01)
    DECLARE @Start2006 NVARCHAR(6) = '04-01-';
    DECLARE @End2006 NVARCHAR(6) = '10-25-';
    DECLARE @Start2007 NVARCHAR(6) = '03-08-';
    DECLARE @End2007 NVARCHAR(6) = '11-01-';

    DECLARE @ServerDST SMALLINT = 0;
    DECLARE @ApptDST SMALLINT = 0;
    DECLARE @Start DATETIME;
    DECLARE @End DATETIME;

    DECLARE @CurrentMinuteOffset INT; 

    DECLARE @str_Year NVARCHAR(4) = LEFT(@DateTimeOffsetStr,4);
    DECLARE @Year INT = CONVERT(INT, @str_Year);

    SET @CurrentMinuteOffset = CONVERT(INT, SUBSTRING(@DateTimeOffsetStr,29,3)) * 60 + CONVERT(INT, SUBSTRING(@DateTimeOffsetStr,33,2)); --Hours + Minutes

    --Determine DST Range for Server Offset
    SET @Start = CASE 
        WHEN @Year <= 2006 THEN CONVERT(DATETIME, @Start2006 + @str_Year + ' 02:00:00')
        ELSE CONVERT(DATETIME, @Start2007 + @str_Year + ' 02:00:00')
        END;
    WHILE @dp_Sunday <> DATEPART(WEEKDAY, @Start) BEGIN
        SET @Start = DATEADD(DAY, 1, @Start)
    END;

    SET @End = CASE 
        WHEN @Year <= 2006 THEN CONVERT(DATETIME, @End2006 + @str_Year + ' 02:00:00')
        ELSE CONVERT(DATETIME, @End2007 + @str_Year + ' 02:00:00')
        END;
    WHILE @dp_Sunday <> DATEPART(WEEKDAY, @End) BEGIN
        SET @End = DATEADD(DAY, 1, @End)
    END;

    --Determine Current Offset based on Year
    IF @DateTimeOffset >= @Start AND @DateTimeOffset < @End SET @ServerDST = 1;

    --Determine DST status of Appointment Date
    SET @Year = YEAR(@AppointmentDate);

    SET @Start = CASE 
        WHEN @Year <= 2006 THEN CONVERT(DATETIME, @Start2006 + @str_Year + ' 02:00:00')
        ELSE CONVERT(DATETIME, @Start2007 + @str_Year + ' 02:00:00')
        END;
    WHILE @dp_Sunday <> DATEPART(WEEKDAY, @Start) BEGIN
        SET @Start = DATEADD(DAY, 1, @Start)
    END;

    SET @End = CASE 
        WHEN @Year <= 2006 THEN CONVERT(DATETIME, @End2006 + @str_Year + ' 02:00:00')
        ELSE CONVERT(DATETIME, @End2007 + @str_Year + ' 02:00:00')
        END;
    WHILE @dp_Sunday <> DATEPART(WEEKDAY, @End) BEGIN
        SET @End = DATEADD(DAY, 1, @End)
    END;

    --Determine Appointment Offset based on Year
    IF @AppointmentDate >= @Start AND @AppointmentDate < @End SET @ApptDST = 1;

    SET @AppointmentDate = DATEADD(MINUTE, @CurrentMinuteOffset + 60 * (@ApptDST - @ServerDST), @AppointmentDate)

    RETURN @AppointmentDate
END
GO
1
Ztrauq

Bien que le titre de la question mentionne SQL Server 2005, la question est généralement associée à SQL Server. Pour SQL Server 2016 et versions ultérieures, vous pouvez utiliser:

SELECT yourUtcDateTime AT TIME ZONE 'Mountain Standard Time'

Une liste de fuseaux horaires est disponible avec SELECT * FROM sys.time_zone_info

0
Charles Burns

La réponse de Bobman est proche, mais présente quelques bogues: 1) Vous devez comparer l'heure locale DAYLIGHT (au lieu de l'heure locale STANDARD) à l'heure avancée de l'heure avancée. vous devriez comparer en utilisant "> = et <" au lieu de BETWEEN.

Voici une version modifiée qui fonctionne avec quelques cas de test: (Encore une fois, cela ne fonctionne que pour les États-Unis)

-- Test cases:
-- select dbo.fn_utc_to_est_date('2016-03-13 06:59:00.000') -- -> 2016-03-13 01:59:00.000 (Eastern Standard Time)
-- select dbo.fn_utc_to_est_date('2016-03-13 07:00:00.000') -- -> 2016-03-13 03:00:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 05:59:00.000') -- -> 2016-11-06 01:59:00.000 (Eastern Daylight Time)
-- select dbo.fn_utc_to_est_date('2016-11-06 06:00:00.000') -- -> 2016-11-06 01:00:00.000 (Eastern Standard Time)
CREATE FUNCTION [dbo].[fn_utc_to_est_date]
(
    @utc datetime
)
RETURNS datetime
as
begin
    -- set offset in standard time (WITHOUT daylight saving time)
    declare @offset smallint
    set @offset = -5  --EST

    declare @localStandardTime datetime
    SET @localStandardTime = dateadd(hh, @offset, @utc)

    -- DST in USA starts on the second sunday of march and ends on the first sunday of november.
    -- DST was extended beginning in 2007:
    --   https://en.wikipedia.org/wiki/Daylight_saving_time_in_the_United_States#Second_extension_.282005.29
    -- If laws/rules change, obviously the below code needs to be updated.

    declare @dstStartDate datetime,
            @dstEndDate datetime,
            @year int
    set @year = datepart(year, @localStandardTime)

    -- get the first possible DST start day
    if (@year > 2006) set @dstStartDate = cast(@year as char(4)) + '-03-08 02:00:00'
    else              set @dstStartDate = cast(@year as char(4)) + '-04-01 02:00:00'
    while ((datepart(weekday,@dstStartDate) != 1)) begin --while not sunday
        set @dstStartDate = dateadd(day, 1, @dstStartDate)
    end

    -- get the first possible DST end day
    if (@year > 2006) set @dstEndDate = cast(@year as char(4)) + '-11-01 02:00:00'
    else              set @dstEndDate = cast(@year as char(4)) + '-10-25 02:00:00'
    while ((datepart(weekday,@dstEndDate) != 1)) begin --while not sunday
        set @dstEndDate = dateadd(day, 1, @dstEndDate)
    end

    declare @localTimeFinal datetime,
            @localTimeCompare datetime
    -- if local date is same day as @dstEndDate day,
    -- we must compare the local DAYLIGHT time to the @dstEndDate (otherwise we compare using local STANDARD time).
    -- See: http://www.timeanddate.com/time/change/usa?year=2016
    if (datepart(month,@localStandardTime) = datepart(month,@dstEndDate)
            and datepart(day,@localStandardTime) = datepart(day,@dstEndDate)) begin
        set @localTimeCompare = dateadd(hour, 1, @localStandardTime)
    end
    else begin
        set @localTimeCompare = @localStandardTime
    end

    set @localTimeFinal = @localStandardTime

    -- check for DST
    if (@localTimeCompare >= @dstStartDate and @localTimeCompare < @dstEndDate) begin
        set @localTimeFinal = dateadd(hour, 1, @localTimeFinal)
    end

    return @localTimeFinal
end
0
Gordon Glas

Pour ceux qui sont bloqués dans SQL Server 2005 et qui ne veulent pas ou ne peuvent pas utiliser un fichier UDF - et le font particulièrement à l'extérieur des États-Unis - j'ai adopté l'approche de Bobman et l'ont généralisée. Ce qui suit fonctionnera aux États-Unis, en Europe, en Nouvelle-Zélande et en Australie, à la condition que tous les États australiens ne respectent pas l'heure d'été, même les États qui se trouvent dans le même fuseau horaire "de base". Il est également facile d'ajouter des règles DST qui ne sont pas encore prises en charge. Ajoutez simplement une ligne aux valeurs @calculation.

-- =============================================
-- Author:      Herman Scheele
-- Create date: 20-08-2016
-- Description: Convert UTC datetime to local datetime
--              based on server time-distance from utc.
-- =============================================
create function dbo.UTCToLocalDatetime(@UTCDatetime datetime)
returns datetime as begin
    declare @LocalDatetime datetime, @DSTstart datetime, @DSTend datetime

    declare @calculation table (
        frm smallint,
        til smallint,
        since smallint,
        firstPossibleStart datetime,-- Put both of these with local non-DST time!
          firstPossibleEnd datetime -- (In Europe we turn back the clock from 3 AM to 2 AM, which means it happens 2 AM non-DST time)
    )

    insert into @calculation
    values
        (-9, -2, 1967, '1900-04-24 02:00', '1900-10-25 01:00'), -- USA first DST implementation
        (-9, -2, 1987, '1900-04-01 02:00', '1900-10-25 01:00'), -- USA first DST extension
        (-9, -2, 2007, '1900-03-08 02:00', '1900-11-01 01:00'), -- USA second DST extension
        (-1,  3, 1900, '1900-03-25 02:00', '1900-10-25 02:00'), -- Europe
        (9.5,11, 1971, '1900-10-01 02:00', '1900-04-01 02:00'), -- Australia (not all Aus states in this time-zone have DST though)
        (12, 13, 1974, '1900-09-24 02:00', '1900-04-01 02:00')  -- New Zealand

    select top 1    -- Determine if it is DST /right here, right now/ (regardless of input datetime)
        @DSTstart = dateadd(year, datepart(year, getdate())-1900, firstPossibleStart),          -- Grab first possible Start and End of DST period
        @DSTend   = dateadd(year, datepart(year, getdate())-1900, firstPossibleEnd),            
        @DSTstart = dateadd(day, 6 - (datepart(dw, @DSTstart) + @@datefirst - 2) % 7, @DSTstart),-- Shift Start and End of DST to first sunday
        @DSTend   = dateadd(day, 6 - (datepart(dw, @DSTend) + @@datefirst - 2) % 7, @DSTend),
        @LocalDatetime = dateadd(hour, datediff(hour, getutcdate(), getdate()), @UTCDatetime),  -- Add hours to current input datetime (including possible DST hour)
        @LocalDatetime = case
                when frm < til and getdate() >= @DSTstart and getdate() < @DSTend               -- If it is currently DST then we just erroneously added an hour above,
                  or frm > til and (getdate() >= @DSTstart or getdate() < @DSTend)              -- substract 1 hour to get input datetime in current non-DST timezone,
                    then dateadd(hour, -1, @LocalDatetime)                                      -- regardless of whether it is DST on the date of the input datetime
                else @LocalDatetime
            end
    from @calculation
    where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
      and datepart(year, getdate()) >= since
    order by since desc

    select top 1    -- Determine if it was/will be DST on the date of the input datetime in a similar fashion
        @DSTstart = dateadd(year, datepart(year, @LocalDatetime)-1900, firstPossibleStart),
        @DSTend   = dateadd(year, datepart(year, @LocalDatetime)-1900, firstPossibleEnd),
        @DSTstart = dateadd(day, 6 - (datepart(dw, @DSTstart) + @@datefirst - 2) % 7, @DSTstart),
        @DSTend   = dateadd(day, 6 - (datepart(dw, @DSTend) + @@datefirst - 2) % 7, @DSTend),
        @LocalDatetime = case
                when frm < til and @LocalDatetime >= @DSTstart and @LocalDatetime < @DSTend     -- If it would be DST on the date of the input datetime,
                  or frm > til and (@LocalDatetime >= @DSTstart or @LocalDatetime < @DSTend)    -- add this hour to the input datetime.
                    then dateadd(hour, 1, @LocalDatetime)
                else @LocalDatetime
            end
    from @calculation
    where datediff(minute, getutcdate(), getdate()) between frm * 60 and til * 60
      and datepart(year, @LocalDatetime) >= since
    order by since desc

    return @LocalDatetime
end

Cette fonction examine la différence entre l'heure locale et l'heure utc au moment où elle s'exécute pour déterminer les règles DST à appliquer. Il sait ensuite si l'opération datediff(hour, getutcdate(), getdate()) inclut ou non une heure DST et la soustrait si elle le faisait. Ensuite, il détermine si était ou sera DST à la date de la date et heure UTC d'entrée et, le cas échéant, ajoute l'heure DST en arrière.

Cela vient avec une bizarrerie, qui est que pendant la dernière heure de l'heure d'été et la première heure de non-heure d'été, la fonction n'a aucun moyen de déterminer ce que c'est et suppose cette dernière. Donc, indépendamment de input-datetime, si ce code s'exécute pendant la dernière heure de l'heure d'été, le résultat obtenu est incorrect . Ce qui signifie que cela fonctionne 99,9886% du temps.

0
funkwurm