5.3. Code and Code Explanation

I will jump right into this application by first discussing how to generate a month-view calendar. Afterwards I will cover the personal calendar application as a whole and discuss the day-view calendar, form and events list. Then I will discuss the reminder and export convenience features.

5.3.1. Creating a Month-View Calendar

The code to generate a calendar that displays days within a month consists really of nothing more than a couple loops once information about the desired month has been established.

A unix timestamp is retrieved first either from a URL parameter or from the time() function, which returns the current timestamp. When the page is initially requested there is little likelihood that a timestamp will be sent in the URL so the current date will be the default. Afterwards a specific timestamp can be passed from links as other dates are navigated. The date() function can then use the timestamp value to identify important aspects of the date. It is a pretty powerful function that can do more than just format a timestamp for display; it can be used to determine the starting day of a month, the number of days in a month, whether the year is a leap year or not, and so on.

$timestamp = (isset($_GET['t'])) ? $_GET['t'] : time();

list($month, $day, $year) = explode('/', date('m/d/Y', $timestamp));
$first_day_of_month = date('w', mktime(0, 0, 0, $month, 1, $year));
$total_days = date('t', $timestamp);

Tables 5-1 and 5-2 show the available format specifiers the date() function accepts and their meaning.

Table 5-1. Date Format Specifiers for the date() Function
SpecifierDescriptionExample
DThree-letter name of daySun
dDay of month with leading 001
FFull name of monthJanuary
jDay of month (no leading 0)1
LIf the year is a leap year or not0 (no), 1 (yes)
lFull name of daySunday
MThree-letter name of monthJan
mMonth as number with leading 001
NISO-8601 day number7 (Sunday)
nMonth as number (no leading 0)1
oISO-8601 year number2008
SEnglish ordinal suffix for day of monthTh
tNumber of days in a given month31
WISO-8601 week number42 (42nd week)
wNumeric representation of the day of the week0 (Sunday)
YFour-digit year2008
yTwo-digit year08
zDay of the year (starts at 0)42 (Feb 11th)

Table 5-2. Time Format Specifiers for the date() Function
SpecifierDescriptionExample
AAM/PM designators (uppercase)AM
aAM/PM designators (lowercase)am
BSwatch Internet time500
CISO-8601 date2008–01–01T06:00:00–05:00
eTime zoneAmerica/New_York
G24-hour hour (no leading 0)13
g12-hour hour (no leading 0)1
H24-hour hour with leading 013
h12-hour hour with leading 001
IIf daylight savings is in effect0 (no), 1 (yes)
iMinutes with leading 009
OOffset from GMT−0500
POffset from GMT with colon separator−05:00
rRFC-2822 dateTue, 1 Jan 2008 06:00:00 – 0500
sSeconds with leading 009
TAbbreviated time zoneEST
USeconds since the unix epoch1199185200
umilliseconds54321
ZTimezone offset in seconds−18000

A timestamp represents the date as an integer. More accurately, it represents the date in terms of the number of seconds since midnight UTC of January 1, 1970. To modify the timestamp for a future or previous date you can add/subtract the appropriate number of seconds or use the function strtotime(), which understands various string constructs.

// link to preceding day. 60 × 60 × 24 = 86400 seconds in a day
echo '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    ($timestamp - 86400) . '">&lt;</a> &nbsp; ';

// link to preceding day using strtotime() function
echo '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('−1 day', $timestamp) . '">&lt;</a> &nbsp; ';

A tracking variable is initialized to keep track of the current day's cell being generated by the loop. An outer while loop can use the variable to iterate until it has exceeded the allotted number of days for the month. Within the while loop, an inner for loop outputs a row of seven cells, one for each day of the week.

<table>
<tr>
 <th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th>
 <th>Fri</th><th>Sat</th>
</tr>
<?php
$current = 1;
while ($current <= $total_days)

{
    echo '<tr>';
    for ($i = 0; $i < 7; $i++)
    {
        if (($current == 1 && $i < $first_day_of_month) ||
            ($current > $total_days))
        {
            echo '<td>&nbsp</td>';
            continue;
        }

        echo '<td>' . $current . '</td>';
        $current++;
    }
    echo '</tr>';
}
?>
</table>

