Hi,
The ISO Week problem arised last year for us. I programmed, tested and benchmarked a set of various conversion routines. Speed was essential in my case.
Above routine: 1360ms for 10000 conversions
fnISOWeek: 513ms for 10000 conversions
In general I first group results by SQL Year/Week then convert the grouped results using fnISOWeekFromSQL (very fast). Finally I regroup, as SQL separates the end and beginning weeks over a year end. This is faster in my case (millions of rows) than converting every row, especially in crosstabs.
I thought I'd share them, though please do credit me if you use them!:
fnISOWeek (@datetime) returns ISO week as an integer from SQL datetime
fnISOYear (@datetime) returns ISO Year as an integer from SQL datetime
fnISOYearWeek (@datetime) Returns the ISO Year Week in YYYYWW format of the given SQL date (useful for ordering data)
fnISOWeekFromSQL (@week, @year) returns ISO week given the SQL DATEPART week/year
fnISOYearWeekFromSQL (@week, @year) Returns the ISO Year Week in YYYYWW format given the SQL DATEPART Year and Week
fnISOWeekStartDate (@week, @year) Returns the date of the first day of the given ISO Week/Year (Monday)
As we're talking date functions, I also have a very fast midnight calculation if anybody is interested (calculates "12/1/2006 00:00:00" from "12/1/2006 xx:xx:xx")
Adrian Peakman
PS (Added)
Oh, the use of DATEPART(dw) is sensitive to the value of DATEFIRST. (I think everyone's routines are affected by this... )
My routines are coded to use the ISO Standard of Monday as the first day of the week (SET DATEFIRST 1). US installation is DATEFIRST 7 (Sunday) by default I believe. They can easily be altered to conform to other requirements.
---------------------------------------
CREATE FUNCTION fnISOWeek
--Adrian Peakman 6/12/2004
--Returns the ISO Week of the given date
(@d datetime)
returns int
AS
BEGIN
DECLARE @ISOWeek int
SET @ISOWeek= DATEPART(wk,@d)
IF DATEPART(dw,CAST(DATEPART(Year,@d) as varchar)) >4
begin
SET @ISOWeek=@ISOWeek-1
IF @ISOWeek=0
begin
SET @d = DATEADD(day,-3,@d)
SET @ISOWeek= DATEPART(wk,@d)
IF DATEPART(dw,CAST(DATEPART(Year,@d) as varchar)) >4
SET @ISOWeek=@ISOWeek-1
end
end
IF @ISOWeek=53
IF (DATEPART(dd,@d)-DATEPART(dw,@d))>= 28 SET @ISOweek=1
Return (@ISOWeek)
END
--------------------------------
CREATE FUNCTION fnISOYear
--Adrian Peakman 6/12/2004
--Returns the ISO Year of the given date
(@d datetime)
returns int
AS
BEGIN
DECLARE @ISOWeek int
DECLARE @iDayOfYear int
DECLARE @iYear int
SET @iDayOfYear=DATEPART(dy,@d)
SET @iYear=DATEPART(Year,@d)
IF @iDayOfYear > 3 AND @iDayOfYear < 363 RETURN(@iYear)
--Okay now this date may be part of the current year, the last year or the next year!
SET @ISOWeek= dbo.fnISOWeek(@d)
IF @iDayOfYear4
begin
SET @ISOWeek=@ISOWeek-1
IF @ISOWeek=0
begin
--SET @ISOWeek=dbo.fnISOWeek(DATEADD(day,-3,@d))
SET @d = DATEADD(day,-3,@d)
SET @ISOWeek= DATEPART(wk,@d)
IF DATEPART(dw,CAST(DATEPART(Year,@d) as varchar)) >4
SET @ISOWeek=@ISOWeek-1
IF @ISOWeek >0 SET @ISOYear=@ISOYear-1
end
end
IF @ISOWeek=53
IF (DATEPART(dd,@d)-DATEPART(dw,@d))>= 28
BEGIN
SET @ISOweek=1
SET @ISOYear=@ISOYear+1
END
RETURN(@ISOYear*100+@ISOWeek)
END
-------------------------------
CREATE FUNCTION fnISOWeekFromSQL
--Adrian Peakman 6/12/2004
--Returns the ISO Week given the SQL Week
(
@IntWeekNumber int,
@IntYear int
)
RETURNS int
As
BEGIN
DECLARE @ISOWeek int
DECLARE @Jan1Day datetime
SET @Jan1Day=DATEPART(dw,CONVERT(varchar,@IntYear))
SET @ISOWeek=@IntWeekNumber
IF @Jan1Day>4
SET @ISOWeek=@ISOWeek-1
IF @ISOWeek>52
begin
IF DATEPART(dw,CONVERT(varchar,@IntYear+1))0
RETURN(@IsoWeek)
SET @ISOWeek=53
IF DATEPART(dw,CONVERT(varchar,@IntYear-1)) >4
SET @ISOWeek=@ISOWeek-1
RETURN(@ISOWeek)
END
---------------------------------
CREATE FUNCTION fnISOYearWeekFromSQL
--Adrian Peakman 6/12/2004
--Returns the ISO Year Week in YYYYWW format given the SQL DATEPART Year and Week
(
@intWeekNumber int,
@intYear int
)
RETURNS int
As
BEGIN
DECLARE @ISOWeek int
DECLARE @ISOYear int
DECLARE @Jan1Day datetime
SET @Jan1Day=DATEPART(dw,CONVERT(varchar,@intYear))
SET @ISOWeek=@intWeekNumber
SET @ISOYear=@intYear
IF @Jan1Day>4
SET @ISOWeek=@ISOWeek-1
IF @ISOWeek>52
begin
IF DATEPART(dw,CONVERT(varchar,@intYear+1))0
RETURN(@ISOYear*100+@IsoWeek)
SET @ISOWeek=53
SET @ISOYear=@ISOYear-1
IF DATEPART(dw,CONVERT(varchar,@IntYear-1)) >4
begin
SET @ISOWeek=@ISOWeek-1
end
RETURN(@ISOYear*100+@ISOWeek)
END
----------------------------------
CREATE FUNCTION fnISOWeekStartDate
--Adrian Peakman 6/12/2004
--Returns the date of the first day of the given ISO Week/Year (Monday)
(
@IntWeekNumber int,
@IntYear int
)
RETURNS DateTime
As
BEGIN
DECLARE @StartDate DateTime
DECLARE @Jan1Day int
SET @StartDate=CONVERT(varchar,@IntYear)
SET @Jan1Day=DATEPART(dw,@StartDate)
IF @Jan1Day>4
Set @StartDate=DATEADD(day,1-@Jan1Day+7*@IntWeekNumber,@StartDate)
ELSE
Set @StartDate=DATEADD(day,1-@Jan1Day+7*(@IntWeekNumber-1),@StartDate)
RETURN(@StartDate)
END