Example on how to calculate the week number of the year from a date

joey.jeremiah

ProgressTalk Moderator
Staff member
Code:
function WeekNumber returns integer ( ptDate as date ):

    define var iDay as int no-undo.

    iDay = ptDate - date( 1, 1, year( ptDate ) ) + 1.   /* days from the beginning of the year */

    return int( trunc( iDay / 7, 0 ) )                  /* weeks past */

        + ( if iDay mod 7 <> 0 then 1 else 0 ).         /* if entered another week also count that week */

end function. /* WeekNumber */



function QuarterNumber returns integer ( ptDate as date ):

    define var iMonth as int no-undo.

    iMonth = month( ptDate ).

    return int( trunc( iMonth / 3, 0 ) )                /* quarters past */

        + ( if iMonth mod 3 <> 0 then 1 else 0 ).       /* if entered another quarter also count that quarter */

end function. /* QuarterNumber */

Hope this helps.
 

kolonuk

Member
This actually calculates from the first day of the year... For example, if the frist day of the year falls on a wednesday, that becomes the first day of the "week", and calculations are done against that...

This should do the trick, and I think it is ISO compliant (at least, what I could test)...

Code:
FUNCTION ISOWeekDay returns INTEGER ( ptDate AS DATE ):
	DEF VAR v_Weekday   AS INTE EXTENT 7 NO-UNDO INITIAL [7,1,2,3,4,5,6].
	RETURN v_Weekday[WEEKDAY(ptdate)].
END FUNCTION.

FUNCTION ISOWeekNumber returns INTEGER ( ptDate AS DATE ):
/* Returns the ISO week number 
   Don't use the progress function WEEKDAY, as that starts on Sunday when the ISO weeks start on Monday! */
	DEF VAR v_Offset    	AS INTE NO-UNDO.
	DEF VAR v_Days      	AS INTE NO-UNDO.
	DEF VAR v_WeekNumber	AS INTE NO-UNDO.

	IF ISOWeekday(ptDate) LT 5 THEN v_offset = 1.			/* Offset by Thursdays */
	v_Days = ptDate - DATE( 1, 1, year( ptDate ) ) + 1.			/* get number of days */
	v_WeekNumber = INTE(ROUND(v_days / 7,0 )) + v_offset.		/* calculate week number */
	IF v_WeekNumber = 53 AND v_OffSet = 1 THEN v_WeekNumber = 1.    /* last week of the year is actually the first week of the next year */
	IF v_WeekNumber = 0 AND v_OffSet = 0 THEN v_WeekNumber = 53.    /* First week in the year is actually the last week of the previous year */
	RETURN v_WeekNumber.

END FUNCTION. /* ISOWeekNumber */

Comments, suggestions?
 

scatirau

New Member
Code:
function WeekNumber returns integer ( ptDate as date ):

    define var iDay as int no-undo.

    iDay = ptDate - date( 1, 1, year( ptDate ) ) + 1.   /* days from the beginning of the year */

    return int( trunc( iDay / 7, 0 ) )                  /* weeks past */

        + ( if iDay mod 7 <> 0 then 1 else 0 ).         /* if entered another week also count that week */

end function. /* WeekNumber */



function QuarterNumber returns integer ( ptDate as date ):

    define var iMonth as int no-undo.

    iMonth = month( ptDate ).

    return int( trunc( iMonth / 3, 0 ) )                /* quarters past */

        + ( if iMonth mod 3 <> 0 then 1 else 0 ).       /* if entered another quarter also count that quarter */

end function. /* QuarterNumber */

Hope this helps.

Thank you very much! This did the trick for me!
 

scatirau

New Member
This actually calculates from the first day of the year... For example, if the frist day of the year falls on a wednesday, that becomes the first day of the "week", and calculations are done against that...

This should do the trick, and I think it is ISO compliant (at least, what I could test)...

Code:
instead of ROUND(v_days / 7,0 ) you should be using TRUNC(v_days/7,0)
FUNCTION ISOWeekDay returns INTEGER ( ptDate AS DATE ):
	DEF VAR v_Weekday   AS INTE EXTENT 7 NO-UNDO INITIAL [7,1,2,3,4,5,6].
	RETURN v_Weekday[WEEKDAY(ptdate)].
END FUNCTION.