The following code demonstrates how by pulling all these pieces together—accepting an incoming timestamp, determining information about the represented month, adjusting the timestamp to formulate new links and using loops to display a grid—you are able to generate a month-view calendar. public_files/month.php serves as an example of a basic calendar which you can later modify to suit your own needs. The output is shown in Figure 5-2.

<?php
include '../lib/common.php';

// accept incoming URL parameter
$timestamp = (isset($_GET['t'])) ? $_GET['t'] : time();

// determine useful aspects of the requested month
list($month, $day, $year) = explode('/', date('m/d/Y', $timestamp));
$first_day_of_month = date('w', mktime(0, 0, 0, $month, 1, $year));
$total_days = date('t', $timestamp);

// output table header
ob_start();
echo '<table id="calendar">';
echo '<tr id="calendar_header"><th colspan="7">';
echo '<a href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('−1 month', $timestamp) . '">&lt;</a> &nbsp; ';
echo date('F', $timestamp) . ' ' . $year;
echo '&nbsp; <a href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('+1 month', $timestamp) . '">&lt;</a>';
echo '</th></tr>';
echo '<tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th>' .
     '<th>Fri</th><th>Sat</th></tr>';

// output date cells
$current = 1;
while ($current <= $total_days)

{
    echo '<tr class="calendar_dates">';
    for ($i = 0; $i < 7; $i++)
    {
        if (($current == 1 && $i < $first_day_of_month) ||
            ($current > $total_days))
        {
            echo '<td class="empty">&nbsp</td>';
            continue;
        }

        echo '<td>' . $current . '</td>';
        $current++;
    }
    echo '</tr>';
}
echo '</table>';
$GLOBALS['TEMPLATE']['content'] = ob_get_clean();

// assign styles for calendar
$GLOBALS['TEMPLATE']['extra_head'] = '<link rel="stylesheet" type="text/css" ' .
    'href="css/monthly_calendar.css"/>';

// display page
include '../templates/template-page.php';
?>

Figure 5-2. Figure 5-2

5.3.2. Creating a Day-View Calendar

The overall logic of generating a day-view calendar is similar to that of the month-view calendar. However, instead of outputting a cell for each day, the calendar shows a single day broken up into quarter-hour increments. An outer for loop iterates from a starting hour to an ending hour whereas an inner for loop increments through intervals of 15 (60 minutes in an hour divided into quarter segments is 15 minutes) and outputs the table's cells.

When working with time values it is often easier to work with a 24-hour clock or military time. If you're not familiar with the concept, the 24-hour clock is a convention of identifying hours sequentially from 0 (midnight) to 23 (11:00 pm) instead of repeating 1 to 12 and using am to denote morning hours and pm for evening hours. Most parts of the world use a 24-hour clock as well as a lot of computer software (MySQL denotes time using the 24-hour format), whereas the United States, Canada, and Mexico use a 12-hour format. Even though the for loop iterates through the day using a 24-hour clock, the code converts the time and displays it using am and pm designators. Table 5-3 compares the two time formats.

define('DAY_HR_START', 9);
define('DAY_HR_END', 17);

echo '<table>';

for ($i = DAY_HR_START; $i <= DAY_HR_END; $i++)
{
    for ($j = 0; $j < 60; $j += 15)
    {
        $hour = $i;
        $minutes = $j;
        $meridian = 'AM';

        if ($hour > 12)
        {
            $meridian = 'PM';
            $hour -= 12;
        }
        else if ($hour == 12)
        {
            $meridian = 'PM';
        }

        echo '<tr>';
        printf('<td>%02d:%02d %s</td>', $hour, $minutes, $meridian);
        echo '<td>&nbsp;</td>';
        echo '</tr>';
    }
}
echo '</table>';

Table 5-3. Comparison of the 12-hr and 24-hr Clocks
Morning HoursEvening Hours
12-hr24-hr12-hr24-hr12-hr24-hr12-hr24-hr
12 am (midnight)006 am0612 pm (noon)126 pm18
1 am017 am071 pm137 pm19
2 am028 am082 pm148 pm20
3 am039 am193 pm159 pm21
4 am0410 am104 pm1610 pm22
5 am0511 am115 pm1711 pm23

