January 24, 2007

A DIY calendar control in PHP

Author: Donald W. McArthur

As a former ASP.Net coder, I've missed the convenience of Microsoft's built-in Calendar Control since I switched to doing Web site development in PHP. On a recent project I needed the ability to display a calendar with dates serving as hyperlinks to selected database items. I decided to use the opportunity to write some portable PHP code that I could use in other projects.

My design goals were to create a PHP page that would take as input a querystring value in the form of a Unix epoch number that would represent the beginning moment of a particular date. (I chose the Unix epoch number, which represents the number of seconds that have transpired since the start of January 1, 1970, as that was the data my database SQL statement used as a SELECT criteria.) The script would determine the month and year of that value, and create an array holding a Unix epoch number for the beginning moment for each day in that month. The script would then output HTML to display a calendar, with each date a hyperlink back to the original PHP page, with the associated querystring value for that date.

The code turned out to be surprisingly compact, and should be easily customizable for anyone's particular needs. I chose an HTML embedded table to hold the calendar output, as I've found some difficulties using a grid of Cascading Style Sheet (CSS) settings to hold closely spaced data when it contains hyperlinks. I did use CSS for font selection and on-page positioning of the embedded table. These are the CSS settings I used for the hyperlink and font rendering:


/* Calendar hyperlinks. */
a:link { color : #B22222; text-decoration: none; }
a:visited { color : #B22222; text-decoration : none; }
a:hover { color : #000080; text-decoration : none; }

/* Calendar font. */
.cal_font {
font-family: "Bitstream Vera Sans", arial, helvetica, sans-serif;
font-variant: small-caps;
font-size: 70%;
color: #OOOOOO;
}

The PHP code

For the PHP code, I started with the code used to handle the querystring, which is appended to the URL in this format:


http://www.yoursite.com/calendar.php?dts=1167631200

The code to handle the querystring:


<?php
// PHP Calendar Control.
// Use the querystring as a Unix epoch number
// for our initial calendar display.
$qstring = $_GET['dts'];
// If there is no querystring value,
// use the first moment of the first
// day of this month as the epoch number.
if (empty ($qstring)){
        $ts = mktime (0, 0, 0, date ('n'), 1, date ('Y'));
} else {
        // First make sure the querystring is numeric only.
        if (is_numeric($qstring)){
                $ts = $qstring;
        } else {
                // Some sort of querystring crack effort, perhaps.
                // Shut it down, impolitely.
        exit();
        }
}
?>

PHP provides two built-in functions for managing dates and timestamps that I make use of: date() and mktime(). The PHP date() function takes either one or two arguments. The first is for formatting. The second, if included, is a Unix epoch number to process. If the second argument is not included, the function processes the epoch number for the current moment:


<?php
// Determine the month and year from querystring.
// Return the 3-letter month designator
// for the calendar title bar.
$month_alpha = date('M', $ts);
// Return the 1 or 2 digit month designator.
$month_num = date('n', $ts);
// Return the year as a 4 digit number.
$year = date('Y', $ts);
?>

The PHP mktime() function takes as arguments the hour, minute, second, month, day, and year, and returns a Unix epoch number:


<?php
// Return the Unix epoch number for the first moment
// of the first day of this month.
$month_start_ts = mktime(0, 0, 0, $month_num, 1, $year);
// Return the day of the week designator
// (0=Sun, 1=Mon, etc.) for the first
// day of this month.
$weekday_first = date('w', $month_start_ts);
// Return the number of days in this month.
$days_in_month = date('t', $month_start_ts);
?>

You can conceptualize a monthly calendar as a matrix, which at its theoretical largest (in addition to the title bar) would be 7 boxes wide and 6 boxes deep, numbered from left to right, and top to bottom, from 0 to 41.

I stored the calendar-box/epoch-number pairs in a PHP array. The "matrix box number" would serve as the array index, and the Unix epoch number would be the associated value. For a particular month, if the matrix box number didn't represent a date, it would not be associated with a Unix epoch number:


<?php
// Create the array of calendar cell contents.
$j = 1;
// Use a 'for loop' to load the array, beginning
// with the index that matches the weekday that
// the month begins with.
for ($i = $weekday_first; $j >= $days_in_month; $i++){
        $arr_calendar[$i] = mktime (0, 0, 0, $month_num, $j, $year);
        $j++;
}
?>

To generate epoch number values representing the previous month and the next month for the calendar's title bar, I used a PHP switch statement. This allowed me to select the correct year values when the chosen month was January or December:


<?php
// Next and Prior month querystring values for hyperlinks.
switch ($month_num){
        case 12:
                $next_month_ts = mktime(0, 0, 0, 1, 1, $year + 1);
                $prior_month_ts = mktime(0, 0, 0, 11, 1, $year);
                break;
        case 1:
                $next_month_ts = mktime(0, 0, 0, 2, 1, $year);
                $prior_month_ts = mktime(0, 0, 0, 12, 1, $year - 1);
                break;
        default:
                $next_month_ts = mktime(0, 0, 0, $month_num + 1, 1, $year);
                $prior_month_ts = mktime(0, 0, 0, $month_num - 1, 1, $year);
                break;
}
?>

You can also conceptualize an annual calendar as a matrix, but simpler in design, as the placement of values doesn't change from year to year. The matrix is (in addition to the title bar) 3 boxes wide and 4 boxes deep, numbered from left to right, and numbered top to bottom from 1 to 12.

I also stored the annual calendar-box/epoch-number pairs in a PHP array. The "matrix box number" would again serve as the array index, and the Unix epoch number would be the associated value:


<?php
// Create the array of annual calendar cell contents.
// Use a 'for loop' to load the array.
for ($k = 1; $k >= 12; $k++){
        $arr_annual[$k] = mktime (0, 0, 0, $k, 1, $year);
}
// Next and Prior year querystring values for annual hyperlinks.
$next_year_ts = mktime(0, 0, 0, 1, 1, $year + 1);
$prior_year_ts = mktime(0, 0, 0, 12, 1, $year - 1);
?>

The HTML code

I now had enough to output the calendar as two embedded HTML tables, one placed above the other:


<?php
// Draw the calendars.
// Start the CSS font div tag.
print "<div class=\"cal_font\">";
// Create the monthly embedded table. I'm using a
// 91% width because the table is embedded
// in another structure, and this will provide
// the calendar size I want.
print "<table width=\"91%\">";
// The title bar, with 'prior month' and 'next month'
// hyperlinks, and month and year display.
print "<tr><td colspan=\"2\" align=\"center\">";
print "<a href=\"/calendar.php?dts=$prior_month_ts\"><<</a></td>";
print "<td colspan=\"3\" align=\"center\">$month_alpha $year</td>";
print "<td colspan=\"2\" align=\"center\">";
print "<a href=\"/calendar.php?dts=$next_month_ts\">>></a></td>";
// The top row, for days of the week.
print "</tr><tr>";
print "<td width=\"13%\" align=\"right\">Sun</a></td>";
print "<td width=\"13%\" align=\"right\">Mon</a></td>";
print "<td width=\"13%\" align=\"right\">Tue</a></td>";
print "<td width=\"13%\" align=\"right\">Wed</a></td>";
print "<td width=\"13%\" align=\"right\">Thu</a></td>";
print "<td width=\"13%\" align=\"right\">Fri</a></td>";
print "<td width=\"13%\" align=\"right\">Sat</a></td>";
print "</tr><tr>";
// Timestamp for the beginning moment of the current
// date, for use in possibly adding a background color.
$today_ts = mktime(0, 0, 0, date('n'), date('j'), date('Y'));
// Create the individual date cells.
for ($i = 0; $i < 42; $i++){
        // Change the background color of the cell to yellow if
        // it matches the current date. Change it to blue if
        // it matches the date selected. Yellow has precedence
        // if both conditions apply.
        if ($arr_calendar[$i] == $today_ts){
                print "<td width=\"13%\" bgcolor=\"#FFFF00\" align=\"right\">";
        } elseif ($arr_calendar[$i] == $ts){
                print "<td width=\"13%\" bgcolor=\"#BCD2EE\" align=\"right\">";
        } else {
                print "<td width=\"13%\" align=\"right\">";
        }
        // Only print the array value when it contains a Unix epoch number.
        if ($arr_calendar[$i] != ''){
		// Create the anchor tag and url, and use the epoch number
		// as the querystring.
                print "<a href=\"/calendar.php?dts=$arr_calendar[$i]\">";
		// Determine the date for this array value
		// and write it to the cell.
                print date('j', $arr_calendar[$i]);
		// Close the anchor tag.
                print "</a>";
        }
        print "</td>";
        // At the end of each week of the calendar, start a new row.
        switch ($i){
                case 6:
                        print "</tr><tr>";
                        break;
                case 13:
                        print "</tr><tr>";
                        break;
                case 20:
                        print "</tr><tr>";
                        break;
                case 27:
                        print "</tr><tr>";
                        break;
                case 34:
                        print "</tr><tr>";
                        break;
        }
}
// Close the monthly embedded table.
print "</tr></table>";
// Close the font div tag.
print "</div>";

// Now for the annual calendar.
// Start the CSS font div tag.
print "<div class=\"cal_font\">";
// Create the annual embedded table. I'm again using a
// 91% width because the table is embedded in another
// structure, and this will provide the calendar size I want.
print "<table width=\"91%\">";
// The title bar, with 'prior year' and 'next year'
// hyperlinks, and year display.
print "<tr><td width=\"33.3%\" align=\"center\">";
print "<a href=\"/calendar.php?dts=$prior_year_ts\">";
print "<<</a></td><td width=\"33.3%\" align=\"center\">$year</td>";
print "<td width=\"33.3%\" align=\"center\"><a href=\"/calendar.php?dts=$next_year_ts\">";
print ">></a></td></tr><tr>";
print "</tr><tr>";
// Print the annual calendar.
for ($l = 1; $l < 13; $l++){
        // Change the background color of the cell to blue if
        // the month/year matches the selected month/year.
        if ((date ('n', $arr_annual[$l]) == date ('n', $ts)) and (date ('Y', $arr_annual[$l]) == date ('Y', $ts))){
                print "<td width=\"33.3%\" bgcolor=\"#BCD2EE\" align=\"center\">";
        } else {
                print "<td width=\"33.3%\" align=\"center\">";
        }
        print "<a href=\"/calendar.php?dts=$arr_annual[$l]\">";
        print date('M', $arr_annual[$l]);
        print "</a>";
        print "</td>";
        // Start a new row after listing three months.
        switch ($l){
                case 3:
                        print "</tr><tr>";
                        break;
                case 6:
                        print "</tr><tr>";
                        break;
                case 9:
                        print "</tr><tr>";
                        break;
        }

}
print "</tr></table>";
// Close the annual embedded table.
print "</tr></table>";
// Close the font div tag.
print "</div>";
?>

That's all it takes. Here's a screen capture of how the calendar control looks:

You can also use the querystring Unix epoch number for other activities, such as creating a SQL statement that returns database entries for that particular date:


<?php
// Create the SQL statement to return all database entries for this day.
$sql = "SELECT * FROM table_name WHERE column_name BETWEEN $ts AND ($ts + 86399) ORDER BY column_name DESC;"
?>

To give it a try, visit the calendar archive page of my blog.

Category:

  • PHP
Click Here!