FUNCTION ISOWeekNumber returns INTEGER ( ptDate AS DATE ):
/* Returns the ISO week number 
   Don't use the progress function WEEKDAY, as that starts on Sunday when the ISO weeks start on Monday! */
	DEF VAR v_Offset    	AS INTE NO-UNDO.
	DEF VAR v_Days      	AS INTE NO-UNDO.
	DEF VAR v_WeekNumber	AS INTE NO-UNDO.

	IF ISOWeekday(ptDate) LT 5 THEN v_offset = 1.			/* Offset by Thursdays */
	v_Days = ptDate - DATE( 1, 1, year( ptDate ) ) + 1.			/* get number of days */
	v_WeekNumber = INTE(ROUND(v_days / 7,0 )) + v_offset.		/* calculate week number */
	IF v_WeekNumber = 53 AND v_OffSet = 1 THEN v_WeekNumber = 1.    /* last week of the year is actually the first week of the next year */
	IF v_WeekNumber = 0 AND v_OffSet = 0 THEN v_WeekNumber = 53.    /* First week in the year is actually the last week of the previous year */
	RETURN v_WeekNumber.

END FUNCTION. /* ISOWeekNumber */

Comments, suggestions?

You should use TRUNC instead of ROUND.
 

kolonuk

Member
You know, you're absolutely right - Thanks! Here is code with the changes:

Code:
FUNCTION ISOWeekDay returns INTEGER ( ptDate AS DATE ):
	DEF VAR v_Weekday   AS INTE EXTENT 7 NO-UNDO INITIAL [7,1,2,3,4,5,6].
	RETURN v_Weekday[WEEKDAY(ptdate)].
END FUNCTION.

FUNCTION ISOWeekNumber returns INTEGER ( ptDate AS DATE ):
/* Returns the ISO week number 
   Don't use the progress function WEEKDAY, as that starts on Sunday when the ISO weeks start on Monday! */
	DEF VAR v_Offset    	AS INTE NO-UNDO.
	DEF VAR v_Days      	AS INTE NO-UNDO.
	DEF VAR v_WeekNumber	AS INTE NO-UNDO.

	IF ISOWeekday(ptDate) LT 5 THEN v_offset = 1.			/* Offset by Thursdays */
	v_Days = ptDate - DATE( 1, 1, year( ptDate ) ) + 1.			/* get number of days */
	v_WeekNumber = INTE(TRUNC(v_days / 7,0 )) + v_offset.		/* calculate week number */
	IF v_WeekNumber = 53 AND v_OffSet = 1 THEN v_WeekNumber = 1.    /* last week of the year is actually the first week of the next year */
	IF v_WeekNumber = 0 AND v_OffSet = 0 THEN v_WeekNumber = 53.    /* First week in the year is actually the last week of the previous year */
	RETURN v_WeekNumber.

END FUNCTION. /* ISOWeekNumber */
 

scatirau

New Member
The logic doesn't seem to be correct, because this is what I am getting between years 08/09

│12/15/08 51│
│12/16/08 51│
│12/17/08 51│
│12/18/08 51│
│12/19/08 50│
│12/20/08 50│
│12/21/08 50│
│12/22/08 52│
│12/23/08 52│
│12/24/08 52│
│12/25/08 52│
│12/26/08 51│
│12/27/08 51│
│12/28/08 51│
│12/29/08 1│
│12/30/08 1│
│12/31/08 1│

│01/01/09 1│
│01/02/09 53│
│01/03/09 53│
│01/04/09 53│
│01/05/09 1│
│01/06/09 1│
│01/07/09 2│
│01/08/09 2│
│01/09/09 1│
│01/10/09 1│
│01/11/09 1│
│01/12/09 2│
│01/13/09 2│
│01/14/09 3│
│01/15/09 3│
 

kolonuk

Member
Oddly, your right! On checking the actual code that I use, I found that it is slightly different in it's calculations. Not sure where the code above came from, but this below is pasted directly from what we use:

Code:
FUNCTION ISOWeekDay returns INTEGER ( ptDate AS DATE ):
	DEF VAR v_Weekday   AS INTE EXTENT 7 NO-UNDO INITIAL [7,1,2,3,4,5,6].
	RETURN v_Weekday[WEEKDAY(ptdate)].
END FUNCTION.


