A Simple Formula to Calculate the ISO Week Number

  • dmbaker (10/23/2015)


    Thanks for the article...5 stars (especially since you didn't say "performant" anywhere) 😀

    Thanks for the feedback and the stars. I appreciate it.

    I'm curious as to why the slang/lingo word "performant" sit's in the craw of so many people, though. To quote the first item that pops up when you Google the definition...

    Performant is a word that was made up by software developers to describe software that performs well, in whatever way you want to define performance. It is a word that is not in the dictionary yet, but I think it should be.

    I agree and I'll add that that's the way new words end up in the dictionary. 😉

    On that same note, have you ever told someone to Google something? If so, then you're just as bad as people who say "performant" because Google is a proper noun and not a verb. :w00t:

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • To the best of my knowledge, I know of no temporal system that considers midnight to be at the end of the day. It always marks the beginning of a day. For example, there is no 24:00 for time even in the military.

    The project managers I work with are often confused. We have asked them to enter 23:59 or 00:01 to be clear which night of the weekend I'll be working on their conversion.

    412-977-3526 call/text

  • robert.sterbal 56890 (10/26/2015)


    To the best of my knowledge, I know of no temporal system that considers midnight to be at the end of the day. It always marks the beginning of a day. For example, there is no 24:00 for time even in the military.

    The project managers I work with are often confused. We have asked them to enter 23:59 or 00:01 to be clear which night of the weekend I'll be working on their conversion.

    They understand the concept of 24 hour time but they can't handle midnight. Remarkable.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • SwePeso (4/8/2013)


    However, the fix is really simple. Since all we need in an anchor date of a monday, write this case statement.

    Thanks for the insight.

  • Kudos to "t-clausen.dk" and thanks to Jeff Moden for the explanation. After a brief read (post search) I wanted to try and create a similar "elegant" equation that would work for a customer of mine that does not use the ISO week, but rather "the first week of the year is whatever week the first day of the year occurs in". I have to generate a material identifier using the year and week as part of the generated ID for this customer. Unfortunately lacking the patience and time in which to thoroughly understand the formula, I decided to create something from scratch quickly enough to get the current (and soon to be "broken") ISO method in place, replaced. My first attempt was dismal, as my incomplete understanding of the formula led me to try to use the days in a year with a calculated "overage" to determine which days at the end of December would fall into the new year. I realized the driving factor is the first day of the coming year and the days in a year just didn't matter. After that I was able to create a relatively simplistic way to get what I needed and did want to share with you all to pick apart to your hearts content.

    DECLARE @OrderDate DATETIME

    DECLARE @OrderYear VARCHAR(2)

    DECLARE @OrderWeek VARCHAR(2)

    SET @OrderDate = '1/1/2000'

    SET NOCOUNT ON

    /******************************************************************************************/

    /* The rule is "whichever week day one of the new year falls on is considered Week 1" */

    /* Calculate the difference in days between Order Date and First Day of Next Year (FDONY) */

    /* if that difference is less than the FDONY day of the week minus one that means the */

    /* Order Date is part of the FDONY week and therefore will be week 1 of the next year */

    /* THIS DOES ASSUME "SET DATEFIRST 7" U.S. English default */

    /******************************************************************************************/

    --DECLARE @FDONY_Date DATETIME = CAST('1/1/' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR) AS DATETIME)

    --DECLARE @DaysDiff_OrderDate_FDONY INT = DATEDIFF(DD, @OrderDate, @FDONY_Date)

    --DECLARE @DayOfWeek_FDONY INT = DATEPART(DW, @FDONY_Date)

    DECLARE @AdjustCalc INT = 0

    --IF @DaysDiff_OrderDate_FDONY <= @DayOfWeek_FDONY - 1 SET @AdjustCalc = 1

    --w/o variables

    IF (DATEDIFF(DD, @OrderDate, (CAST('1/1/' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR) AS DATETIME)))) <= (DATEPART(DW, (CAST('1/1/' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR) AS DATETIME)))) - 1

    SET @AdjustCalc = 1

    SET @OrderWeek = ( CASE @AdjustCalc WHEN 1 THEN '01' ELSE RIGHT( '0' + CAST(DATEPART(WK, @OrderDate) AS VARCHAR), 2) END )

    SET @OrderYear = RIGHT( '0' + CAST(DATEPART(YYYY, @OrderDate) + @AdjustCalc AS VARCHAR), 2)

  • Interesting...but have a play with this, which could be converted into an inline table-valued function:

    -- SET DATEFIRST 7 = SUNDAY

    SELECT DATENAME(DW,'18991231') -- sunday

    -- count the number of whole weeks between '18991231' (a sunday) and '20160101'

    SELECT DATEDIFF(WEEK,'18991231','20160101')

    -- 6052

    -- add this figure to '18991231' to get the start of the week (the sunday) containing '20160101'

    SELECT DATEADD(WEEK,6052,'18991231')

    -- 2015-12-27 00:00:00.000

    -- combine these two expressions

    SELECT DATEADD(WEEK,DATEDIFF(WEEK,'18991231','20160101'),'18991231')

    -- 2015-12-27 00:00:00.000

    -- test with a bunch of dates

    SELECT

    FirstDay,

    DATENAME(DW,FirstDay),

    x.FDONY,

    DATENAME(DW,x.FDONY)

    FROM (VALUES

    (CAST('20180101' AS datetime)),

    ('20170101'),

    ('20160101'),

    ('20150101'),

    ('20140101'),

    ('20130101'),

    ('20120101'),

    ('20110101')) d (FirstDay)

    CROSS APPLY (SELECT FDONY = DATEADD(WEEK,DATEDIFF(WEEK,'18991231',FirstDay),'18991231')) x

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Thank you I did manage to take a good run at this but feel I have fallen short on the result. I know there is some better way to get the week number out of what you shared, and that still does not account for having to bump the year up to properly set the identifier for the material code. I will take another look when time permits, thanks again for your reply.

    DECLARE @OrderDate DATETIME = '12/28/2008'

    SELECT CASE WHEN (DATEDIFF( DD, DATEADD(WEEK,DATEDIFF(WEEK,'18991231', (CAST('1/1/' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR) AS DATETIME) )), '18991231'), @OrderDate )) >= 0 THEN (RIGHT( '0' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR), 2)) + '01' ELSE (RIGHT( '0' + CAST(DATEPART(YYYY, @OrderDate) AS VARCHAR), 2)) + RIGHT( '0' + CAST(DATEPART(WK, @OrderDate) AS VARCHAR), 2) END AS YearWeek

  • chill-1062987 (12/8/2015)


    Thank you I did manage to take a good run at this but feel I have fallen short on the result. I know there is some better way to get the week number out of what you shared, and that still does not account for having to bump the year up to properly set the identifier for the material code. I will take another look when time permits, thanks again for your reply.

    DECLARE @OrderDate DATETIME = '12/28/2008'

    SELECT CASE WHEN (DATEDIFF( DD, DATEADD(WEEK,DATEDIFF(WEEK,'18991231', (CAST('1/1/' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR) AS DATETIME) )), '18991231'), @OrderDate )) >= 0 THEN (RIGHT( '0' + CAST(DATEPART(YYYY, @OrderDate) + 1 AS VARCHAR), 2)) + '01' ELSE (RIGHT( '0' + CAST(DATEPART(YYYY, @OrderDate) AS VARCHAR), 2)) + RIGHT( '0' + CAST(DATEPART(WK, @OrderDate) AS VARCHAR), 2) END AS YearWeek

    If you can post up a little sample data (this article [/url]will show you how), I'm sure we can help.

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden

  • Hello again and thank you for your continued interest in this in advance. I want to clarify a couple of things real quick in the interests of better understanding. The system I am currently (and hopefully somewhat temporarily) saddled with maintaining uses a procedural process to generate a material identifier, a portion of which is a fixed format "YYWK" representing the last 2 digits of the year and another 2 for the week of the year. Our process is driven by a weekly order cycle, currently with a Monday date representing the order week, but could be designated as any day of the week. This date drives the population of the "YYWK", which is the same for every ID for that week. If the week is the first week of the year, the year value should reflect the year that Jan 1st is in, not necessarily the year of the date given. I did create and post what for now is an acceptable solution, but for both academic interest and potential future improvement I wanted to take this further.

    The date data to drive this could be any date, forgive my awkward usage of a CTE (which was fun) to create the test data.

    IF OBJECT_ID('TempDB..#TestTable','U') IS NOT NULL

    DROP TABLE #TestTable

    CREATE TABLE #TestTable

    (

    CalendarKey INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,

    CalendarDate DATETIME,

    ISO_YearWeek VARCHAR(4),

    Adj_YearWeek VARCHAR(4)

    )

    SET IDENTITY_INSERT #TestTable ON

    ;WITH tDates AS

    (

    SELECT 1 AS CalendarKey,

    CAST('12/26/1999' AS DATETIME) AS CalendarDate,

    NULL AS ISO_YearWeek,

    NULL AS Adj_YearWeek

    UNION ALL

    SELECT CalendarKey + 1,

    CalendarDate + 1 AS CalendarDate,

    NULL AS ISO_YearWeek,

    NULL AS Adj_YearWeek

    FROM tDates

    WHERE CalendarDate < '1/4/2020'

    )

    INSERT #TestTable

    (

    CalendarKey,

    CalendarDate,

    ISO_YearWeek,

    Adj_YearWeek

    )

    SELECT CalendarKey,

    CalendarDate,

    ISO_YearWeek,

    Adj_YearWeek

    FROM tDates

    OPTION (MAXRECURSION 7315)

    SET IDENTITY_INSERT #TestTable OFF

    Below find the equivalent of what is in place today (ISO), with the somewhat unsatisfactory replacement (Jan 1st based) to determine the "YYWK" value:

    UPDATE #TestTable

    SET ISO_YearWeek =

    CASE

    WHEN (MONTH(CalendarDate) = 12 AND DATEPART(ISOWW, CalendarDate) = 1)

    THEN RIGHT(CONVERT(CHAR(4), YEAR(CalendarDate) + 1), 2)

    ELSE RIGHT(CONVERT(CHAR(4), YEAR(CalendarDate)), 2)

    END +

    RIGHT('0' + CAST(CONVERT(INT, DATEPART(ISOWW, CalendarDate)) AS VARCHAR), 2)

    UPDATE #TestTable

    SET Adj_YearWeek =

    CASE

    WHEN (DATEDIFF( DD, DATEADD(WEEK,DATEDIFF(WEEK,'18991231', (CAST('1/1/' + CAST(DATEPART(YYYY, CalendarDate) + 1 AS VARCHAR) AS DATETIME) )), '18991231'), CalendarDate )) >= 0

    THEN (RIGHT( '0' + CAST(DATEPART(YYYY, CalendarDate) + 1 AS VARCHAR), 2)) + '01'

    ELSE (RIGHT( '0' + CAST(DATEPART(YYYY, CalendarDate) AS VARCHAR), 2)) + RIGHT( '0' + CAST(DATEPART(WK, CalendarDate) AS VARCHAR), 2)

    END

    SELECT CalendarDate,

    DATENAME(DW,CalendarDate) AS Weekday,

    ISO_YearWeek,

    Adj_YearWeek

    FROM #TestTable

    The results illustrate the wanted value in the "Adj" column, and also enlightened me that even our current ISO method would fail if our weekly order date was changed to a Sunday.

    CalendarDateWeekdayISO_YearWeekAdj_YearWeek

    ----------------------- ------------------------------------------------------

    2008-12-26 00:00:00.000 Friday08520852

    2008-12-27 00:00:00.000 Saturday08520852

    2008-12-28 00:00:00.000 Sunday08520901

    2008-12-29 00:00:00.000 Monday09010901

    2008-12-30 00:00:00.000 Tuesday09010901

    2008-12-31 00:00:00.000 Wednesday09010901

    2009-01-01 00:00:00.000 Thursday09010901

    2009-01-02 00:00:00.000 Friday09010901

    2009-01-03 00:00:00.000 Saturday09010901

    2009-01-04 00:00:00.000 Sunday09010902

    2009-01-05 00:00:00.000 Monday09020902

  • Is this still the best way to do this in 2021?  Google landed me at another SSC Forum from 2005, which I can't find anymore, and now I'm using this

    with iso_dts_cte(yr, mo, wk) as (
    select * from (values ('2020', '12', '50'),
    ('2020', '12', '51'),
    ('2020', '12', '52'),
    ('2020', '12', '53'),
    ('2021', '01', '01'),
    ('2021', '01', '02'),
    ('2021', '01', '03')) v(yr, mo, wk))
    select iso.*, v.*
    from iso_dts_cte iso
    cross apply (values (cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk-1)*7) as date),
    cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk)*7)-1 as date))) v(start_dt, end_dt);
    yrmowkstart_dtend_dt
    202012502020-12-072020-12-13
    202012512020-12-142020-12-20
    202012522020-12-212020-12-27
    202012532020-12-282021-01-03
    202101012021-01-042021-01-10
    202101022021-01-112021-01-17
    202101032021-01-182021-01-24

     

    Aus dem Paradies, das Cantor uns geschaffen, soll uns niemand vertreiben können

  • Steve Collins wrote:

    Is this still the best way to do this in 2021?  Google landed me at another SSC Forum from 2005, which I can't find anymore, and now I'm using this

    with iso_dts_cte(yr, mo, wk) as (
    select * from (values ('2020', '12', '50'),
    ('2020', '12', '51'),
    ('2020', '12', '52'),
    ('2020', '12', '53'),
    ('2021', '01', '01'),
    ('2021', '01', '02'),
    ('2021', '01', '03')) v(yr, mo, wk))
    select iso.*, v.*
    from iso_dts_cte iso
    cross apply (values (cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk-1)*7) as date),
    cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk)*7)-1 as date))) v(start_dt, end_dt);
    yrmowkstart_dtend_dt
    202012502020-12-072020-12-13
    202012512020-12-142020-12-20
    202012522020-12-212020-12-27
    202012532020-12-282021-01-03
    202101012021-01-042021-01-10
    202101022021-01-112021-01-17
    202101032021-01-182021-01-24

    Do "this"?  Just to be absolutely sure (I've not had enough coffee yet), what is "this"?  Do you mean the conversion of an ISO week to an actual date range?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden wrote:

    Steve Collins wrote:

    Is this still the best way to do this in 2021?  Google landed me at another SSC Forum from 2005, which I can't find anymore, and now I'm using this

    with iso_dts_cte(yr, mo, wk) as (
    select * from (values ('2020', '12', '50'),
    ('2020', '12', '51'),
    ('2020', '12', '52'),
    ('2020', '12', '53'),
    ('2021', '01', '01'),
    ('2021', '01', '02'),
    ('2021', '01', '03')) v(yr, mo, wk))
    select iso.*, v.*
    from iso_dts_cte iso
    cross apply (values (cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk-1)*7) as date),
    cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk)*7)-1 as date))) v(start_dt, end_dt);
    yrmowkstart_dtend_dt
    202012502020-12-072020-12-13
    202012512020-12-142020-12-20
    202012522020-12-212020-12-27
    202012532020-12-282021-01-03
    202101012021-01-042021-01-10
    202101022021-01-112021-01-17
    202101032021-01-182021-01-24

    Do "this"?  Just to be absolutely sure (I've not had enough coffee yet), what is "this"?  Do you mean the conversion of an ISO week to an actual date range?

    Yes.  Conversion of ISO date parts to calendar date.  This code brought 25 points on Stack Overflow last week but I was kind of wondering/hoping someone had a better/newer way.  'iso_week' was added as a datepart but it doesn't seem to help with this

    Aus dem Paradies, das Cantor uns geschaffen, soll uns niemand vertreiben können

  • Thanks.  I'll look at it in a bit more detail later but my first impression is that the following line of code in the CROSSAPPLY is incorrect.

    cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk)*7)-1 as date)

    The gives you the start of the end date.  That will perpetuate the use of things like BETWEEN or doing the silly thing of adding 23:59:59 to the end date.  It would be MUCH better (IMHO) to remove the -1 at the end of the formula and make people understand that the "end date" is exclusive and is truly the first date when something is no longer true to support the much more correct criteria of WHERE SomeDateColumn >= StartDate and SomeDateColumn < EndDate (or NextStartDate, which is more technically and obviously correct).

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden wrote:

    Thanks.  I'll look at it in a bit more detail later but my first impression is that the following line of code in the CROSSAPPLY is incorrect.

    cast(dateadd(wk,datediff(wk,0,'01/04/'+iso.yr),0)+((iso.wk)*7)-1 as date)

    The gives you the start of the end date.  That will perpetuate the use of things like BETWEEN or doing the silly thing of adding 23:59:59 to the end date.  It would be MUCH better (IMHO) to remove the -1 at the end of the formula and make people understand that the "end date" is exclusive and is truly the first date when something is no longer true to support the much more correct criteria of WHERE SomeDateColumn >= StartDate and SomeDateColumn < EndDate (or NextStartDate, which is more technically and obviously correct).

    Ok thank you.  Awesome.  Yeah, recently I can recall where 23:59:59 was bluntly applied... the OP had other issues tho.  It should be one way yes.  On topic it seems datefromisoparts(iso_year, iso_month, ...) or something would be nice to have

    Aus dem Paradies, das Cantor uns geschaffen, soll uns niemand vertreiben können

  • DATEPART(ISO_WEEK, @thedate) ???


    N 56°04'39.16"
    E 12°55'05.25"

Viewing 15 posts - 61 through 75 (of 77 total)

You must be logged in to reply to this topic. Login to reply