Building a Simple Display Calendar

Let's bring together the date and time functions you learned in the previous hour, and use them to build a calendar that can display the dates for any month between 1980 and 2010. The user will be able to select both month and year with pull-down menus, and the dates for the selected month will be organized according to the days of the week. We will be working with two variables?one for month and one for year?which the user will supply.

These pieces of information will be used to build a timestamp based on the first day of the month defined. If the user input is invalid or absent, we will default to the first day of the current month.

Checking User Input

When the user opens our calendar application for the first time, he or she will not be submitting any information. We must therefore make sure that our script can handle the fact that the variables for month and year may not be defined. We could use the isset() function for this. (isset() returns false if the variable passed to it has not been defined.) However, let's use checkdate() instead. Listing 12.1 shows the fragment of code that checks for month and year variables coming from a form, and builds a timestamp based on them.

Listing 12.1 Checking User Input for the Calendar Script
  1: <?php
  2: if (!checkdate($_POST[month], 1, $_POST[year])) {
  3:     $nowArray = getdate();
  4:     $month = $nowArray['mon'];
  5:     $year = $nowArray['year'];
  6: } else {
  7:     $month = $_POST[month];
  8:     $year = $_POST[year];
  9: }
 10: $start = mktime (12, 0, 0, $month, 1, $year);
 11: $firstDayArray = getdate($start);
 12: ?>

Listing 12.1 is a fragment of a larger script, so it does not produce any output itself. In the if statement on line 2, we use checkdate() to test whether the month and year have been provided by a form. If they have not been defined, checkdate() returns false because the script cannot make a valid date from undefined month and year arguments. This approach has the added bonus of ensuring that the data submitted by the user constitutes a valid date.

If the date is not valid, we use getdate() on line 3 to create an associative array based on the current time. We then set values for $month and $year ourselves, using the array's mon and year elements (lines 4 and 5). If the variables have been set from the form, we put the data into $month and $year variables so as not to touch the values in the original $_POST superglobal.

Now that we are sure that we have valid data in $month and $year, we can use mktime() to create a timestamp for the first day of the month (line 10). We will need information about this timestamp later on, so on line 11 we create a variable called $firstDayArray that will store an associative array returned by getdate() and based on this timestamp.

Building the HTML Form

We need to create an interface by which users can ask to see data for a month and year. For this, we will use SELECT elements. Although we could hard-code these in HTML, we must also ensure that the pull-downs default to the currently chosen month, so we will dynamically create these pull-downs, adding a SELECT attribute to the OPTION element where appropriate. The form is generated in Listing 12.2.

Listing 12.2 Building the HTML Form for the Calendar Script
  1: <?php
  2: if (!checkdate($_POST[month], 1, $_POST[year])) {
  3:     $nowArray = getdate();
  4:     $month = $nowArray['mon'];
  5:     $year = $nowArray['year'];
  6: } else {
  7:     $month = $_POST[month];
  8:     $year = $_POST[year];
  9: }
 10: $start = mktime (12, 0, 0, $month, 1, $year);
 11: $firstDayArray = getdate($start);
 12: ?>
 13: <html>
 14: <head>
 15: <title><?php print "Calendar:
 16:     ".$firstDayArray['month']." ".$firstDayArray['year'] ?></title>
 17: <head>
 18: <body>
 19: <form method="post" action="<?php print "$_SERVER[PHP_SELF]"; ?>">
 20: <select name="month">
 21: <?php
 22: $months = Array("January", "February", "March", "April", "May",
 23: "June", "July", "August", "September", "October", "November", "December");
 25: for ($x=1; $x <= count($months); $x++) {
 26:     print "\t<option value=\"$x\"";
 27:     print ($x == $month)?" SELECTED":"";
 28:     print ">".$months[$x-1]."\n";
 29: }
 30: ?>
 31: </select>
 32: <select name="year">
 33: <?php
 34: for ($x=1980; $x<=2010; $x++) {
 35:     print "\t<option";
 36:     print ($x == $year)?" SELECTED":"";
 37:     print ">$x\n";
 38: }
 39: ?>
 40: </select>
 41: <input type="submit" value="Go!">
 42: </form>
 43: </body>
 44: </html>

Having created the $start timestamp and the $firstDayArray date array (lines 2?11), let's write the HTML for the page. Notice that we use $firstDayArray to add the month and year to the TITLE element on lines 15 and 16.

Line 19 is the beginning of our form. To create the SELECT element for the month pull-down, we drop back into PHP mode on line 21 to write the individual OPTION tags. First, we create an array called $months on line 22 that contains the 12 month names. We then loop through this array, creating an OPTION tag for each name (lines 25?29). This would probably be an overcomplicated way of writing a simple SELECT element were it not for the fact that we are testing $x (the counter variable in the for statement) against the $month variable on line 27. If $x and $month are equivalent, we add the string SELECTED to the OPTION tag, ensuring that the correct month will be selected automatically when the page loads. We use a similar technique to write the year pull-down on lines 34?38. Finally, back in HTML mode, we create a submit button on line 41.

We now have a form that can send the month and year parameters to itself, and will default either to the current month and year, or the month and year previously chosen. If you save this listing as listing12.2.php, place it in your Web server document root, and access it with your Web browser, you should see something like Figure 12.1 (your month and year may differ).

Figure 12.1. The calendar form.


Creating the Calendar Table

We now need to create a table and populate it with dates for the chosen month. We do this in Listing 12.3, which represents the complete calendar script.