5.3.3. Adding and Showing Events

An HTML form is needed to collect information about the event from the user. The date will be determined automatically based on the page viewed, so the form posts back to the page with the timestamp in the action URL. The form will gather the name of the event, the starting time and whether or not a reminder should be sent via e-mail.

<h2>Add Event</h2>
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
 $timestamp; ?>"method="post">
 <table>
  <tr>
   <td class="label"><label for="evt_name">Event:</label></td>
   <td><input type="text" id="evt_name" name="evt_name"></td>
  </tr><tr>
   <td class="label"><label for="evt_hour">Time:</label></td>
   <td>
    <select name="evt_hour" id="evt_hour">
     <option value="12">12</option>
<?php

    for ($i = 1; $i < 12; $i++)
    {
        printf('<option value="%d">%02d</option>', $i, $i);
    }
?>
    </select> : <select name="evt_min">
<?php
    for ($i = 0; $i < 59; $i += 15)
    {
            printf('<option value="%d">%02d</option>', $i, $i);
    }
?>
    </select>
    <select name="evt_pm">
     <option value="no">AM</option>
     <option value="yes">PM</option>

</select>
   </td>
  </tr><tr>
   <td class="label">Notify</td>
   <td>
    <input type="radio" name="evt_notify" id="evt_notify_yes" value="yes"
     checked="checked"/>
    <label for="evt_notify_yes">Yes</label>
    <input type="radio" name="evt_notify" id="evt_notify_no" value="no"/>
    <label for="evt_notify_no">No</label>
   </td>
  </tr><tr>
   <td></td>
   <td>
    <input type="hidden" name="submitted" value="true"/>
    <input type="submit" value="Add Event"/></td>
  </tr>
 </table>
</form>

Code can be positioned at the beginning of the script immediately after a timestamp has been gleaned and the needed aspects of the month have been determined with date() to validate the incoming values and add the event to the database.

if (isset($_POST['submitted']))
{
    // validate incoming values
    $evt_name = (isset($_POST['evt_name'])) ? $_POST['evt_name'] : '';
    $evt_name = trim($evt_name);
    if (!$evt_name)
    {
        $evt_name = 'Unknown';
    }
    $evt_pm = (isset($_POST['evt_pm']) && $_POST['evt_pm'] == 'yes'),
    $evt_hour = (isset($_POST['evt_hour'])) ? (int)$_POST['evt_hour'] : 0;
    if ($evt_pm)
    {
        $evt_hour += 12;
    }
    if ($evt_hour == 24)
    {
       $evt_hour = 12;
    }
    else if ($evt_hour == 12)
    {
       $evt_hour = 0;
    }
    $evt_min = (isset($_POST['evt_min'])) ? (int)$_POST['evt_min'] : 0;
    $evt_notify = (isset($_POST['evt_notify']) &&
        $_POST['evt_notify'] == 'yes'),

    // add to database
    $query = sprintf('INSERT INTO %sCALENDAR (EVENT_NAME, EVENT_TSTAMP, ' .
        'NOTIFY) VALUES ("%s", "%04d-%02d-%02d %02d:%02d:00", %d)',
        DB_TBL_PREFIX,

mysql_real_escape_string($evt_name, $GLOBALS['DB']),
        $year, $month, $day,
        $evt_hour, $evt_min,
        $evt_notify);
    mysql_query($query, $GLOBALS['DB']);
}

The logic to display the events can be incorporated into the loop that generates the day-view calendar. Within the inner for loop a query is sent to the database to retrieve any events for the current timeslot. Personally, I prefer to use MySQL's UNIX_TIMESTAMP() function when retrieving dates from a database table. It returns the date and time as a unix timestamp as opposed to MySQL's Y-m-d h:m:s format, which is easier to manipulate in PHP using the date() function.