FUNCTION ISOWeekNumber returns INTEGER ( ptDate AS DATE ):
/* Returns the ISO week number 
   Don't use the progress function WEEKDAY, as that starts on Sunday when the ISO weeks start on Monday! */
	DEF VAR v_Days      	AS INTE NO-UNDO.
	DEF VAR v_WeekNumber	AS INTE NO-UNDO.

	DEF VAR v_WeekMonday	AS DATE NO-UNDO.
	DEF VAR v_WeekThursday	AS DATE NO-UNDO.
	DEF VAR v_WeekSunday	AS DATE NO-UNDO.

	v_WeekMonday	= ptDate - ISOWeekDay( ptDate ) + 1.	/* Start of week */
	v_WeekThursday	= v_WeekMonday + 3.
	v_WeekSunday	= v_WeekMonday + 6.

	v_Days = v_WeekMonday - DATE( 1, 1, YEAR( ptDate ) ).	/* get number of days for monday*/
	v_WeekNumber = INTE(ROUND(v_Days / 7,0 ) ) + 1.		/* calculate week number */

	IF v_WeekNumber = 53 THEN DO				/* work where the overlapping week sits */
	:
		IF YEAR(v_WeekThursday) = YEAR(ptDate)
		THEN v_WeekNumber = 53.
		ELSE v_WeekNumber = 1.
	END.
	IF v_WeekNumber = 0 THEN v_WeekNumber = 53.

	RETURN v_WeekNumber.

END FUNCTION. /* ISOWeekNumber */

Also, The ROUND in this needs to be there, not TRUNCATE - check the weeks returned around the year end.

Let me know how that goes...
 

bhuwan

New Member
I'm new to progress and checking out the solution but still the logic doesn't seem correct
For date 01/01/2022 it should give 52 (in ISO date) but actually it gives 53. Please correct me if I'm wrong.

can anyone provide a solution that can actually gives ISO week number
thanks in advance..
 

Cringer

ProgressTalk.com Moderator
Staff member
I'm new to progress and checking out the solution but still the logic doesn't seem correct
For date 01/01/2022 it should give 52 (in ISO date) but actually it gives 53. Please correct me if I'm wrong.

can anyone provide a solution that can actually gives ISO week number
thanks in advance..
Are you working on a Windows or *nix solution?
 

Den Duze

Member
I'm not saying this is the way to do it but as fas as I know this works fine
Code:
    /* own construction!! based on ISO-8601 rules for weeks of the year (week always starts with a Monday)
                          week 1 is the week with the first Thursday of the year
                          Verified output with https://www.calendar-12.com/week_number
    */                     
    define variable v-DAfirst as date    no-undo.
    define variable v-NRweek  as integer no-undo.
   
    /* ISO-rule (also see Online calculator: Week number of a given date
       The first week of the year is the week that includes the first thursday of the year or week with the 4 jan in it */
    assign v-DAfirst = date(substitute("01/01/&1", year(p-DAin))).
    if weekday(v-DAfirst) eq 1 or weekday(v-DAfirst) gt 5 then
      assign v-DAfirst = date(substitute("4/1/&1", year(p-DAin))). /* 4 jan always falls in the first week of the year */
   
    /* if the provided date is earlier then the Monday of the calculated first week of the year of the requested date
       then search for the first day of the previous year because it will be week 52/53 */
    if p-DAin < v-DAfirst then
      do:
        assign v-DAfirst = date(month(p-DAin), 1, year(p-DAin) - 1).
        if weekday(v-DAfirst) eq 1 or weekday(v-DAfirst) gt 5 then
          assign v-DAfirst = date(substitute("4/1/&1", year(v-DAfirst))). /* 4 jan always falls in the first week of the year */
      end. 
 
    assign v-DAfirst = v-DAfirst - weekday(v-DAfirst) + 2
           v-NRweek  = truncate(interval(p-DAin, v-DAfirst, "day") / 7, 0) + 1.

    /* when the calculated week is 53 and the month of the requested date is december (could also be januari)
       then check is it's not the first week of the next year */
    if v-NRweek = 53 and month(p-DAin) eq 12 then
      do:
        assign v-DAfirst = date(substitute("01/01/&1", year(p-DAin) + 1)).
        if weekday(v-DAfirst) gt 5 then
          assign v-DAfirst = date(substitute("4/1/&1", year(p-DAin))). /* 4 jan always falls in the first week of the year */
     
        if truncate(interval(p-DAin, v-DAfirst, "day") / 7, 0) + 1 eq 1 then
          assign v-NRweek = 1.
      end.
 
Last edited by a moderator:

Stefan

Well-Known Member
Repeatedly dating a substituted string with an assumed session DMY format is asking for trouble.
Use the date function with three integer inputs for month, day and year, it will work regardless of the session settings:
 

bhuwan

New Member
hey Den... normally it seems fine. correct me if I'm wrong but for date 01/03/2022 it should give 1 but it gives 53
thank you.
 

Den Duze

Member
Repeatedly dating a substituted string with an assumed session DMY format is asking for trouble.
Use the date function with three integer inputs for month, day and year, it will work regardless of the session settings:
I agree but for me that is not an issue ... we are sure about the se
hey Den... normally it seems fine. correct me if I'm wrong but for date 01/03/2022 it should give 1 but it gives 53
thank you.
mhhhh, indeed ...

