Fun with PHP Calendars: Part 1
Recently I've writted up some functions using the various date related functions built into PHP to create some calendars and other listings. Here's a method for displaying a list of weeks for a particular event.
Recently I had to restyle a fairly elaborate web page calendar as part of my work. The original version was written in straight html, and the page would need to be rewritten at least once a year, so naturally I decided to recode it as a dynamic PHP page to save on future maintenance. One major feature of this calendar was listing the weeks of any given semester which was to be placed alongside a traditional calendar display. My approach was to write a function with the first and last dates as the key parameters, and then let PHP do the rest of the working out. The result is a simple script that will display a range of values for each week between a starting and end date - for instance, the date of Monday of each week, the calendar week, and so on.
The completed function is attached to the end of this article, but I'll break it down some of the key elements as we go. Firstly we need to make sure the dates are in a format that PHP can use:
$start_ts = strtotime($start);
$end_ts = strtotime($end);
The advantage of the above method is that we can provide the date parameters in almost any format and the strtotime() function will convert the date into a usable timestamp. I usually provide a MySQL formatted date (e.g. '2010-09-23') but you can use a string instead (e.g. 'September 23 2010' or even '23 Sept 2010').
Next we make sure that the end date comes after the start date, otherwise all sorts of horrible things are likely to happen:
if($start_ts > $end_ts) {
exit();
}
Now we use the timestamps we created above to define a few handy variables:
$total_time = $end_ts - $start_ts;
$total_weeks = round($total_time/604800);
$start_w = date('W',$start_ts);
$end_w = date('W',$end_ts);
The first line gives us the total number of seconds between our starting date and our ending date. The second line converts that amount of seconds into the number of weeks, there being 604800 seconds in a week, and rounds it to the nearest whole number of weeks. Our last two lines work use the 'W' parameter of the PHP date() function to give us the ISO week number (a value between 1 and 53) of the starting and ending weeks. Now that we have these values we can create a loop that will give us a whole slew of additional values pertaining to the intervening weeks.
We start our loop like this:
for($i = 0; $i < $total_weeks; ++$i) {
Our starting value is 0, our upper limit is 1 less than the total number of weeks (since we're starting at 0 rather than 1). Since $i represents each week and we want to increment by one week for each iteration of the loop we simply increment $i by 1 each time we go through the loop. Basic stuff.
Here we work out some variables for each week as we go through the loop
$w_ts = strtotime($start.' + '.$i.' weeks');
$w = date('W',$w_ts); // ISO or calendar week
$w = str_pad($w, 2, '0', STR_PAD_LEFT);
$Y = date('Y',$w_ts); // year
$event_week = ($i+1); // event week number
The first line works out the timestamp for the week by adding the appropriate number of weeks to the starting date (not the timestamp, you'll note). Say our event starting date is 'September 23, 2010', the string generated for the first iteration of the loop will be 'September 23, 2010 + 0 weeks', the second iteration will be 'September 23, 2010 + 1 weeks', and so on. Once again, strtotime() will convert the string into a usable timestamp.
On the second line we work out the ISO week number - this is useful if you want to display the listing generated by this function alongside a conventional calendar, whereby you can compare the week number value to the week number on the main calendar. On the fourth line we add a preceding zero if the week number is less than 10 (this is so the next bit doesn't fail on us). The fourth line calculates the year, in case we have events that run over more than one year, or that simply span through December and January. On the fifth line we just have the week number of the event itself, so the first week will be Week 1, the second week is Week 2, and so on.
For the last significant step I place all of this stuff into an array:
$weeks[$event_week] = array(
'title' => $title,
'calendar_week' => (int)$w, // cast as integer (optional)
'event_week' => $i,
'year' => $Y,
'mon_ts' => strtotime($Y.'W'.$w),
'sun_ts' => strtotime($Y.'W'.$w.'7')
);
I use the event week as the array key so I can quickly access details for, say, week 7 if I need to. The first four values in the array have already been covered (with $title defined, optionally, as one of the function parameters).
The last two elements give us the timestamp for the Monday and Sunday, respectively, of the particular week. This uses a slightly lesser known string parameter for strtotime whereby we specify the ISO week number of a year, and the number of days into that week (with day 0 being Monday). For the Monday of ISO week 23 of 2010 the string would look like this: '2010W23' (note that we don't need to specify a day value as the default is day 0, or Monday). For the Sunday we would use: '2010W237' (you can optionally separate the values with hyphens, e.g. '2010-W23-7', the most important thing is that the week number has to be 2 digits otherwise calculations for weeks 1-9 will fail). Also take note that this last option is only available in PHP5 and up.
And that's it - the completed script is available as a text file below. Bear in mind that you can set up any values you want in the $weeks array. For instance, I just return the timestamp for the Monday and Sunday, but you can return a formatted date if you prefer. Or you can return the Wednesday if, for instance, you have a weekly meeting that always falls on a Wednesday. If you want even more information for specific days within the week you could always use the getdate() function. I've not covered getdate() here as it's not all that useful when you're looking at weeks rather than specific days, but it's out there if you want it.
As ever, please let me know if you pick up any bugs in the script, have some suggestions to improve it, or generally find it useful.