for ($hour = DAY_HR_START; $hour <= DAY_HR_END; $hour++)
{
    for ($minute = 0; $minute < 60; $minute += 15)
    {
        echo '<tr>';
$d_meridian = 'AM';

$d_hour = $hour;
        if ($hour >= 12)
        {

$d_meridian = 'PM';

$d_hour = ($hour > 12)?$hour − 12:$hour;
        }
printf('<td>%d: %02d %s</td>', $d_hour, $minutes, $d_meridian);
        echo '<td>';

        $query = sprintf('SELECT EVENT_NAME FROM %sCALENDAR WHERE ' .
            'EVENT_TSTAMP = "%04d-%02d-%02d %02d:%02d:00"',
            DB_TBL_PREFIX,
            $year, $month, $day,
            $i, $j);
        $result = mysql_query($query, $GLOBALS['DB']);

        if (mysql_num_rows($result))
        {
            while ($row = mysql_fetch_assoc($result))
            {
                echo '<div>' . htmlspecialchars($row['EVENT_NAME']) .
                    '</div>';
            }
        }
        else
        {
            echo '&nbsp;';
        }
        mysql_free_result($result);
        echo '</td>';
        echo '</tr>';
    }
}
echo '</table>';

It is possible for some events to be scheduled outside the timeframe displayed by the daily-view calendar, but it is just as important that they be displayed as well. It is possible to submit a query to select the events for the given day that are outside the time period and displayed in a bulleted list.

<h2>Also Scheduled</h2>
<?php
// retrieve and display events that fall outside the daily-view hours
$query = sprintf('SELECT EVENT_NAME, UNIX_TIMESTAMP(EVENT_TSTAMP) AS ' .
     'EVENT_TSTAMP FROM %sCALENDAR WHERE EVENT_TSTAMP NOT BETWEEN ' .
     '"%4d-%02d-%02d %02d:00:00" AND "%4d-%02d-%02d %02d:59:59" ORDER BY ' .
     'EVENT_TSTAMP ASC, EVENT_NAME ASC',
     DB_TBL_PREFIX,
     $year, $month, $day, DAY_HR_START,
     $year, $month, $day, DAY_HR_END);
$result = mysql_query($query, $GLOBALS['DB']);

echo '<ul>';
if (mysql_num_rows($result))
{
    while ($row = mysql_fetch_assoc($result))
    {
        echo '<li>' . date('h:i A - ', $row['EVENT_TSTAMP']) .
        htmlspecialchars($row['EVENT_NAME']) . '</li>';
    }
}
else
{
    echo '<p><i>No other events scheduled</i></p>';
}
mysql_free_result($result);
echo '</ul>';

The following code listing for public_files/calendar.php incorporates all of the previously discussed concepts to create the main functionality of the personal calendar application. One line not discussed yet adds a link to export.php so the user can download the calendar information. Figure 5-1 (shown earlier) shows the interface in a web browser.

<?php
include '../lib/common.php';
include '../lib/db.php';

print_r($_GET);
print_r($_POST);
// view definitions
define('DAY_HR_START', 9);
define('DAY_HR_END', 17);

// accept incoming URL parameter
$timestamp = (isset($_GET['t'])) ? $_GET['t'] : time();

// determine useful aspects of the requested month
list($month, $day, $year) = explode('/', date('m/d/Y', $timestamp));
$first_day_of_month = date('w', mktime(0, 0, 0, $month, 1, $year));

$total_days = date('t', $timestamp);

// add new event
if (isset($_POST['submitted']))
{
    // validate incoming values
    $evt_name = (isset($_POST['evt_name'])) ? $_POST['evt_name'] : '';
    $evt_name = trim($evt_name);
    if (!$evt_name)
    {
        $evt_name = 'Unknown';
    }
    $evt_pm = (isset($_POST['evt_pm']) && $_POST['evt_pm'] == 'yes'),
    $evt_hour = (isset($_POST['evt_hour'])) ? (int)$_POST['evt_hour'] : 0;
    if ($evt_pm)
    {
        $evt_hour += 12;
    }
    if ($evt_hour == 24)
    {
       $evt_hour = 12;
    }
    else if ($evt_hour == 12)
    {
       $evt_hour = 0;
    }
    $evt_min = (isset($_POST['evt_min'])) ? (int)$_POST['evt_min'] : 0;
    $evt_notify = (isset($_POST['evt_notify']) &&
        $_POST['evt_notify'] == 'yes'),
    // add to database
    $query = sprintf('INSERT INTO %sCALENDAR (EVENT_NAME, EVENT_TSTAMP, ' .
        'NOTIFY) VALUES ("%s", "%04d-%02d-%02d %02d:%02d:00", %d)',
        DB_TBL_PREFIX,
        mysql_real_escape_string($evt_name, $GLOBALS['DB']),
        $year, $month, $day,
        $evt_hour, $evt_min,
        $evt_notify);
    mysql_query($query, $GLOBALS['DB']);
}

