2014-02-14 14 views
0

我希望爲當前和歷史日期(包括日光節約時間)提供與本地時間和UTC時間之間的日期轉換相關的代碼考慮。我回顧了一些最近發佈的帖子,但找不到我正在尋找的解決方案,因此我總結了我發現開發自己的解決方案的解決方案。我在SQL Server 2008R2中運行我的代碼。測試用例在每個功能之前作爲註釋提供。請隨時查看相關帖子的積分部分,試用我的代碼,並讓我知道如果我錯過任何東西。謝謝。通過考慮美國日光節省時間,將當地時間和UTC時間之間的日期轉換的功能

-- Test 1: select dbo.fn_isDayLightSavings(CAST('2014-01-01' AS DATE)), expect 0 
-- Test 2: select dbo.fn_isDayLightSavings(CAST('2014-04-14' AS DATE)), expect 1 

IF OBJECT_ID(N'fn_isDayLightSavings') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_isDayLightSavings'),'IsScalarFunction') = 1 
    DROP FUNCTION fn_isDayLightSavings; 
GO 
CREATE FUNCTION fn_isDayLightSavings (@dt AS DATETIME) 
RETURNS TINYINT 
AS 
BEGIN 
    DECLARE @rtn TINYINT, @year INT, @dtsStartMonth DATETIME, @dtsEndMonth DATETIME, @dstStart DATETIME, @dstEnd DATETIME; 

    SET @year = DATEPART(YEAR, @dt); 
    -- In year 2007, US day light savings period changes from Apr-Oct to Mar-Nov. 
    if @year < 2007 
    BEGIN 
     -- Last Sunday of April at 2 AM 
     SET @dtsStartMonth = DATEADD(MONTH, 4, DATEADD(YEAR, @year - 1900, 0)); 
     SET @dstStart = DATEADD(HOUR, 2, DATEADD(day, -(DATEPART(dw, @dtsStartMonth) - 1), @dtsStartMonth)); 
     -- Last Sunday of October at 2 AM 
     SET @dtsEndMonth = DATEADD(MONTH, 10, DATEADD(YEAR, @year - 1900, 0)); 
     SET @dstEnd = DATEADD(HOUR, 2, DATEADD(day, -(DATEPART(dw, @dtsEndMonth) - 1), @dtsEndMonth)); 
    END 
    else 
    BEGIN 
     -- 2nd Sunday of March at 2 AM 
     SET @dtsStartMonth = DATEADD(MONTH, 2, DATEADD(YEAR, @year - 1900, 0)); 
     SET @dstStart = DATEADD(HOUR, 2, DATEADD(day, ((15 - DATEPART(dw, @dtsStartMonth)) % 7) + 7, @dtsStartMonth)); 
     -- 1st Sunday of November at 2 AM 
     SET @dtsEndMonth = DATEADD(MONTH, 10, DATEADD(YEAR, @year - 1900, 0)); 
     SET @dstEnd = DATEADD(HOUR, 2, DATEADD(day, ((8 - DATEPART(dw, @dtsEndMonth)) % 7) + 7, @dtsEndMonth)); 
    END 

    if @dt BETWEEN @dstStart AND @dstEnd SET @rtn=1 ELSE SET @rtn=0; 
    RETURN @rtn; 
END 
GO 

-- Test 1: select dbo.fn_DateTime2UTC(CAST('2014-01-01 01:00:00' AS DATETIME), 0), expect '2014-01-01 07:00:00.000' 
-- Test 2: select dbo.fn_DateTime2UTC(CAST('2014-01-01 01:00:00' AS DATETIME), 1), expect '2013-01-01 07:00:00.000' 
-- Test 3: select dbo.fn_DateTime2UTC(CAST('2014-05-01 01:00:00' AS DATETIME), 0), expect '2014-05-01 06:00:00.000' 
-- Test 4: select dbo.fn_DateTime2UTC(CAST('2014-05-01 01:00:00' AS DATETIME), 1), expect '2014-05-01 07:00:00.000' 
IF OBJECT_ID(N'fn_DateTime2UTC') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_DateTime2UTC'),'IsScalarFunction') = 1 
    DROP FUNCTION fn_DateTime2UTC; 
GO 
CREATE FUNCTION fn_DateTime2UTC (@dt AS DATETIME, @ignoreDST AS TINYINT = 0) 
-- do CAST(? AS DATETIMEOFFSET), if need datetimeoffset type 
RETURNS DATETIME 
AS 
BEGIN 
    DECLARE @tzOffset INT, @utcDt DATETIME; 

    -- Get current time zone offset in minutes 
    SET @tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) + 
     (CASE WHEN dbo.fn_isDayLightSavings(@dt)=1 THEN 60 ELSE 0 END); 
    if dbo.fn_isDayLightSavings(@dt)=0 
     set @utcDt = DATEADD(MINUTE, [email protected], @dt); 
    else if @ignoreDST=0 
     set @utcDt = DATEADD(MINUTE, [email protected], @dt); 
    else 
     set @utcDt = DATEADD(MINUTE, [email protected]+60, @dt); 

    return @utcDt; 
END 
GO 

