0

I need a round number of weeks per months as per the ISO standard.

Also I'm using momentJS rather than Js's Date.

according to ISO date formatting the week counts for that month if it has a thursday otherwise it counts for the previous month.

according to that the months who's rounded week count are 5 this year (2018) are :

Mars, May, August and November

the rest are said to have 4 weeks.

Here's my code (inspiration here):

EDIT if I don't apply the suggested fix in the comments, I end up with 1 week for the month of December for 2018, same for 2017.

ngOnInit() {
  this.generateTimelineForGivenYear('2018');
}

generateTimelineForGivenYear(year){
  for (let i = 1; i < 13; i++) {
    let month;
    if (i < 10) month = '0'.concat(i.toString());
    else month = i;
    this.howManyWeeksForGivenMonth(moment(year + '-'+ month + '-05'));
  }
}

howManyWeeksForGivenMonth(myDate){
  console.log('The Month : ', myDate.month() + 1);
  const start = myDate.clone().startOf('month').week();
  let end = myDate.clone().endOf('month').week();
  if( start > end ){ end = 52 + end }
  const res =  end - start;
  console.log('The Number of Weeks: ', res);
  console.log('----------------------------------------------------');
}

my result :

The Month :  1
The Number of Weeks:  4
----------------------------------------------------
The Month :  2
The Number of Weeks:  4
----------------------------------------------------
The Month :  3
The Number of Weeks:  4
----------------------------------------------------
The Month :  4
The Number of Weeks:  4
----------------------------------------------------
The Month :  5
The Number of Weeks:  4
----------------------------------------------------
The Month :  6
The Number of Weeks:  4
----------------------------------------------------
The Month :  7
The Number of Weeks:  4
----------------------------------------------------
The Month :  8
The Number of Weeks:  4
----------------------------------------------------
The Month :  9
The Number of Weeks:  5
----------------------------------------------------
The Month :  10
The Number of Weeks:  4
----------------------------------------------------
The Month :  11
The Number of Weeks:  4
----------------------------------------------------
The Month :  12
The Number of Weeks:  5
----------------------------------------------------

as you can see I get two months that are 5 weeks and not the right ones either :

September and December

I tried this as well :

howManyWeeksForGivenMonth(myDate){
  console.log('The Month : ', myDate.month() + 1);
  const first = myDate.day() == 0 ? 6 : myDate.day()-1;;
  const day = 7-first;
  const last = myDate.daysInMonth();
  let res = Math.floor((last-day)/7);
  if ((last-day) % 7 !== 0) res++;
  console.log('The Number of Weeks: ', res);
  console.log('----------------------------------------------------');
}

which gave :

The Month :  1
The Number of Weeks:  4
----------------------------------------------------
The Month :  2
The Number of Weeks:  3
----------------------------------------------------
The Month :  3
The Number of Weeks:  4
----------------------------------------------------
The Month :  4
The Number of Weeks:  4
----------------------------------------------------
The Month :  5
The Number of Weeks:  5
----------------------------------------------------
The Month :  6
The Number of Weeks:  4
----------------------------------------------------
The Month :  7
The Number of Weeks:  4
----------------------------------------------------
The Month :  8
The Number of Weeks:  5
----------------------------------------------------
The Month :  9
The Number of Weeks:  4
----------------------------------------------------
The Month :  10
The Number of Weeks:  4
----------------------------------------------------
The Month :  11
The Number of Weeks:  4
----------------------------------------------------
The Month :  12
The Number of Weeks:  4
----------------------------------------------------

...not better.

what am I doing wrong?

UPDATE :

once figuring out me not cutting at Thursday was the issue, here's my new attempt :

howManyWeeksForGivenMonth(myDate){
    console.log('The Month ', myDate.month() + 1 );

    let weeks = 4
    if(myDate.clone().startOf('month').weekday() >= 5 ){
        weeks ++;
        console.log('first week extra');
    }

    if(myDate.clone().endOf('month').weekday() < 5 ) {
        weeks ++;
        console.log('last week extra');
    }

    return weeks;
}

I get :

