2010-08-04 24 views
16

Desidero eseguire il ciclo su un periodo di tempo in tsql e stampare i dati relativi al dat della utc e la nostra variante locale. Viviamo in UTC +1, quindi potrei aggiungere facilmente 1 ora, ma in estate viviamo in UTC +2.Come calcolare il datetime locale da un datetime utc in tsql (sql 2005)?

In C# posso creare un datetime e utilizzare un metodo per chiedere la variante UTC e viceversa.

Fino ad ora ho questo:

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 

risposta

5

Supponendo che si sta utilizzando SQL 2005 verso l'alto, è possibile sviluppare una funzione SQL CLR per prendere una data UTC e convertire alla data locale.

This link è un MSDN How-To che spiega come è possibile creare una UDF scalare in C#.

Creare una funzione SQL lungo le linee di

[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 
} 

tuo esempio sopra sarebbe poi diventato

set @localdate = dbo.ConvertUtcToLocal(@utcdate) 

SQL CLR ha le sue spese in termini di diffusione, ma mi sento casi come questo sono dove si adatta al meglio.

+0

grazie, andando provare questo, io farò sapere – Michel

+0

capito come convertire UTC a altri fusi orari, AND ha tentato di implementare una funzione SqlCLR. La combinazione di loro però non ha funzionato, perché sto utilizzando l'oggetto TimeZoneInfo per calcolare le datetimes differenza, e non possono fare riferimento l'assembly che classe è in dal mio SqlProject (come sembra si può solo fare riferimento a un sottoinsieme di .NET framework) – Michel

+0

OK - curioso del motivo per cui è necessario la classe TimeZoneInfo, data la necessità di convertire UTC in locale. Se il server SQL è configurato come essere nel tuo avvenire fuso orario locale (accordo - questo è un vincolo), allora il vostro c funzione # diventa qualcosa come 'return new SqlDateTime (utcDate.Value.toLocalTime());' . Non è necessario specificare un fuso orario. Ho capito male? –

1

GETUTCDATE() appena ti dà il tempo corrente in UTC, qualsiasi DATEADD() si fa a questo valore non includerà alcun diurne turni risparmio di tempo.

La cosa migliore è costruire il proprio tabella di conversione UTC o semplicemente usare qualcosa di simile:

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

+1

wow. Spero che qualcuno venga a dirmi che questo non è vero che SQL Server non può farlo? – Michel

+1

SQL Server non può farlo fuori dalla scatola, avrete bisogno di costruire la propria funzione o compilare il proprio look up table –

3

Questa soluzione sembra troppo ovvia.

Se si può ottenere UTC Data con GETUTCDATE() e si può ottenere la data locale con GETDATE() è stato un offset che è possibile applicare per qualsiasi datetime

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

questo dovrebbe restituire l'ora locale che si eseguita la query,

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

questo tornerà 2011-01-14 02: 00: 00.000 perché sono in UTC +5

a meno che non mi manca qualcosa?

+21

Non penso che gestirà gli offset dell'ora legale – Tomas

+6

Non accumularsi gratuitamente, ma non solo non gestisce l'ora legale (o l'ora legale), ma non gestisce i cambiamenti storici in fusi orari o calendari o. –

-1

Recentemente ho dovuto fare la stessa cosa. Il trucco sta nel calcolare l'offset rispetto a UTC, ma non è un trucco difficile. È sufficiente utilizzare DateDiff per ottenere la differenza in ore tra locale e UTC. Ho scritto una funzione per prendermi cura di questo.

Create Function ConvertUtcDateTimeToLocal(@utcDateTime DateTime) 
Returns DateTime 
Begin 
    Declare @utcNow DateTime 
    Declare @localNow DateTime 
    Declare @timeOffSet Int 

    -- Figure out the time difference between UTC and Local time 
    Set @utcNow = GetUtcDate() 
    Set @localNow = GetDate() 
    Set @timeOffSet = DateDiff(hh, @utcNow, @localNow) 

    DECLARE @localTime datetime 

    Set @localTime = DateAdd(hh, @timeOffset, @utcDateTime) 

    -- Check Results 
    return @localTime 

End 
GO 

questo ha sulla cruciale breve venuta: se un fuso orario utilizza un offset frazionaria, come ad esempio il Nepal, che è GMT + 5: 45, questo non riuscirà perché questo solo si occupa di ore intere. Tuttavia, dovrebbe adattarsi perfettamente alle tue esigenze.

+13

Purtroppo questo non riguarda l'ora legale. La differenza tra GetDate() e GetUtcDate() non è costante per tutto l'anno. – Nik

23

Ho aspettato 5 anni per una soluzione più elegante ma dal momento che uno non è emerso, posterò quello che ho usato finora ...

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 

Note:

Questo è per i server del Nord America che osservatore ora legale. Si prega di cambiare il @Offest variabile per l'offset del server che esegue la funzione di SQL (mentre non Osservando l'ora legale) Orario ...

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

Come le regole relative all'ora legale cambia aggiornarli qui ...

--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) 

Cheers,

+2

Grazie mille per aver condiviso questo! – Rachel

+0

Questo ha un paio di bug. Vedi la mia risposta contenente la spiegazione e il codice aggiornato: http://stackoverflow.com/a/36361558/341942 –

2

È possibile utilizzare il mio progetto SQL Server Time Zone Support per la conversione tra fusi orari IANA standard as listed here.

Esempio: risposta

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

di Bobman è vicino, ma ha un paio di bug: 1) È necessario confrontare l'ora legale locale (al posto di tempo standard locale) per l'ora legale Fine DateTime. 2) SQL TRA è compreso in modo si dovrebbe essere a confronto con "> = e <" invece di TRA.

Ecco una versione funzionante modificata insieme ad alcuni casi di test: (Ancora una volta, questo funziona solo per gli Stati Uniti)

-- 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 
1

Ecco una funzione (di nuovo agli Stati Uniti), ma è un po 'più flessibile . Converte una data UTC nell'ora locale del server. Inizia regolando la data dell'appuntamento in base all'offset corrente e quindi regola in base alla differenza dell'offset corrente e dell'offset della data dell'appuntamento.

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 
Problemi correlati