-- Test 1: select dbo.fn_UTC2DateTime(CAST('2014-01-01 07:00:00.000' AS DATETIME), 0), expect '2014-01-01 01:00:00' 
-- Test 2: select dbo.fn_UTC2DateTime(CAST('2013-01-01 07:00:00.000' AS DATETIME), 1), expect '2014-01-01 01:00:00' 
-- Test 3: select dbo.fn_UTC2DateTime(CAST('2014-05-01 06:00:00.000' AS DATETIME), 0), expect '2014-05-01 01:00:00' 
-- Test 4: select dbo.fn_UTC2DateTime(CAST('2014-05-01 07:00:00.000' AS DATETIME), 1), expect '2014-05-01 01:00:00' 
IF OBJECT_ID(N'fn_UTC2DateTime') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_UTC2DateTime'),'IsScalarFunction') = 1 
    DROP FUNCTION fn_UTC2DateTime; 
GO 
CREATE FUNCTION fn_UTC2DateTime (@utcDt AS DATETIME, @ignoreDST AS TINYINT = 0) 
RETURNS DATETIME 
AS 
BEGIN 
    DECLARE @tzOffset INT, @dt DATETIME; 

    -- Get current time zone offset in minutes 
    SET @tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) + 
     (CASE WHEN dbo.fn_isDayLightSavings(@utcDt)=1 THEN 60 ELSE 0 END); 
    if dbo.fn_isDayLightSavings(@utcDt)=0 
     set @dt = DATEADD(MINUTE, @tzOffset, @utcDt); 
    else if @ignoreDST=0 
     set @dt = DATEADD(MINUTE, @tzOffset, @utcDt); 
    else 
     set @dt = DATEADD(MINUTE, @tzOffset-60, @utcDt); 

    return @dt; 
END 
GO 

-- Test 1: select dbo.fn_UTC2DateTimeOffset(CAST('2014-01-01 07:00:00.000' AS DATETIME), 0), expect '2014-01-01 01:00:00.0000000 -06:00' 
-- Test 2: select dbo.fn_UTC2DateTimeOffset(CAST('2013-01-01 07:00:00.000' AS DATETIME), 1), expect '2014-01-01 01:00:00.0000000 -06:00' 
-- Test 3: select dbo.fn_UTC2DateTimeOffset(CAST('2014-05-01 06:00:00.000' AS DATETIME), 0), expect '2014-05-01 01:00:00.0000000 -05:00' 
-- Test 4: select dbo.fn_UTC2DateTimeOffset(CAST('2014-05-01 07:00:00.000' AS DATETIME), 1), expect '2014-05-01 00:00:00.0000000 -06:00' 
IF OBJECT_ID(N'fn_UTC2DateTimeOffset') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_UTC2DateTimeOffset'),'IsScalarFunction') = 1 
    DROP FUNCTION fn_UTC2DateTimeOffset; 
GO 
CREATE FUNCTION fn_UTC2DateTimeOffset (@utcDt AS DATETIME, @ignoreDST TINYINT = 0) 
RETURNS DATETIMEOFFSET 
AS 
BEGIN 
    DECLARE @tzOffset INT, @dt DATETIMEOFFSET; 

    -- Get current time zone offset in minutes 
    SET @tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) + 
     (CASE WHEN dbo.fn_isDayLightSavings(@utcDt)=1 THEN 60 ELSE 0 END); 
    if dbo.fn_isDayLightSavings(@utcDt)=0 
     set @dt = SWITCHOFFSET(CAST(@utcDt AS DATETIMEOFFSET), @tzOffset); 
    else if @ignoreDST=0 
     set @dt = SWITCHOFFSET(CAST(@utcDt AS DATETIMEOFFSET), @tzOffset); 
    else 
     set @dt = SWITCHOFFSET(CAST(@utcDt AS DATETIMEOFFSET), @tzOffset-60); 
    return @dt; 
END 
GO 

-- Credits: 
-- Determination of day light savings start and end time, Jamie F., 11/1/2013 
--  (http://stackoverflow.com/questions/19732896/how-to-create-daylight-savings-time-start-and-end-function-in-sql-server) 
-- Day light savings time calendar, USNO 
--  (http://aa.usno.navy.mil/faq/docs/daylight_time.php) 
-- Time zone offset, Microsoft 
--  (http://msdn.microsoft.com/en-us/library/ms174420.aspx) 
-- Local system date time with time zone, Robert Cantor, 12/7/2013 
--  (http://stackoverflow.com/questions/1205142/tsql-how-to-convert-local-time-to-utc-sqlserver2008) 
-- Convert date with time zone and day light savings, Eric Z Beard, 8/24/2008 
--  (http://stackoverflow.com/questions/24797/effectively-converting-dates-between-utc-and-local-ie-pst-time-in-sql-2005#25073) 

回答

0

將您的知識組裝成一個地方的榮譽。假設它是準確的,很容易立即拿起並使用。

但是,麻煩在於,如果沒有大量的單元測試,就無法確定準確性。

我建議日期/時間轉換的最佳選擇是使用現有經過高度測試的開源庫,如http://nodatime.org/。日期/時間轉換有很多intracacies和細節,不可能包含在幾十行SQL代碼中。

nodatime是一個.NET庫,應該可以通過SQL Server CLR用戶定義的函數功能輕鬆訪問。

+1

不錯的想法,但是Noda Time或TimeZoneInfo目前都不能通過sqlclr訪問,而無需在「UNSAFE」模式下運行。我正在努力爲諾達時間貢獻一份未來版本,以支持SQLCLR作爲「安全」模式程序集。 –

相關問題