Linux.com

Feature: PHP

A DIY calendar control in PHP

By Donald W. McArthur on January 24, 2007 (8:00:00 AM)

Share    Print    Comments   

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.

Calendar matrix

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.

Annual

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:

Calendar control 2

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.

Share    Print    Comments   

Comments

on A DIY calendar control in PHP

Note: Comments are owned by the poster. We are not responsible for their content.

PEAR::Calendar

Posted by: Anonymous Coward on January 25, 2007 03:15 AM
instead of writing all that code, you could just use <a href="http://pear.php.net/package/Calendar" title="php.net">PEAR::Calendar</a php.net>, which is fully tested, not tied to HTML only, allows you to use decorators and add new events.

Here's the <a href="http://pear.php.net/manual/en/package.datetime.calendar.php" title="php.net">package documentation</a php.net>.


HTH

#

Garbage. Disappointing garbage.

Posted by: Anonymous Coward on January 25, 2007 07:37 AM
God. I've had enough of hard-coded echo/print statements, lack of templating, and all that to last me a lifetime. I used to code that way, and it bit me in the *** too many times.

The first thing I do when I see a PHP article nowadays is look for that. If I find it, I gather that the writer probably doesn't know what he's talking about and skip the article. Everyone's a PHP expert nowadays. And everyone does it wrong.

And hmm...going back and looking at his is_numeric() check proves that my initial instinct was right. Read the f***ing manual. <a href="http://php.net/is_numeric" title="php.net">http://php.net/is_numeric</a php.net> The is_numeric() function allows all kinds of crazy inputs, including floating point values. BAD! Why not just use intval() on the "querystring" epoch value? The result is always an integer -- 0 on bad input. Instead this code bails out of the entire webpage with an exit() call. Yeah. I really want my calendar component silently ending the page instead of gracefully recovering or invoking some kind of error handling or error message.

Yes, I know that it's easy enough to retro-fit in some kind of templating system, but why should I have to? I can write a better calendar that serves my exact needs in less time than it would take to do so and fix any limitations/bugs that get in my way.

It's especially shocking that an ASP.NET programmer would write garbage like this, when ASP.NET itself is heavily based around objects and templating, with huge obvious benefits such as<nobr> <wbr></nobr>...code reusability. Yes, the very reusability the author is trying to tout as a reason to use this garbage calendar component. Hmm. I'd love to see the author put 5 copies of this calendars into one page. Lots of copy and paste and find and replace, I'm feeling. As much as I think objects are overused and (wrongly) considered to be the answer to everything, the ugly guts of this are just screaming and begging to be encapsulated into an object, or even just a few really-well-thought-out functions.

Disappointing.

#

Re:Garbage. Disappointing garbage.

Posted by: Anonymous Coward on January 25, 2007 05:46 PM
Perhaps it would better serve all of us if you used your superior skill and intellect to educate rather than criticize.

Just a thought.

#

Re:Garbage. Disappointing garbage.

Posted by: Anonymous Coward on January 25, 2007 08:14 PM
Hey give us a break, even smart people like to flame<nobr> <wbr></nobr>:P

#

Re:Garbage. Disappointing garbage.

Posted by: EnigmaOne on January 26, 2007 07:50 AM
"I can write a better calendar that serves my exact needs in less time than it would take to do so and fix any limitations/bugs that get in my way."



Good. Put your ego where your mouth is, and improve it publicly--instead of acting like a juvenile, pedantic @$$hole.

#

Re:Garbage. Disappointing garbage.

Posted by: Anonymous Coward on January 26, 2007 05:54 PM
Gee folks!
[a] Interesting comment on the article and reasonable criticism.
[b] ruined by bitter and twisted language
[c] could we confine ourselves to at least aspiring toward adulthood (drop the abuse - pleeeeeeeze)

#

Re:Garbage. Disappointing garbage.

Posted by: Anonymous Coward on January 28, 2007 02:13 AM
This is an article on using the php functions date() and mktime(), and php arrays, to create a calendar control. It is not an article on writing functions.

I used is_numeric to block sql injection attempts (though the mysql connection user only has SELECT privileges on the underlying db). If someone gets spurious returns by manipulating the querystring, that is not a concern of mine, as long as they cannot harm my server.

I use error messages in code during development, and often remove them when finished, especially if I think the error is going to be caused by malicious actions. I do not generally provide feedback to crack efforts.

The rest of your comments appear to be juvenilia, though you grammar leads me to believe you are older. I will simply point out that social skills can be learned, like most other skills.

Don McArthur

#

Reply by "Garbage" commenter.

Posted by: Anonymous Coward on January 31, 2007 06:58 AM
Don,

I'm the commenter who called the article "garbage". Perhaps I was too harsh in my wording, but I still believe I was correct in everything I said.

If, as you state, your purpose was really to write a tutorial on how to use date, mktime, and PHP arrays, you shouldn't have branded the article as "A DIY calendar control" (see the article title). "Control" strongly implies modularity and reusability. This is anything but.

Even if it was meant to show the uses of date, mktime, and PHP arrays, they're buried inside the calendar code -- any user who really needed to read an article like this to learn about them would probably have gotten confused/bored by the time they got to that part.