Listing 12.3 The Complete Calendar Script
  1: <?php
  2: define("ADAY", (60*60*24));
  3: if (!checkdate($_POST[month], 1, $_POST[year])) {
  4:     $nowArray = getdate();
  5:     $month = $nowArray['mon'];
  6:     $year = $nowArray['year'];
  7: } else {
  8:     $month = $_POST[month];
  9:     $year = $_POST[year];
 10: }
 11: $start = mktime (12, 0, 0, $month, 1, $year);
 12: $firstDayArray = getdate($start);
 13: ?>
 14: <html>
 15: <head>
 16: <title><?php print "Calendar:
 17:     ".$firstDayArray['month']." ".$firstDayArray['year'] ?></title>
 18: <head>
 19: <body>
 20: <form method="post" action="<?php print "$_SERVER[PHP_SELF]"; ?>">
 21: <select name="month">
 22: <?php
 23: $months = Array("January", "February", "March", "April", "May",
 24: "June", "July", "August", "September", "October", "November", "December");
 26: for ($x=1; $x <= count($months); $x++) {
 27:     print "\t<option value=\"$x\"";
 28:     print ($x == $month)?" SELECTED":"";
 29:     print ">".$months[$x-1]."\n";
 30: }
 31: ?>
 32: </select>
 33: <select name="year">
 34: <?php
 35: for ($x=1980; $x<2010; $x++) {
 36:     print "\t<option";
 37:     print ($x == $year)?" SELECTED":"";
 38:     print ">$x\n";
 39: }
 40: ?>
 41: </select>
 42: <input type="submit" value="Go!">
 43: </form>
 44: <br>
 45: <?php
 46: $days = Array("Sunday", "Monday", "Tuesday", "Wednesday",
 47: "Thursday", "Friday", "Saturday");
 49: print "<TABLE BORDER = 1 CELLPADDING=5>\n";
 50: foreach ($days as $day) {
 51:     print "\t<td><b>$day</b></td>\n";
 52: }
 53: for ($count=0; $count < (6*7); $count++) {
 54:     $dayArray = getdate($start);
 55:     if (($count % 7) == 0) {
 56:         if ($dayArray['mon'] != $month) {
 57:             break;
 58:         } else {
 59:             print "</tr><tr>\n";
 60:         }
 61:     }
 62:     if ($count < $firstDayArray['wday'] || $dayArray['mon'] != $month) {
 63:         print "\t<td><br></td>\n";
 64:     } else {
 65:         print "\t<td>".$dayArray['mday']." &nbsp;&nbsp; </td>\n";
 66:         $start += ADAY;
 67:     }
 68: }
 69: print "</tr></table>";
 70: ?>
 71: </body>
 72: </html>

Because the table will be indexed by days of the week, we loop through an array of day names created on lines 50?52, printing each to its own cell on line 51. All the real magic of the script happens in the final for statement on line 53.

We initialize a variable called $count and ensure that the loop will end after 42 iterations. This is to make sure that we will have enough cells to populate with date information. Within the loop, we transform the $start variable into a date array with getdate(), assigning the result to $dayArray (line 54). Although $start is the first day of the month during the loop's initial execution, we will increment this timestamp by 24 hours for every iteration.


Adding 24 hours to a day is not necessarily the same as adding one whole day. Since the start date and time is not midnight, this format works for introducing the concept of adding time.

On line 55, we test the $count variable against the number 7 using the modulus operator. The block of code belonging to this if statement will therefore only be run when $count is either zero or a multiple of 7. This is our way of knowing whether we should end the loop altogether or start a new row.

After we have established that we are in the first iteration or at the end of a row, we can go on to perform another test on line 56. If the mon (month number) element of the $dayArray is no longer equivalent to the $month variable, we are finished. Remember that $dayArray contains information about the $start timestamp, which is the current place in the month that we are displaying. When $start goes beyond the current month, $dayArray['mon'] will hold a different figure than the $month number provided by user input. Our modulus test demonstrated that we are at the end of a row, and the fact that we are in a new month means that we can leave the loop altogether. Assuming, however, that we are still in the month that we are displaying, we end the row and start a new one on line 59.

In the next if statement, on line 62, we determine whether to write date information to a cell. Not every month begins on a Sunday, so it's likely that we will start with an empty cell or two. Few months will finish at the end of one of our rows, so it's also likely that we will need to write a few empty cells before we close the table. We have stored information about the first day of the month in $firstDayArray; in particular, we can access the number of the day of the week in $firstDayArray['wday']. If $count is smaller than this number, we know that we haven't yet reached the correct cell for writing. By the same token, if the $month variable is no longer equal to $dayArray['mon'], we know that we have reached the end of the month (but not the end of the row, as we determined in our earlier modulus test). In either case, we write an empty cell to the browser on line 63.

In the final else clause on line 64, we can do the fun stuff. We have already ascertained that we are within the month that we want to list and that the current day column matches the day number stored in $firstDayArray['wday']. Now we must use the $dayArray associative array that we established early in the loop to write the day of the month and some blank space into a cell.

Finally, on line 66, we need to increment the $start variable, which contains our date stamp. We simply add the number of seconds in a day to it (we defined this value in line 2), and we're ready to begin the loop again with a new value in $start to be tested.

If you save this listing as listing12.3.php, place it in your Web server document root, and access it with your Web browser, you should see something like Figure 12.2 (your month and year may differ).

Figure 12.2. The calendar form and script.


    Part III: Getting Involved with the Code