Try by moving the line:
assign v-DAfirst = v-DAfirst - weekday(v-DAfirst) + 2
to just before the line:
/* if the provided date is earlier then the Monday of the calculated first week of the year of the requested date

Keep me informed
 

Cecil

19+ years progress programming and still learning.
If you are using Windows as your OS then you can use the .NET inbuilt methods for getting the week number.

Code:
// SOURCE https://docs.microsoft.com/en-us/dotnet/api/system.globalization.calendar.getweekofyear?view=net-6.0

using System.*.
using System.Globalization.*.

function getWeekNumber returns integer (input pDateTime as datetime ):

    DEFINE VARIABLE myCI as class CultureInfo NO-UNDO.
    DEFINE variable myCal as class Calendar NO-UNDO.
    DEFINE VARIABLE myCWR as class CalendarWeekRule NO-UNDO.
    DEFINE VARIABLE myFirstDOW as class DayOfWeek NO-UNDO.
   
// Gets the Calendar instance associated with a CultureInfo.
    myCI = new CultureInfo("en-NZ").
    myCal = myCI:Calendar.

    // Gets the DTFI properties required by GetWeekOfYear.
    myFirstDOW = myCI:DateTimeFormat:FirstDayOfWeek.
    myCWR = myCI:DateTimeFormat:CalendarWeekRule.
   
    return myCal:GetWeekOfYear( pDateTime , myCWR, myFirstDOW ). 
   
    finally:
   
        if valid-object(myCI) Then
            delete object myCI.
   
    end.

end function.


message "Date:" string(today,"99/99/9999") skip
"Week Number:"getWeekNumber(input datetime(today) )
    view-as alert-box info title "Get Week Number".
 
Last edited:

Cecil

19+ years progress programming and still learning.
Same code as above, but it overrides the system's default 'CalendarWeekRule'.

Code:
// SOURCE https://docs.microsoft.com/en-us/dotnet/api/system.globalization.calendar.getweekofyear?view=net-6.0

using System.*.
using System.Globalization.*.

function getWeekNumber returns integer (input pDateTime as datetime ):

    DEFINE VARIABLE myCI as class CultureInfo NO-UNDO.
    DEFINE variable myCal as class Calendar NO-UNDO.
    DEFINE VARIABLE myCWR as class CalendarWeekRule NO-UNDO.
    DEFINE VARIABLE myFirstDOW as class DayOfWeek NO-UNDO.
   
// Gets the Calendar instance associated with a CultureInfo.
    myCI = new CultureInfo("en-NZ").
    myCal = myCI:Calendar.

    // Gets the DTFI properties required by GetWeekOfYear.
    myFirstDOW = myCI:DateTimeFormat:FirstDayOfWeek.   // FirstDayOfWeek System Defined
    myCWR = myCI:DateTimeFormat:CalendarWeekRule.      // CalendarWeekRule System Defined
   
    //Debug
    // message myFirstDOW:ToString().
    // message myCWR:ToString().
   
    // Override the systems CalendarWeekRule
   
    // myCWR = CalendarWeekRule:FirstDay.             // Indicates that the first week of the year starts on the first day of the year and ends before the following designated first day of the week.
    // myCWR = CalendarWeekRule:FirstFourDayWeek.   //Indicates that the first week of the year is the first week with four or more days before the designated first day of the week.
    myCWR = CalendarWeekRule:FirstFullWeek.         // Indicates that the first week of the year begins on the first occurrence of the designated first day of the week on or after the first day of the year
   
    // message myCWR:ToString().
   
   
    return myCal:GetWeekOfYear( pDateTime , myCWR, myFirstDOW ).
   
    finally:
   
        if valid-object(myCI) Then
            delete object myCI.
   
    end.

end function.



message "Date:" string(today,"99/99/9999") skip
"Week Number:"getWeekNumber(input datetime(today) )
    view-as alert-box info title "Get Week Number".
 

Den Duze

Member
mhhh, I'm (almost) sure you're code is correct and I want to use it as a check for the output of my code (mine needs to work on Windows and Linux) but while checking I see that
Your code returns 53 for 31/12/19
My code returns 1 for 31/12/19
When I check on https://www.calendar-12.com/week_number (yes I know .. who says that that one is correct) I also get weeknumber 1 back for 31/12/19 when using ISO8601 rules.
I also checked a few other sites and ocx controls and as far as I see 31/12/19 should be 1? So I guess something is wrong in your example
 
Last edited:
Top