It constantly outrages me that programmers who are learning PHP see such articles and write/use code like this instead of learning better techniques. I've seen (and had to fix) enough buggy, limited, and vulnerable PHP to last me a lifetime. I don't want any more programmers learning bad habits. We have enough of those already.

And as other commenters have suggested, use the PEAR calendar code. It's a heck of a lot better than what you have here. I'm surprised an ASP.NET programmer didn't look for a ready-made control before writing his own.

#

Re:Garbage. Disappointing garbage.

Posted by: Anonymous Coward on March 12, 2007 06:37 PM
Naw, I agree. Utter garbage.

First, your table declarations are old school. The widths and colors need to be moved out of the HTML and into a stylesheet.

Second, why are you using and array and epoch to figure out the date? Why go through all the overhead. The computer already knows what the date is. Let it do the math for you.

Third, where did the switch statement come from? Just count to 7 to figure out when to start a new row.

Finally, your reason for checking the value of the query string is, IMO, premature. Presumably, no matter where this is used, you will need a form and form handler to put in a database. I think the validation would be better handled there, just before insert, instead of here in the browser where it is still vulnerable.



This little note from the docs are all that you need: "mktime() is useful for doing date arithmetic and validation, as it will automatically calculate the correct value for out-of-range input." With a little creative math, you don't even need to know the actual date. Try this instead: No arrays, no switch statements, and presentation separated from content.
<tt><html><head>
<style type="text/css">
<!--
a:link { color : #B22222; text-decoration: none; }
a:visited { color : #B22222; text-decoration : none; }
a:hover { color : #000080; text-decoration : none; }

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

td {
width: 13%;
}

.picked {
border: 1px solid grey;
background-color: blue;
}

.not_picked {
}

.today {
font-weight: bold
background-color: yellow;
}
-->
</style>
</head>
<body>
<?php

$curryear = (isset($_REQUEST["date"])) ? intval(date(substr($_REQUEST["date"], 0, 4))):date("Y");
$currmonth = (isset($_REQUEST["date"])) ? intval(date(substr($_REQUEST["date"], 4, 2))):date("m");
$currday = (isset($_REQUEST["date"])) ? intval(date(substr($_REQUEST["date"], 6))):date("d");

$firstweekdayofmonth = strftime("%w", mktime(0,0,0,$currmonth,1, $curryear));// Sun = 0, Sat = 6
$lastdayofmonth = strftime("%d", mktime(0,0,0,$currmonth+1,0, $curryear));
$WeekDays = array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");

echo "<center><h1>" . strftime("%B", mktime(0,0,0,$currmonth, 1, $curryear))<nobr> <wbr></nobr>." $curryear";
echo "</h1><table class=\"cal_font\"><tr>\n";

foreach($WeekDays as $weekday)
        {
        echo "<td>$weekday</td>\n";
        }
echo "</tr>\n<tr>\n";
$day = 1;
// offset is difference between the day displayed and number day of the week.
// if the first of month is on Wed, then offset will be negative on Sun, Mon, Tues.
for ($offset = $day - $firstweekdayofmonth; $day <= $lastdayofmonth; $offset++){
  $picked = ($currday == $day)?"picked":"";
  echo "<td><span class=\"$picked\">";
  $weekdaycounter++;
  if ($offset > 0){
        echo "<a href=\"calender.php?date=".$curryear.sprintf("%02<nobr>d<wbr></nobr> ",$currmonth).sprintf("%02d",$day)."\">";

        if($day == date("d") && $currmonth == date("m")){
                echo "<span class=\"today\">$day</span>";
        }else {
                echo "$day";
        }
        echo "</a>";
  $day++;
  }

  echo "</span></td>\n";
  if ($weekdaycounter ==7){
    echo "</tr>\n<tr>\n";
    $weekdaycounter = 0;
  }
}
echo "</tr>\n</table>";

echo "<b>[</b><a href=\"calender.php?date=".strftime("%Y%m", mktime(0,0,0,$currmonth,-1,$curryear))."\">&lt&lt Last month</a><b>]</b>";
echo "<b>[</b><a href=\"calender.php?date=".strftime("%Y%m", mktime(0,0,0,$currmonth+1,+1,$curryear))."\">Next month &gt&gt</a><b>]</b>";
echo "</center>";
?>
</body></html></tt>

#

Lightweight Calendar class

Posted by: omerida on January 28, 2007 12:25 PM
If you're looking for a reusable class to output calendars, and PEAR::Calendar is just a little over-engineered for you, I wrote a simple PHP class that is available here:

<a href="http://www.oscarm.org/page/calendarClass" title="oscarm.org">http://www.oscarm.org/page/calendarClass</a oscarm.org>

#

unbelievable...

Posted by: Anonymous Coward on March 18, 2007 12:59 AM
I can't believe this.

A dysfunctional script full of syntactical errors and none of the posters realized it.

What a shame.

#

This story has been archived. Comments can no longer be posted.



 
Tableless layout Validate XHTML 1.0 Strict Validate CSS Powered by Xaraya