By Taco van den Broek Why do we use ISO week numbering?
In our framework (including modules) we use the ISO 8601 standard for week numbering. We've chosen this standard because it is very related to western european workweeks: weeks start on monday and the first week of the year is the first week with a workday in it. Our clients are mostly businesses operating in western europe and therefore this standard is the most obvious choice.
Why this article?
Our framework is built using PHP. In PHP requesting the week and / or year number according to this standard is easy:
// Week and year number using date()
$w = date('W');
$y = date('o');
// Week and year number using strftime()
$w = strftime('%V');
$y = strftime('%G');
But in javascript no built-in methods are available to calculate these values.
ISO 8601 week numbering
This (part of the) standard primarily defines two rules:
- Weeks start on monday
- The first week of the year is the week with the first thursday of that year
An alternative to the second rule is that the first week of the year is the week with the 4th of January in it. This rule is mostly more convenient in coding.
As a result of this second rule the week with the 1st of January isn't always week number 1. Another result is that some years have 53 weeks instead of 52.
Extending the javascript Date class
To be able to get the week and year numbers in javascript we extend the Date class with two methods: getWeek() and getWeekYear().
Extending a class in javascript can be done by adding properties to the prototype object of that class. In the code below we define Date.prototype.getWeek() and Date.prototype.getYearWeek(), these new methods can be called directly on instances of the Date class:
var today = new Date();
alert("The current week number is: " + today.getWeek());
Date.getWeek()
First, take a look at the code below:
/**
* Get the ISO week date week number
*/
Date.prototype.getWeek = function () {
// Create a copy of this date object
var target = new Date(this.valueOf());
// ISO week date weeks start on monday
// so correct the day number
var dayNr = (this.getDay() + 6) % 7;
// Set the target to the thursday of this week so the
// target date is in the right year
target.setDate(target.getDate() - dayNr + 3);
// ISO 8601 states that week 1 is the week
// with january 4th in it
var jan4 = new Date(target.getFullYear(), 0, 4);
// Number of days between target date and january 4th
var dayDiff = (target - jan4) / 86400000;
// Calculate week number: Week 1 (january 4th) plus the
// number of weeks between target date and january 4th
var weekNr = 1 + Math.ceil(dayDiff / 7);
return weekNr;
}
The first thing we do is find out the difference in days between the requested date and the 4th of January that same (calendar) year. We subtract the two Date objects, which will result in a number of milliseconds, and divide that by the number of milliseconds in a day: 86400000. Since weeks always start on monday (rule #1) we are more interested in the difference in days between January 4th and the monday of the target week, so we correct this day difference. Next we convert this amount of days to weeks and add one (since it was the difference between week #1 and the target date).
This was the general part of the method, next we need to correct for the exceptions (remember that the 1st of January might be in a week of the previous year?). If the result of our method is less than 1, we know this date belongs to the last week of the previous year, simply return the result of the getWeek() method for December 31st of the previous year.
On the other hand the last few days of the year might belong to a week of the next year. Since at most 3 days can belong to the next year (remember that the first week is the week with January 4th) we only need to check this exception when the date is after December 28th. We return 1 when the monday of the requested week is after the 28th of December.
Date.getWeekYear()
That was the getWeek method, the most important one of the two. The getWeekYear() method uses getWeek() to determine what year to return.
This method is very simple, just return the calendar year except when:
- The requested date is in January but the getWeek() method returns 52 or 53
- The requested date is in December but the getWeek() method returns 1
The code below (and it's comments) speak for itself ;)
/**
* Get the ISO week date year number
*/
Date.prototype.getWeekYear = function ()
{
// Create a new date object for the thursday of this week
var target = new Date(this.valueOf());
target.setDate(target.getDate() - ((this.getDay() + 6) % 7) + 3);
return target.getFullYear();
}
So that's about all you'll need to calculate ISO 8601 year and week numbers in javascript, happy coding!
Optimisation
After reading the site from Dr J R Stockton that Rick recommended in his post I stole a simple trick to make the code a little shorter. Instead of handling the edge cases I first change the date to the thursday of the requested week so the week year number equals the calendar year. I did keep the code more verbose than Stockton did though, just so you understand what exactly happens.