// output table header
ob_start();
echo '<table id="day_calendar">';
echo '<tr id="day_calendar_header"><th colspan="2">';
echo '<a href="'. htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('−1 day', $timestamp) . '">&lt;</a> &nbsp; ';
echo date('l F d, Y', $timestamp);
echo '&nbsp; <a href="'. htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('+1 day', $timestamp) . '">&gt;</a>';
echo '</th></tr>';

// output cells

for ($i = DAY_HR_START; $i <= DAY_HR_END; $i++)
{
    for ($j = 0; $j < 60; $j += 15)
    {
        echo '<tr>';

        if ($i < 12)
        {
            printf('<td class="time">%d:%02d %s</td>', $i, $j, 'AM'),
        }
        else if ($i > 12)
        {
            printf('<td class="time">%d:%02d %s</td>', $i − 12,
                $j, 'PM'),
        }
        else
        {
            printf('<td class="time">%d:%02d %s</td>', $i, $j, 'PM'),
        }
        echo '<td class="event">';

        $query = sprintf('SELECT EVENT_NAME FROM %sCALENDAR WHERE ' .
            'EVENT_TSTAMP = "%04d-%02d-%02d %02d:%02d:00"',
            DB_TBL_PREFIX,
            $year, $month, $day,
            $i, $j);
        $result = mysql_query($query, $GLOBALS['DB']);

        if (mysql_num_rows($result))
        {
            while ($row = mysql_fetch_assoc($result))
            {
                echo '<div>' . htmlspecialchars($row['EVENT_NAME']) .
                    '</div>';
            }
        }
        else
        {
            echo '&nbsp;';
        }
        mysql_free_result($result);
        echo '</td>';
        echo '</tr>';
    }
}
echo '</table>';

// display month calendar
echo '<table id="calendar">';
echo '<tr id="calendar_header"><th colspan="7">';
echo '<a href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('−1 month', $timestamp) . '">&lt;</a> &nbsp; ';
echo date('F', $timestamp) . ' ' . $year;

echo '&nbsp; <a href="' . htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
    strtotime('+1 month', $timestamp) . '">&gt;</a>';
echo '</th></tr>';
echo '<tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th>' .
    '<th>Fri</th><th>Sat</th></tr>';
$current = 1;
while ($current <= $total_days)
{
    echo '<tr class="calendar_dates">';
    for ($i = 0; $i < 7; $i++)
    {
        if (($current == 1 && $i < $first_day_of_month) ||
            ($current > $total_days))
        {
            echo '<td class="empty">&nbsp</td>';
            continue;
        }
        echo '<td><a href="' . htmlspecialchars($_SERVER['PHP_SELF']) .
            '?t=' . mktime(0, 0, 0, $month, $current, $year) . '">' .
            $current . '</a></td>';
        $current++;
    }
    echo '</tr>';
}
echo '</table>';

// Form to add event
?>
<h2>Add Event</h2>
<form action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']) . '?t=' .
 $timestamp; ?>" method="post">
 <table>
  <tr>
   <td class="label"><label for="evt_name">Event:</label></td>
   <td><input type="text" id="evt_name" name="evt_name"></td>
  </tr><tr>
   <td class="label"><label for="evt_hour">Time:</label></td>
   <td>
    <select name="evt_hour" id="evt_hour">
     <option value="12">12</option>
<?php

    for ($i = 1; $i < 12; $i++)
    {
        printf('<option value="%d">%02d</option>', $i, $i);
    }
?>
    </select> : <select name="evt_min">
<?php
    for ($i = 0; $i < 59; $i += 15)
    {
            printf('<option value="%d">%02d</option>', $i, $i);
    }
?>

