Several years ago I wrote a post about how to calculate the ISO 8601 week number of a given date without using an external library like Moment.js and without using ugly implementation details such as seconds since 1970. It worked, but it wasn't the most elegant code ever. It used a while loop. Maybe it's just me, but I don't get along well with while loops. I always get the while condition wrong at first, or I forget to increment or decrement something, and I end up with a loop that lasts forever or crashes my program.
An alternative for a while loop is recursion. In this post, I'll examine what happens if I change the iterative function of that old post to a recursive one.

The original iterative function

To recapture, the algorithm of the original function was:
  1. Given a date d, adapt this date to Thursday of the same week. 
  2. Subtract 1 week from this date and repeat this until the result is in the previous year. 
  3. The week number for date d is the number of subtractions needed in the previous step to get to the previous year. 
Here is a slightly modified version of the original code:

function createThursdayDate(date) {
    "use strict";
    let dayOfWeek = date.getDay();
    let newDate = new Date(date);
    newDate.setHours(12);
    if (dayOfWeek != 4) {
        let addDays = (dayOfWeek == 0 ? -3 : 4 - dayOfWeek);
        newDate.setDate(newDate.getDate() + addDays);
    }
    return newDate;
}

function getWeekNumber(date) {
    "use strict";
    let modifiedDate = createThursdayDate(date);
    let year = modifiedDate.getFullYear();
    let weekCount = 0;
    do {
        modifiedDate.setDate(modifiedDate.getDate() - 7);
        weekCount++;
    } while (modifiedDate.getFullYear() == year);
    return weekCount;
}

I've encapsulated step 1 of the algorithm in the auxiliary function createThursdayDate. First it creates a new date which is a copy of date. This is necessary because the JavaScript Date object methods change their own Date object, and we don't want to change our input variable date. Then, it sets the new date's hour to 12. This just is to avoid potential issues when the time of the original date is around midnight and overflow might occur as we subtract days and we pass days with one hour less because of daylight saving time. Finally it adapts the new date to be on Thursday in the same ISO 8601 week as the original date.

The function getWeekNumber itself hasn't fundamentally changed. It calls the new createThursdayDate, and then performs steps 2 and 3 of the algorithm like before, using a while loop.

A new recursive function

A recursive function is a function that calls itself. Generally, it knows how to solve a problem for one very simple case (or for several simple cases). For more complicated cases, it calls itself with slightly simplified parameters and combines the result with the original parameters to get the final result. In our case, the simple case is that the date is in the first week of the year. That implies that the week's Thursday is the first Thursday of the year. If that is the case, the function just returns 1. In all other cases, it calls itself with a date that is one week earlier than the input date and adds 1 to the result. This would translate into JavaScript code like this:
function getWeekNumberRec(date) {
    "use strict";
    let modifiedDate = createThursdayDate(date);
    let year = modifiedDate.getFullYear();
    modifiedDate.setDate(modifiedDate.getDate() - 7);
    if (modifiedDate.getFullYear() < year) return 1;
    else return getWeekNumberRec(modifiedDate) + 1;
}

One nice thing about this recursive version of the function is that the code is shorter than in the iterative version. To me it's also easier to read, but that probably differs from person to person.

What's less nice is that this function is less efficient than the original one. In each call to getWeekNumberRec, a new Date object is created and adapted to be on Thursday. The original function does this only once, but the new one does it for every week.
We can remedy this by creating the Thursday date before calling the recursive function. Then, we pass it as a parameter to the recursive function.

Another problem is a more general issue with recursive functions: they crash if the recursion goes too deep. For each recursive call, the system has to keep in memory the state of the calling function. In our recursive function, this means the system remembers modifiedDate and year until the recursive call has ended and it has added 1 to the result. Only then, after doing the return, it frees up these variables. So each recursive call introduces a new set of variables that need to be remembered. For this particular function, that won't be much of an issue, because the recursion won't ever go deeper than 53 calls (because a year never has more than 53 weeks), but it still uses a lot more memory than the iterative function.
In the future, or now if you use Safari, this issue can be solved by applying tail recursion.

A tail recursive function

In tail recursion, the recursive call is the last thing that the function does. The recursive function above is not tail recursive, because it still has to add 1 after calling getWeekNumberRec. If it were tail recursive, it would be able to benefit from tail call optimization. Tail call optimization is a nice trick that the compiler/interpreter can do when it sees a call at the end of a function with nothing after it. If such a call is performed, then all variables and other state (often called the stack) of the calling function are no longer needed. They are cleared then and reused by the called function. For JavaScript, this technique is currently only implemented in Safari, but EcmaScript 2015 specifies it, so it should come to other browsers as well.

If we rewrite our recursive function to be tail recursive, while also applying the optimization of only calling createThursdayDate once before entering the recursion, we will end up with something like this:

function getWeekNumberTailRec(date) {
    "use strict";
    let loop = function (date, acc, year) {
        date.setDate(date.getDate() - 7);
        if (date.getFullYear() < year) return acc;
        return loop(date, acc + 1, year);
    }
    let modifiedDate = createThursdayDate(date);
    let year = modifiedDate.getFullYear();
    return loop(modifiedDate, 1, year);
}
The function getWeekNumberTailRec itself isn't actually recursive, but it calls an internal function loop which is tail recursive. To avoid having to add 1 after the recursive call, we pass the intermediate result as parameter acc. All of this does make things more complicated, so now the code is not shorter anymore than the original iterative function. I guess it's a matter of taste which one you prefer.