We are migrating an application to Azure, but currently all of our dates are stored as Eastern Standard Time. Since SQL Azure is on UTC and many of our dates are generated from a getdate() call, we're going to have issues if we leave everything as it is. It seems that the option that will provide the least long term headache is to just convert all of our stored dates to UTC, and have them converted to local time on the front end.

Unfortunately it seems that there isn't a built-in way to convert a SQL datetime from one time zone to another, and many of the solutions that I've found don't take daylight savings time into consideration. The solutions that I've found that do consider DST involve importing calendars and time zone tables and are extremely complex.

I'm just looking for something that will work for a single conversion, and it doesn't have to be pretty. Any ideas?

UPDATE: I wrote the following function that I believe will accomplish what I need. Does anyone see any holes with this? All of my dates are currently Eastern Standard Time, but my dates start around 2002 so I had to handle that law change in 2007.

I'm thinking I would use this function like this:

UPDATE myTable SET myDate = dbo.fnESTtoUTC(myDate)

Here's the function:

CREATE FUNCTION [dbo].[fnESTtoUTC]
    (@pESTDate  DATETIME)
    RETURNS DATETIME
AS
BEGIN

DECLARE @TimeZoneOffset INT
DECLARE @Year INT
DECLARE @Day INT
DECLARE @DSTStart DATETIME
DECLARE @DSTEnd DATETIME

SELECT @Year = DATEPART(year, @pESTDate)

IF @Year >= 2007
BEGIN
    SELECT @Day = 8
    WHILE @DSTStart IS NULL
    BEGIN
        -- Second Sunday in March
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 3, @Day)) = 1
            SELECT @DSTStart = DATETIMEFROMPARTS(@Year, 3, @Day, 2, 0, 0, 0)
        SELECT @Day = @Day + 1
    END

    SELECT @Day = 1
    WHILE @DSTEnd IS NULL
    BEGIN
        -- First Sunday in November
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 11, @Day)) = 1
            SELECT @DSTEnd = DATETIMEFROMPARTS(@Year, 11, @Day, 1, 0, 0, 0)
        SELECT @Day = @Day + 1
    END
END
ELSE
BEGIN
    SELECT @Day = 1
    WHILE @DSTStart IS NULL
    BEGIN
        -- First Sunday in April
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 4, @Day)) = 1
            SELECT @DSTStart = DATETIMEFROMPARTS(@Year, 4, @Day, 2, 0, 0, 0)
        SELECT @Day = @Day + 1
    END

    SELECT @Day = 31
    WHILE @DSTEnd IS NULL
    BEGIN
        -- Last Sunday in October
        IF DATEPART(weekday, DATEFROMPARTS(@Year, 10, @Day)) = 1
            SELECT @DSTEnd = DATETIMEFROMPARTS(@Year, 10, @Day, 1, 0, 0, 0)
        SELECT @Day = @Day - 1
    END
END

IF @pESTDate >= @DSTStart AND @pESTDate < @DSTEnd
BEGIN
    -- Date is in DST
    SELECT @TimeZoneOffset = 4
END
ELSE
BEGIN
    -- Not DST
    SELECT @TimeZoneOffset = 5
END

RETURN ( dateadd(hh, @TimeZoneOffset, @pESTDate) )
END
有帮助吗?

解决方案

There are several problems:

  • You are basing which rule to use on the current date, rather than on the date provided in your input. You should change that for sure.

  • If I pass a value such as 2013-03-10 02:30, your function will assume that it was EDT, but in reality that time was invalid and should not exist in your data. You should probably raise an error.

  • If I pass a value such as 2013-11-03 01:30, your function will assume that it was EDT, but in reality it might have been in either EDT or EST. You would need to have stored either an offset or a dst flag to disambiguate. If it's not in the data, you have no choice but to assume one or the other.

  • This function doesn't account for dates before 1987, which also had a DST rule change in the United States. If you have data from before then, you should account for that as well.

Other than that, it looks fine. Still, the points in the comments are correct. This will only work for this one time zone, and you have no guarantees that the rules for this time zone won't change in the future. I recommend you use convert your data to use UTC going forward. You could use this function for the conversion if you like, or you could just as easily do it in application-level code.

Oh, and one other thing. "Eastern Standard Time" or "EST" literally means UTC-5 without regard to daylight saving time at all. Just like "Eastern Daylight Time" or "EDT" always means UTC-4. I assume that you meant to say that your data is in "Eastern Time" in your question, which accommodates both.

If you actually meant EST, then your job is a lot simpler - just add 5 hours and call it done. I bring this up because there are indeed scenarios where data is recorded without respect to daylight saving time. (I believe there are some use cases in the financial sector that work like that.)

其他提示

I have just created own which takes a datetime variable in the future. It follows the NIST rules stated below However, my requirement was to return it as TEXT, and not a datetime, and I had to se MS SQL 2005. Feel free to tweak. :)

Rules based on www.nist.gov

  • Start second sunday of March @ 02:00
  • End first sunday of november @ 02:00

My TimeZone : West Coast (-8)

CREATE FUNCTION [dbo].[Local_Time]
(
    @dt     datetime    --  In UTC
)
RETURNS varchar(40)
AS
BEGIN

declare @local as datetime
declare @dw as int
declare @startDLS as datetime
declare @stopDLS as datetime
declare @tz as int
set @tz = -8        -- Time Zone - AKA sunny (and expensive) California

set @startDLS = 0  -- init
set @startDLS = cast (dateadd (yy, datepart(yy,@dt)-1900, @startDLS) as datetime)       --  Current Year
set @stopDLS = @startDLS                                                            --     Current Year
set @startDLS = cast (dateadd (mm, 2, @startDLS) as datetime)   -- Make date March 1st
set @stopDLS  = cast (dateadd (mm, 10, @stopDLS) as datetime)   -- Make date November 1st
set @dw = datepart(dw, @startDLS)

IF @dw = 1  
    BEGIN
        set @startDLS = cast (dateadd (dd, 7 , @startDLS) as datetime)
    END
ELSE 
    BEGIN
        set @startDLS = cast (dateadd (dd, 15-@dw, @startDLS) as datetime)
    END
SET @startDLS = cast (dateadd (hh, 2, @startDLS) as datetime)

IF @dw = 1 
    BEGIN
        set @stopDLS = cast (dateadd (dd, 7 , @stopDLS) as datetime)
    END
ELSE 
    BEGIN
        set @stopDLS = cast (dateadd (dd, 8-@dw, @stopDLS) as datetime)
    END
SET @stopDLS = cast (dateadd (hh, 2, @stopDLS) as datetime)

IF (@dt >= @startDLS ) and 
    (@dt < @stopDLS )
    BEGIN  --  @dt is within Daylight Savings Time Rules - Add an hour
        set @local = dateadd(hh, -7, @dt)
    END
ELSE    --  @dt is outside Daylight Savings Time Rules - No Adjustment
    BEGIN
        set @local = dateadd(hh, -8, @dt)
    END
return cast(@local as varchar(40) )

END
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top