The Month  1
last week extra
5
The Month  2
last week extra
5
The Month  3
4
The Month  4
last week extra
5
The Month  5
last week extra
5
The Month  6
first week extra
5
The Month  7
last week extra
5
The Month  8
4
The Month  9
first week extra
last week extra
6
The Month  10
last week extra
5
The Month  11
4
The Month  12
first week extra
last week extra
6

which there's nothing to read into here it's just plain garbage.

Obviously I get that both ifs should never be triggered together for one month but I had supposed that would simply not happen.

I supposed wrong but besides that it still doesn't correspond to the right months. so what is it now?

tatsu
  • 2,316
  • 7
  • 43
  • 87
  • I've got it: I'm using month start and month end instead of counting from and before Thursdays. That's what's causing the difference. Now I just need to figure out how to tell whether the given week has crossed Thursday or not. – tatsu May 03 '18 at 13:25
  • I found this : https://marcin-chwedczuk.github.io/get-day-of-week-from-date – tatsu May 03 '18 at 13:37
  • ah MometJS has a `weekday()` function! much easier! – tatsu May 03 '18 at 13:54

2 Answers2

1

I don't follow your method of determining the number of weeks in the month. My approach is to count the number of days left after the first Thursday: if there are 28 or more, there are 5 weeks. Otherwise, there are 4.

/* @param {number} year - full year
** @param {number} month - calendar month number (Jan = 1, Feb = 2, etc.)
** @returns {number} weeks in month
*/
function weeksInMonth(year, month) {
  // Create a Date for the given year and month
  var d = new Date(year, month-1, 1);
  // Get the number of days in the month
  var daysInMonth = new Date(year, month, 0).getDate();
  // Get date of first Thursday
  var firstThuDate = (11 - d.getDay()) % 7 + 1;
  // Get number of days in month after 1st Thursday
  var daysLeft = daysInMonth - firstThuDate;
  // If there are 28 or more days left, there are 5 weeks
  // Otherwise, there are 4
  return daysLeft > 27? 5 : 4;
}

// Weeks in 2018 months
for (var i=0; i<12; i++) {
  var monthName = new Date(2018,i).toLocaleString(undefined, {month:'short'});
  console.log(monthName + ' 2018: ' + weeksInMonth(2018,i+1));
}

Of course you can reduce the above to less code, it's written for clarity of the algorithm.

The same algorithm using moment.js:

// Count ISO weeks in month
function weeksInMonth(year, month) {
  // Create moment object
  var d = moment([year, month - 1, 1]);
  // Get date of first Thursday
  var firstThu = d.weekday(4).date();;
  // Subtract from last day of month
  var daysLeft = d.endOf('month').date() - firstThu;
  // If days left > 27, there are 5 weeks
  // Otherwise there are 4
  return daysLeft > 27 ? 5 : 4;
}

// Weeks in 2018 months
for (var i=0; i<12; i++) {
  var monthName = new Date(2018,i).toLocaleString(undefined, {month:'short'});
  console.log(monthName + ' 2018: ' + weeksInMonth(2018,i+1));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.1/moment.min.js"></script>
RobG
  • 142,382
  • 31
  • 172
  • 209
0

There are some properties to week based calendars. The fourth day is always part of both week based and month based calendars "month" or "year". Same for the fourth day before the end of the time period.

function getWeeksInMonth(year, month)
{
    // 4th day of the month
    const start_date = new Date(year, month, 4);
    // 1st day of next month -4 day
    const end_date = new Date(year, month + 1, -3);
    // (0 || 7) => 7, it's a fix: ISO sundays have a number of 7, not 0
    // how much days must be added at the beginning
    const start_delta = (start_date.getDay() || 7) - 1;
    // how much days must be added at the end
    const end_delta = 7 - (end_date.getDay() || 7);
    // how much days are between the two dates
    // replace knowing if the month is 28/29/30/31 days long
    const between_delta = Math.round((end_date - start_date) / 86400000);
    // divided by 7 to get the number of weeks
    return Math.floor((start_delta + between_delta + end_delta) / 7);
}
Kulvar
  • 1,139
  • 9
  • 22