</select>
    <select name="evt_pm">
     <option value="no">AM</option>
     <option value="yes">PM</option>
    </select>
   </td>
  </tr><tr>
   <td class="label">Notify</td>
   <td>
    <input type="radio" name="evt_notify" id="evt_notify_yes" value="yes"
     checked="checked"/>
    <label for="evt_notify_yes">Yes</label>
    <input type="radio" name="evt_notify" id="evt_notify_no" value="no"/>
    <label for="evt_notify_no">No</label>
   </td>
  </tr><tr>
   <td></td>
   <td>
    <input type="hidden" name="submitted" value="true"/>
    <input type="submit" value="Add Event"/></td>
  </tr>
 </table>
</form>

<h2>Also Scheduled</h2>
<?php
// retrieve and display events that fall outside the daily-view hours
$query = sprintf('SELECT EVENT_NAME, UNIX_TIMESTAMP(EVENT_TSTAMP) AS ' .
     'EVENT_TSTAMP FROM %sCALENDAR WHERE EVENT_TSTAMP NOT BETWEEN ' .
     '"%4d-%02d-%02d %02d:00:00" AND "%4d-%02d-%02d %02d:59:59" ORDER BY ' .
     'EVENT_TSTAMP ASC, EVENT_NAME ASC',
     DB_TBL_PREFIX,
     $year, $month, $day, DAY_HR_START,
     $year, $month, $day, DAY_HR_END);
$result = mysql_query($query, $GLOBALS['DB']);

echo '<ul>';
if (mysql_num_rows($result))
{
    while ($row = mysql_fetch_assoc($result))
    {
        echo '<li>' . date('h:i A - ', $row['EVENT_TSTAMP']) .
        htmlspecialchars($row['EVENT_NAME']) . '</li>';
    }
}
else
{
    echo '<p><i>No other events scheduled</i></p>';
}
mysql_free_result($result);
echo '</ul>';

// link to download iCal file

echo '<p><a href="export.php">Export as iCalendar file</a></p>';


$GLOBALS['TEMPLATE']['content'] = ob_get_clean();

$GLOBALS['TEMPLATE']['extra_head'] = '<link rel="stylesheet"' .
    'type="text/css" href="css/daily.css"/>';

include '../templates/template-page.php';
?>

5.3.4. Sending Reminders

The project requirements also call for the ability for reminders to be sent when an event approaches. The first part of this is already in place with the form — selecting the option to be reminded or not. The preference is stored in the database for later examination. A separate script must be written to run periodically to check the database for upcoming events with the preference set. The script can then be scheduled the same way the mailing list scripts in chapter 3 were.

The script must know where to send the reminders so the e-mail address is provided as a constant. It must also determine the current date and time values to be able to construct the desired WHERE clause in the query. The query retrieves any events from the database that are scheduled to take place in the next hour from the current 15-minute timeframe, constructs an e-mail message, and mails it out.

Here is the complete code listing for public_files/notify.php. Because it will be run as a shell script instead of in response to a user request, the first line must be #! /usr/bin/php (or whichever path your installation's PHP interpreter resides at) and the file's execute permissions must be set correctly.

#! /usr/bin/php
<?php
include '../lib/common.php';
include '../lib/db.php';

