That function looked like this:
CREATE FUNCTION [dbo].[ConvertGMT](@MYDATE DATETIME)RETURNS DATETIME ASBEGINDECLARE @OFFSET INTDECLARE @YEAR INTDECLARE @DST_BEGIN DATETIMEDECLARE @DST_END DATETIMEDECLARE @RETURNED_DATE DATETIMESET @YEAR = YEAR(@MYDATE)SET @DST_BEGIN = (select DST_BEGIN from dbo.DST_LOOKUP where YEAR_LOOKUP = @YEAR)SET @DST_END = (select DST_END from dbo.DST_LOOKUP where YEAR_LOOKUP = @YEAR)IF (@MYDATE BETWEEN @DST_BEGIN AND @DST_END)SET @OFFSET = -4ELSESET @OFFSET = -5SET @RETURNED_DATE = DATEADD(hour,@OFFSET,@MYDATE)RETURN @RETURNED_DATEEND
and the associated table looked like this:
DST_BEGIN DST_END YEAR_LOOKUP4/4/1999 2:00:00 AM 10/31/1999 3:00:00 AM 19994/2/2000 2:00:00 AM 10/29/2000 3:00:00 AM 20004/1/2001 2:00:00 AM 10/28/2001 3:00:00 AM 20014/7/2002 2:00:00 AM 10/27/2002 3:00:00 AM 20024/6/2003 2:00:00 AM 10/26/2003 3:00:00 AM 20034/4/2004 2:00:00 AM 10/31/2004 3:00:00 AM 20044/3/2005 2:00:00 AM 10/30/2005 3:00:00 AM 20054/2/2006 2:00:00 AM 10/29/2006 3:00:00 AM 20063/11/2007 2:00:00 AM 11/4/2007 3:00:00 AM 20073/9/2008 2:00:00 AM 11/2/2008 3:00:00 AM 20083/8/2009 2:00:00 AM 11/1/2009 3:00:00 AM 20093/14/2010 2:00:00 AM 11/7/2010 3:00:00 AM 20103/13/2011 2:00:00 AM 11/6/2011 3:00:00 AM 2011
This worked well enough for a while, but it cost a couple of table hits for every conversion. That adds up when you're doing 6 conversion per row returning a couple hundred rows a few times a minute. This benchmarked at ≅15seconds to return 45,000 at 3 conversions / row in a simple select.
Our next step was to eliminate the table lookup. That was simple and inelegant, and looked like this:
ALTER FUNCTION [dbo].[ConvertGMT] (@MYDATE DATETIME)RETURNS DATETIME ASBEGINDECLARE @OFFSET INTDECLARE @YEAR INTDECLARE @RETURNED_DATE DATETIMESET @YEAR = YEAR(@MYDATE)IF @YEAR = 2010 AND @MYDATE between 'Mar 14 2010 2:00AM ' and 'Nov 7 2010 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2009 AND @MYDATE between 'Mar 8 2009 2:00AM ' and 'Nov 1 2009 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2008 AND @MYDATE between 'Mar 9 2008 2:00AM ' and 'Nov 2 2008 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2007 AND @MYDATE between 'Mar 11 2007 2:00AM ' and 'Nov 4 2007 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2006 AND @MYDATE between 'Apr 2 2006 2:00AM ' and 'Oct 29 2006 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2005 AND @MYDATE between 'Apr 3 2005 2:00AM ' and 'Oct 30 2005 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2004 AND @MYDATE between 'Apr 4 2004 2:00AM ' and 'Oct 31 2004 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2003 AND @MYDATE between 'Apr 6 2003 2:00AM ' and 'Oct 26 2003 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2002 AND @MYDATE between 'Apr 7 2002 2:00AM ' and 'Oct 27 2002 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2001 AND @MYDATE between 'Apr 1 2001 2:00AM ' and 'Oct 28 2001 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2000 AND @MYDATE between 'Apr 2 2000 2:00AM ' and 'Oct 29 2000 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 1999 AND @MYDATE between 'Apr 4 1999 2:00AM ' and 'Oct 31 1999 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2011 AND @MYDATE between 'Mar 13 2011 2:00AM ' and 'Nov 6 2011 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2012 AND @MYDATE between 'Mar 11 2012 2:00AM ' and 'Nov 4 2012 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2013 AND @MYDATE between 'Mar 10 2013 2:00AM ' and 'Nov 3 2013 3:00AM ' SET @OFFSET = -4ELSE IF @YEAR = 2014 AND @MYDATE between 'Mar 9 2014 2:00AM ' and 'Nov 2 2014 3:00AM ' SET @OFFSET = -4ELSE SET @OFFSET = -5SET @RETURNED_DATE = DATEADD(hour,@OFFSET,@MYDATE)RETURN @RETURNED_DATEEND
The performance boost with this version was amazing. It processed the same 45,000 x 3 conversion / row query in ≅3 seconds. Across all our GUI and report calls, that was a noticeable performance boost. But, it looked like something someone in CS101 would write, and it still required occasional updates to add new years and keep current.
The final step - and current solution - started with a DST discussion at MSSqlTips that used some pre-calculated offsets to dynamically calculate DST start and stop dates based on the DOW of the start and end months. This worked well for dates 2007 and later, but earlier dates had a different date pattern. I continued the same model that Tim Cullen started with the MSSqlTips post and used a set of precalculated offsets for 2006 and earlier starts, and dynamically calculated DST end dates.
The final function looks like this:
CREATE FUNCTION [dbo].[ConvertGMT](@MYDATE DATETIME)RETURNS DATETIME ASBEGINDECLARE @OFFSET INTDECLARE @YEAR INTDECLARE @DST_BEGIN DATETIMEDECLARE @DST_END DATETIMEDECLARE @RETURNED_DATE DATETIMESET @YEAR = YEAR(@MYDATE)declare @DSTStartWeek smalldatetime, @DSTEndWeek smalldatetimeif @YEAR >= 2007BEGINset @DSTStartWeek = '03/01/' + convert(varchar,@YEAR)SET @DST_BEGIN = case datepart(dw,@DSTStartWeek)when 1 thendateadd(hour,170,@DSTStartWeek)when 2 thendateadd(hour,314,@DSTStartWeek)when 3 thendateadd(hour,290,@DSTStartWeek)when 4 thendateadd(hour,266,@DSTStartWeek)when 5 thendateadd(hour,242,@DSTStartWeek)when 6 thendateadd(hour,218,@DSTStartWeek)when 7 thendateadd(hour,194,@DSTStartWeek)endset @DSTEndWeek = '11/01/' + convert(varchar,@Year)SET @DST_END = case datepart(dw,dateadd(week,1,@DSTEndWeek))when 1 thendateadd(hour,2,@DSTEndWeek)when 2 thendateadd(hour,146,@DSTEndWeek)when 3 thendateadd(hour,122,@DSTEndWeek)when 4 thendateadd(hour,98,@DSTEndWeek)when 5 thendateadd(hour,74,@DSTEndWeek)when 6 thendateadd(hour,50,@DSTEndWeek)when 7 thendateadd(hour,26,@DSTEndWeek)endENDELSEBEGINset @DSTStartWeek = '04/01/' + convert(varchar,@YEAR)SET @DST_BEGIN = case datepart(dw,@DSTStartWeek)when 1 thendateadd(hour,2,@DSTStartWeek)when 2 thendateadd(hour,146,@DSTStartWeek)when 3 thendateadd(hour,122,@DSTStartWeek)when 4 thendateadd(hour,98,@DSTStartWeek)when 5 thendateadd(hour,74,@DSTStartWeek)when 6 thendateadd(hour,50,@DSTStartWeek)when 7 thendateadd(hour,26,@DSTStartWeek)endSET @DSTEndWeek = '11/01/' + convert(varchar,@Year)SET @DST_END = dateadd(hh,3,dateadd (dd,(1 - datepart(dw,@DSTEndWeek)),@DSTEndWeek))ENDIF @MYDATE between @DST_BEGIN and @DST_ENDSET @OFFSET = -4ELSESET @OFFSET = -5SET @RETURNED_DATE = DATEADD(hour,@OFFSET,@MYDATE)RETURN @RETURNED_DATEEND
This code has one change from Tim's code - it now ends at 3am in November, for 2007 and later. This is what we have in production now. It's just as performant as the previous version (≅3 sec for the benchmark query), but it has the advantage of never needing updating for new years. It's more elegant than it's predecessors, but I have no doubt that it could be done better.
Use, share, comment.
-Bill
(Thanks to the Wikipedia DST Article for clarity on this.)