2010-08-04 65 views
16

quiero recorrer un período de tiempo en tsql e imprimir las fechas de tiempo utc y nuestra variante local. Vivimos en UTC +1, por lo que podría agregar fácilmente 1 hora, pero en el verano vivimos en UTC +2.Cómo calcular la hora local desde una fecha y hora utc en tsql (sql 2005)?

En C# puedo crear una fecha y utilizar un método para solicitar la variante UTC y viceversa.

Hasta ahora tengo esto:

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 

Respuesta

5

Suponiendo que está utilizando SQL 2005 hacia arriba, puede desarrollar una función de SQL CLR para tomar una fecha UTC y convierte a la fecha local.

This link es un MSDN How-To que explica cómo puede crear una UDF escalar en C#.

Crear una función de SQL en la línea 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 
} 

Su ejemplo anterior se convertiría entonces en

set @localdate = dbo.ConvertUtcToLocal(@utcdate) 

SQL CLR tiene sus gastos generales en términos de despliegue, pero me siento casos como este son donde encaja mejor.

+0

gracias, voy a intentar eso, le haré saber – Michel

+0

descubrió cómo convertir utc a otras zonas horarias, Y traté de implementar una función SqlCLR. Combinarlos sin embargo no funcionó, porque estoy usando el objeto TimeZoneInfo para calcular las fechas de diferencia, y no puedo hacer referencia al ensamblaje de esa clase desde mi SqlProject (ya que parece que solo se puede hacer referencia a un subconjunto de .net framework) – Michel

+0

OK - curiosidad sobre por qué necesita la clase TimeZoneInfo dado su requisito de convertir UTC a Local. Si su servidor SQL está configurado como _your_ zona horaria local (de acuerdo, esto es una restricción), entonces su función C# se convierte en algo así como 'return new SqlDateTime (utcDate.Value.toLocalTime());' . No necesita especificar un huso horario. ¿Lo he entendido mal? –

1

GETUTCDATE() sólo te da la hora actual en GMT, cualquier DATEADD() lo hace a ese valor no incluirá cualquier cambio de horario de verano.

Su mejor opción es construir su propia tabla de conversión UTC o sólo tiene que utilizar algo como esto:

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

+1

wow. Espero que alguien venga y me diga que esto no es cierto que el servidor sql no puede hacer esto? – Michel

+1

SQL Server no puede hacerlo de la caja, necesitará crear su propia función o llenar su propia tabla de búsqueda –

3

Esta solución parece demasiado obvia.

Si usted puede conseguir UTC fecha con GETUTCDATE() y se puede obtener la fecha local con GETDATE() que haya un desplazamiento que se puede aplicar para cualquier fecha y hora

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

esto debe devolver la hora local ejecuta la consulta,

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

esto devolverá 2011-01-14 02: 00: 00,000 porque estoy en UTC 5

a menos que me falta algo?

+21

No creo que maneje compensaciones de verano – Tomas

+6

No se amontone gratuitamente, pero no solo no maneja horario de verano (o horario de verano), sino que no maneja cambios históricos en zonas horarias o calendarios ya sea. –

-1

Recientemente tuve que hacer lo mismo. El truco está en descifrar el desplazamiento de UTC, pero no es un truco difícil. Simplemente use DateDiff para obtener la diferencia en horas entre local y UTC. Escribí una función para encargarme de esto.

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 

Esto tiene el próximo corto crucial: Si una zona horaria utiliza un desplazamiento fraccional, como Nepal que es GMT + 5: 45, se producirá un error, porque esto sólo se ocupa de horas enteras. Sin embargo, debería ajustarse perfectamente a sus necesidades.

+13

Lamentablemente, esto no se ocupa de los ahorros de luz diurna. La diferencia entre GetDate() y GetUtcDate() no es constante durante todo el año. – Nik

23

He estado esperando durante 5 años una solución más elegante pero como no ha surgido una, publicaré lo que he estado usando hasta ahora ...

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 

Notas:

Esto es para los servidores de América del Norte que observador horario de verano. Por favor cambia la variable @Offest a la zona horaria de compensación del servidor que ejecuta la función de SQL (Aunque no observar el horario de verano) ...

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

Como cambiar las reglas de DST actualizarlos aquí ...

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

Saludos,

+2

¡Muchas gracias por compartir esto! – Rachel

+0

Esto tiene un par de errores. Ver mi respuesta con explicación y código actualizado: http://stackoverflow.com/a/36361558/341942 –

2

puede utilizar mi proyecto SQL Server Time Zone Support para convertir entre IANA zonas horarias estándar, as listed here.

Ejemplo: Respuesta

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

de Bobman está cerca, pero tiene un par de errores: 1) Usted debe comparar el horario de local (en lugar de la hora estándar local) en el horario de verano Fin DateTime. 2) SQL BETWEEN es inclusivo, por lo que debería comparar utilizando "> = y <" en lugar de BETWEEN.

Aquí está una versión de trabajo modificado, junto con algunos casos de prueba: (Una vez más, este sólo funciona para los Estados Unidos)

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

Aquí es una función (de nuevo los EEUU SOLAMENTE), pero es un poco más flexible . Convertirá una fecha UTC a la hora local del servidor. Comienza ajustando la fecha de la cita en función de la compensación actual y luego se ajusta en función de la diferencia entre la compensación actual y la compensación de la fecha de la cita.

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 
Cuestiones relacionadas