// the e-mail address that will receive reminders
define('E-mail_ADDR', '[email protected]);

// determine the current date and time values
list($month, $day, $year, $hour, $minute, $am) = explode('/',
    date('m/d/Y/G/i/A'));

// retrieve upcoming events
$query = sprintf('SELECT EVENT_NAME, UNIX_TIMESTAMP(EVENT_TSTAMP) AS ' .
    'EVENT_TSTAMP FROM %sCALENDAR WHERE NOTIFY = 1 AND EVENT_TSTAMP BETWEEN ' .
    '"%4d-%02d-%02d %02d:%02d:00" AND "%4d-%02d-%02d %02d:%02d:00" ORDER BY ' .
    'EVENT_TSTAMP ASC, EVENT_NAME ASC',
     DB_TBL_PREFIX,
     $year, $month, $day, $hour, $minute,
     $year, $month, $day, $hour, $minute + 15);

$result = mysql_query($query, $GLOBALS['DB']);
if (mysql_num_rows($result))
{
    // construct the reminder message

$msg = 'Don't forget!  You have the following events scheduled:' . "

";
    while ($row = mysql_fetch_assoc($result))
    {
        $msg .= '  * ' . date('h:i A - ', $row['EVENT_TSTAMP']) .
            $row['EVENT_NAME'] . "
";
    }

    // send the message
    mail(E-mail_ADDR, "Reminders for $month/$day/$year $hour:$minute $am", $mgs);
}
mysql_free_result($result);
mysql_close($GLOBALS['DB']);
?>

The script is written to look at events scheduled in the next hour from the current 15-minute timeframe, so it should be scheduled to run once every 15 minutes. You may refer back to chapter 3 if you need to review scheduling a script with cron or the Windows Scheduled Tasks applet.

5.3.5. Exporting the Calendar

The final feature of the personal calendar application is the ability to export the saved entries as an iCalendar file so they can easily be shared with others. iCalendar is a text-based format to exchange calendar information and is standardized as RFC 2445 (available online at http://rfc.net/rfc2445.txt). The iCalendar format defines various components such as a calendar, an event, an alarm, busy times, and to-do lists. However, the only ones used in this application are the calendar, event, and alarm.

Some components may nest within other components and each component follows the schema BEGIN:component, properties, or other components then END:component. Each property is listed as an identifier and its value separated by a colon, one on each line. Each line terminates with a carriage return and a new line character ( ).

The iCalendar file begins with BEGIN:VCALENDAR and ends with END:VCALENDAR. Between the two delimiters are contained a few properties necessary to indicate which version of iCalendar is being adhered to and the collection of other components which populates the calendar.

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Wrox//PHP Reuse//EN
...
END:VCALENDAR

The component that collects several properties to define an event is VEVENT. In particular, the properties I'm interested in are DTSTART and DTEND, which define the starting and ending time of an event and SUMMARY, which provides a brief textual description. Here's an example of an event listing:

BEGIN:VEVENT
DTSTART:20071228T103000
DTEND:20071228T110000
SUMMARY:Dentist Appointment
END:VEVENT

The alarm component VALARM may be nested within a VEVENT and contains information an iCalendar application uses to trigger reminders. The properties I'm interested in are ACTION and TRIGGER. The TRIGGER property defines when to trigger the alert and the ACTION property defines how the alarm is issued. The application can be instructed to display the alert (DISPLAY), e-mail it (E-mail), play a sound (AUDIO), or trigger some other program (PROCEDURE). Here's the sample event again, but this time with a defined alert component to issue an on-screen reminder an hour beforehand.

BEGIN:VEVENT
DTSTART:20071228T103000
DTEND:20071228T110000
SUMMARY:Dentist Appointment
BEGIN:VALARM
TRIGGER:-PT60M
SUMMARY:You have a dentist appointment in 1 hour
ACTION:DISPLAY
END:VALARM
END:VEVENT

Here is the complete code for public_files/export.php, which queries the database and constructs an iCalendar file. A listing of all the properties for the calendar, event, and alarm components is provided in Table 5-4.

<?php
include '../lib/common.php';
include '../lib/db.php';

define('CRLF', "
");

// retrieve all events
$query = sprintf('SELECT EVENT_NAME, UNIX_TIMESTAMP(EVENT_TSTAMP) AS ' .
     'EVENT_TSTAMP, NOTIFY FROM %sCALENDAR ORDER BY EVENT_TSTAMP ASC, ' .
     'EVENT_NAME ASC',
     DB_TBL_PREFIX);
$result = mysql_query($query, $GLOBALS['DB']);

// generate iCalendar
ob_start();
echo 'BEGIN:VCALENDAR' . CRLF;
echo 'PRODID:-//Wrox//PHP Reuse//EN' . CRLF;
echo 'VERSION:2.0' . CRLF;

while ($row = mysql_fetch_assoc($result))
{
    echo 'BEGIN:VEVENT' . CRLF;
    echo 'DTSTART:' . date('YmdTHis', $row['EVENT_TSTAMP']) . CRLF;
    echo 'DTEND:' . date('YmdTHis', strtotime('+30 minutes',
        $row['EVENT_TSTAMP'])) . CRLF;
    echo 'SUMMARY:' . htmlspecialchars($row['EVENT_NAME']) . CRLF;
    if ($row['NOTIFY'])
    {
        echo 'BEGIN:VALARM' . CRLF;
        echo 'ACTION:DISPLAY' . CRLF;

echo 'SUMMARY:' . date('m/d/Y H:i A - ', $row['EVENT_TSTAMP']) .
            htmlspecialchars($row['EVENT_NAME']) . CRLF;
        echo 'TRIGGER:-PT60M' . CRLF;
        echo 'END:VALARM' . CRLF;
    }
    echo 'END:VEVENT' . CRLF;
}
mysql_free_result($result);

echo 'END:VCALENDAR' . CRLF;
$ics = ob_get_clean();
// send iCalendar file to browser
header('Content-Type: text/calendar'),
header('Content-Disposition: attachment; filename="export.ics";'),
header('Content-Transfer-Encoding: binary'),
header('Content-Length: ' . strlen($ics));
echo $ics;

mysql_close($GLOBALS['DB']);
?>

Table 5-4. Properties for the iCalendar Calendar, Event, and Alarm Components
Component/PropertyDescription
VCALENDARCore object that contains all other properties and components that define the calendar
CALSCALESets the calendar scale, default is GREGORIAN
METHODDefines the method associated with the calendar (used by the iCalendar Transport-Independent Interoperability Protocol)
PRODID(Required) The product identifier of the application used to generate the iCalendar file
VERSION(Required) iCalendar version
VEVENTComponent that groups properties to describe an event
ATTACHAssociates an external document with the event
ATTENDEESpecifies an event participant
CATEGORIESAssociates the component with a particular category for organizational purposes
CLASSSets the access scope for the event, default is PUBLIC
COMMENTAssociates a short comment with the event
CONTACTAssociates contact information with the event
CREATEDSpecifies the date and time the event was created
DESCRIPTIONProvides long description of an event
DTENDSpecifies the date and time the event ends
DTSTAMPSpecifies the date and time the event was created
DTSTART(Required) Specifies the date and time the event begins
DURATIONSpecifies the duration of the event (reoccurring events)
EXDATESpecifies an exception date/time for the event (reoccurring events)
EXRULEDefines a repeating pattern rule (reoccurring events)
GEOSpecifies the geographical coordinates (latitude and longitude) where the event will take place
LAST-MODSpecifies the date and time the information associated with the event was last modified
LOCATIONSpecifies the location where the event will take place
ORGANIZERSpecifies the organizer of an event (group calendars)
PRIORITYSpecifies the relative priority of the event
RDATESpecifies the dates and times for a reoccurrence set (reoccurring events)
RECURRENCE-IDUsed with SEQ and UID to identify a specific instance of a reoccurring event (reoccurring events)
RELATEDSpecifies the relationship between an alarm trigger and the beginning or end of the event, default is START
RESOURCESSpecifies equipment or resources needed at the event
RRULESpecifies a repeating pattern rule (reoccurring events)
SEQSpecifies the revision number in a sequence of revisions to the event
STATUSSpecifies the completion or confirmation status for an event (group calendars)
SUMMARYProvides a short summary of the event
TRANSPSpecifies whether the time occupied by an event is marked "busy" or not
UIDSpecifies a globally unique identifier
URLAssociates a URL with the event
VALARMComponent that groups properties to define an alarm
ACTION(Required) Specifies which action to invoke when triggering an alarm (may be AUDIO, DISPLAY, EMAIL or PROCEDURE)
ATTACHAssociates an external document with the alarm
DESCRIPTION(Required if ACTION is DISPLAY or EMAIL) Provides a more complete description of an event than the SUMMARY property
DURATIONSpecifies the duration of the alarm
SUMMARY(Required if ACTION is EMAIL) Provides a short summary for the alarm
TRIGGER(Required) Specifies when an alarm will be triggered

After the calendar information has been exported as an iCalendar file, it can be shared with others. Figures 5-3 and 5-4 show the information after it has been imported into a couple different desktop calendar programs. Figure 5-3 shows it in Microsoft Windows Calendar, which ships with its new Vista operating system; Figure 5-4 shows it in the Mozilla Thunderbird program running the Lightning integrated calendar extension.

Figure 5-3. Figure 5-3

Figure 5-4. Figure 5-4

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.86.56