Приглашаем посетить
Html (html.find-info.ru)

9.3 Dates and Times in Forms

Previous Table of Contents Next

9.3 Dates and Times in Forms

When you need a user to input a date in a form, the best thing to do is to use <select> menus. This generally restricts the possible input to whatever you display in the menus. The specific date or time information you need controls what you populate the <select> menus with.

9.3.1 A Single Menu with One Choice Per Day

If there are a small number of choices, you can have just one menu that lists all of them. Example 9-9 prints a <select> menu that lets a user pick one day in the coming week. The value for each option in the menu is an epoch timestamp corresponding to midnight on the displayed day.

Example 9-9. A day choice <select> menu
$midnight_today = mktime(0,0,0);

print '<select name="date">';

for ($i = 0; $i < 7; $i++) {

    $timestamp = strtotime("+$i day", $midnight_today);

    $display_date = strftime('%A, %B %d, %Y', $timestamp);

    print '<option value="' . $timestamp .'">'.$display_date."</option>\n";

}

print "\n</select>";

On October 20, 2004, Example 9-9 prints:

<select name="date"><option value="1098244800">Wednesday, October 20, 2004</option>

<option value="1098331200">Thursday, October 21, 2004</option>

<option value="1098417600">Friday, October 22, 2004</option>

<option value="1098504000">Saturday, October 23, 2004</option>

<option value="1098590400">Sunday, October 24, 2004</option>

<option value="1098676800">Monday, October 25, 2004</option>

<option value="1098763200">Tuesday, October 26, 2004</option>

If you're using the input_select( ) form helper function from Chapter 6, put the timestamps and display dates in an array inside the for( ) loop and then pass that array to input_select( ), as shown in Example 9-10.

Example 9-10. A day choice menu with input_select( )
require 'formhelpers.php';



$midnight_today = mktime(0,0,0);

$choices = array( );

for ($i = 0; $i < 7; $i++) {

    $timestamp = strtotime("+$i day", $midnight_today);

    $display_date = strftime('%A, %B %d, %Y', $timestamp);

    $choices[$timestamp] = $display_date;

}

input_select('date', $_POST, $choices);

Example 9-10 prints the same menu as Example 9-9.

9.3.2 Multiple Menus for Month, Day, and Year

To let a user enter an arbitrary date, provide separate menus for month, day, and year, as shown in Example 9-11.

Example 9-11. Multiple <select> menus for date picking
$months = array(1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 

                5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',

                9 => 'September', 10 => 'October', 11 => 'November', 

                12 => 'December');



print '<select name="month">';

// One choice for each element in $months

foreach ($months as $num => $month_name) {

    print '<option value="' . $num . '">' . $month_name ."</option>\n";

}

print "</select> \n";



print '<select name="day">';

// One choice for each day from 1 to 31

for ($i = 1; $i <= 31; $i++) {

    print '<option value="' . $i . '">' . $i ."</option>\n";

}

print "</select> \n";



print '<select name="year">';

// One choice for each year from last year to five years from now

for ($year = date('Y') -1, $max_year = date('Y') + 5; $year < $max_year; $year++) {

    print '<option value="' . $year . '">' . $year ."</option>\n";

}

print "</select> \n";

Example 9-11 displays a set of three menus like the ones shown in Figure 9-1.

Figure 9-1. Multiple <select> menus for date picking
figs/lphp_0901.gif


To display month, day, and year menus with the input_select( ) helper function, use the same $months array, but also build arrays of days and years. Pass these arrays to input_select( ). Example 9-12 prints the three menus using input_select( ).

Example 9-12. Date picking with input_select( )
require 'formhelpers.php';



$months = array(1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 

                5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',

                9 => 'September', 10 => 'October', 11 => 'November', 

                12 => 'December');



$days = array( );

for ($i = 1; $i <= 31; $i++) { $days[$i] = $i; }



$years = array( );

for ($year = date('Y') -1, $max_year = date('Y') + 5; $year < $max_year; $year++) {

    $years[$year] = $year;

}



input_select('month',$_POST, $months);

print ' ';

input_select('day',  $_POST, $days);

print ' ';

input_select('year', $_POST, $years);

Note that each element of the $days and $years arrays in Example 9-12 has a key equal to its value. This is to ensure that each choice displayed in the menu is the same as the value attribute of the corresponding <option> tag.

One common application for date input is checking for credit card expiration. Example 9-13 displays a form with month and year menus for inputting a credit card expiration date. The program checks whether the submitted month and year are before the current month and year. If so, the associated credit card is expired.

Example 9-13. Checking a credit card expiration date
require 'formhelpers.php';



$months = array(1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 

                5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',

                9 => 'September', 10 => 'October', 11 => 'November', 

                12 => 'December');



$years = array( );

for ($year = date('Y'), $max_year = date('Y') + 10; $year < $max_year; $year++) {

    $years[$year] = $year;

}



if ($_POST['_submit_check']) {

    if ($form_errors = validate_form( )) {

        show_form($form_errors);

    } else {

        process_form( );

    }

} else {

    show_form( );

}



function show_form($errors = '') {

    if ($errors) {

        print 'You need to correct the following errors: <ul><li>';

        print implode('</li><li>',$errors);

        print '</li></ul>';

    }



    print '<form method="POST" action="' . $_SERVER['PHP_SELF'] . '">';



    print 'Expiration Date: ';

    input_select('month',$_POST,$GLOBALS['months']);

    print ' ';

    input_select('year', $_POST,$GLOBALS['years']);

    print '<br/>';

    input_submit('submit','Check Expiration');



    // the hidden _submit_check variable and the end of the form

    print '<input type="hidden" name="_submit_check" value="1"/>';

    print '</form>';

}



function validate_form( ) {

    $errors = array( );

    

    // Make sure a valid month and year were entered

    if (! array_key_exists($_POST['month'], $GLOBALS['months'])) {

        $errors[  ] = 'Please select a valid month.';

    }

    if (! array_key_exists($_POST['year'], $GLOBALS['years'])) {

        $errors[  ] = 'Please select a valid year.';

    }

    // Make sure the month and the year are the current month

    // and year or after

    $this_month = date('n');

    $this_year  = date('Y');



    if ($_POST['year'] < $this_year) {

        // If the year entered is in the past, the credit card

        // is expired

        $errors[  ] = 'The credit card is expired.';

        

    } elseif (($_POST['year'] =  = $this_year) &

              ($_POST['month'] < $this_month)) {

        // If the year entered is this year and the month entered

        // is before this month, then the credit card is expired

        $errors[  ] = 'The credit card is expired.';

    }



    return $errors;

}



function process_form( ) {

    print "You entered a valid expiration date.";

}

The process_form( ) function in Example 9-13 just prints a message saying that the expiration date is acceptable. More typically, a credit card-handling program also needs to verify that the credit card number itself is valid. A PHP program that does this is available at http://www.analysisandsolutions.com/software/ccvs/.

9.3.3 Multiple Menus for Hour and Minute

Use <select> menus to allow for time input as well. Use one menu for hours and one for minutes. To keep the minutes menu a manageable size, just display choices in 5-minute increments. If you use 12-hour time for the hours menu, also include an am/pm menu. Example 9-14 displays time select menus, and Example 9-15 does the same thing, but uses the input_select( ) helper function.

Example 9-14. Multiple <select> menus for time picking
print '<select name="hour">';

for ($hour = 1; $hour <= 12; $hour++) {

    print '<option value="' . $hour . '">' . $hour ."</option>\n";

}

print "</select>:";



print '<select name="minute">';

for ($minute = 0; $minute < 60; $minute += 5) {

    printf('<option value="%02d">%02d</option>', $minute, $minute);

}

print "</select> \n";



print '<select name="ampm">';

print '<option value="am">am</option';

print '<option value="pm">pm</option';

print '</select>';

Example 9-15. Time picking with input_select( )
require 'formhelpers.php';



$hours = array( );

for ($hour = 1; $hour <= 12; $hour++) { $hours[$hour] = $hour; }



$minutes = array( );

for ($minute = 0; $minute < 60; $minute += 5) {

    $formatted_minute = sprintf('%02d', $minute);

    $minutes[$formatted_minute] = $formatted_minute;

}



input_select('hour', $_POST, $hours);

print ':';

input_select('minute', $_POST, $minutes);

input_select('ampm', $_POST, array('am' => 'am', 'pm' => 'pm'));

There are two important formatting details to note about Examples Example 9-14 and Example 9-15. The first is that they both print a colon between the hour menu and the minute menu. This is to make the layout of the menus mirror how hours and minutes are normally written (at least in the U.S.). The second is the use of printf( ) in Example 9-14 and a new function, sprintf( ), in Example 9-15.

Both of these functions accomplish the same goal: padding the minutes that are less than 10 with a leading 0. The printf( ) in Example 9-14 uses the %02d rule, which means "print an integer, make it take up at least two characters, padding with leading zeroes if necessary." In Example 9-15, sprintf( ) uses the same rule. The sprintf( ) function behaves identically to printf( ), except it returns the formatted string instead of printing it. In Example 9-15, when $minute is 5, sprintf( ) returns 05, which is assigned to $formatted_minute and then put into the $minutes array.

9.3.4 Processing Date and Time <select> Menus

When you have individual time/date part form elements in a form, your process_form( ) function should construct an epoch timestamp out of the parts in the form to use in the program. Example 9-16 prints a form with month, day, year, hour, and minute menus. Its validate_form( ) function checks that all of these form parameters are submitted with acceptable values.

The process_form( ) function in Example 9-16 prints out the date of the first New York PHP users group meeting after the submitted date. NYPHP meetings are at 6:30 p.m. on the fourth Thursday of every month. So, process_form( ) uses mktime( ) to calculate an epoch timestamp from the form parameters, and then uses strtotime( ) to find the appropriate meeting date. If the submitted date is the same day as a meeting, process_form( ) uses the submitted time to report whether the meeting has started already.

Example 9-16. Doing calculations with a user-submitted date
<?php

require 'formhelpers.php';



// Set up arrays of months, days, years, hours, and minutes 

$months = array(1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', 

                5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August',

                9 => 'September', 10 => 'October', 11 => 'November', 

                12 => 'December');



$days = array( );

for ($i = 1; $i <= 31; $i++) { $days[$i] = $i; }



$years = array( );

for ($year = date('Y') -1, $max_year = date('Y') + 5; $year < $max_year; $year++) {

    $years[$year] = $year;

}



$hours = array( );

for ($hour = 1; $hour <= 12; $hour++) { $hours[$hour] = $hour; }



$minutes = array( );

for ($minute = 0; $minute < 60; $minute+=5) {

    $formatted_minute = sprintf('%02d', $minute);

    $minutes[$formatted_minute] = $formatted_minute;

}



if ($_POST['_submit_check']) {

    // If validate_form( ) returns errors, pass them to show_form( )

    if ($form_errors = validate_form( )) {

        show_form($form_errors);

    } else {

        // The submitted data is valid, so process it

        process_form( );

    }

} else {

    // The form wasn't submitted, so display

    show_form( );

}



function show_form($errors = '') {

    global $hours, $minutes, $months, $days, $years;



    // If the form is submitted, get defaults from submitted variables

    if ($_POST['_submit_check']) {

        $defaults = $_POST;

    } else {

        // Otherwise, set our own defaults: the current time and date parts

        $defaults = array('hour'  => date('g'),

                          'ampm'  => date('a'),

                          'month' => date('n'),

                          'day'   => date('j'),

                          'year'  => date('Y'));

        

        // Because the choices in the minute menu are in five-minute increments,

        // if the current minute isn't a multiple of five, we need to make it

        // into one.

        $this_minute = date('i');

        $minute_mod_five = $this_minute % 5;

        if ($minute_mod_five != 0) { $this_minute -= $minute_mod_five;  }

        $defaults['minute'] = sprintf('%02d', $this_minute);

    }



    // If errors were passed in, put them in $error_text (with HTML markup)

    if ($errors) {

        print 'You need to correct the following errors: <ul><li>';

        print implode('</li><li>',$errors);

        print '</li></ul>';

    }



    print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';

    print 'Enter a date and time:';

    

    input_select('hour',$defaults,$hours);

    print ':';

    input_select('minute',$defaults,$minutes);

    input_select('ampm', $defaults,array('am' => 'am', 'pm' => 'pm'));

    input_select('month',$defaults,$months);

    print ' ';

    input_select('day',$defaults,$days);

    print ' ';

    input_select('year',$defaults,$years);

    print '<br/>';

    input_submit('submit','Find Meeting');

    print '<input type="hidden" name="_submit_check" value="1"/>';

    print '</form>';

}



function validate_form( ) {

    global $hours, $minutes, $months, $days, $years;

 

    $errors = array( );

   

    if (! array_key_exists($_POST['month'], $months)) {

        $errors[  ] = 'Select a valid month.';

    }

    if (! array_key_exists($_POST['day'], $days)) {

        $errors[  ] = 'Select a valid day.';

    }

    if (! array_key_exists($_POST['year'], $years)) {

        $errors[  ] = 'Select a valid year.';

    }

    if (! array_key_exists($_POST['hour'], $hours)) {

        $errors[  ] = 'Select a valid hour.';

    }

    if (! array_key_exists($_POST['minute'], $minutes)) {

        $errors[  ] = 'Select a valid minute.';

    }

    if (($_POST['ampm'] != 'am') && ($_POST['ampm'] != 'pm')) {

        $errors[  ] = 'Select a valid am/pm choice.';

    }



    return $errors;

}



function process_form( ) {

    

    // Before we can feed the form parameters to mktime( ), we must

    // convert the hour to a 24-hour value with influence from 

    // $_POST['ampm']

    

    if (($_POST['ampm'] =  = 'am') & ($_POST['hour'] =  = 12)) {

        // 12 am is 0 in 24-hour time

        $_POST['hour'] = 0;

    } elseif (($_POST['ampm'] =  = 'pm') & ($_POST['hour'] != 12)) {

        // For all pm times except 12 pm, add 12 to the hour

        // 1pm becomes 13, 11 pm becomes 23, but 12 pm (noon)

        // stays 12

        $_POST['hour'] += 12;

    }



    // Make an epoch timestamp for the user-entered date

    $timestamp = mktime($_POST['hour'], $_POST['minute'], 0,

                        $_POST['month'], $_POST['day'], $_POST['year']);





    // How to figure out the next NYPHP meeting on or after the user-entered date:

    // If $timestamp is on or before the fourth thursday of the month, then use the NYPHP

    // meeting date for $timestamp's month

    // Otherwise, use the NYPHP meeting date for the next month.



    // Midnight on the user-entered date

    $midnight  = mktime(0,0,0, $_POST['month'], $_POST['day'], $_POST['year']);

    // Midnight on the first of the user-entered month

    $first_of_the_month = mktime(0,0,0,$_POST['month'],1,$_POST['year']);

    // Midnight on the fourth thursday of the user-entered month 

    $month_nyphp = strtotime('fourth thursday',$first_of_the_month);

    

    if ($midnight < $month_nyphp) {

        // The user-entered date is before the meeting day

        print "NYPHP Meeting this month: ";

        print date('l, F j, Y', $month_nyphp);

    } elseif ($midnight =  = $month_nyphp) {

        // The user-entered date is a meeting day

        print "NYPHP Meeting today. ";

        $meeting_start = strtotime('6:30pm', $month_nyphp);

        // If it's afer 6:30pm, say that the meeting has already started

        if ($timestamp > $meeting_start) {

            print "It started at 6:30 but you entered ";

            print date('g:i a', $timestamp);

        }

    } else {

        // The user-entered date is after a meeting day, so find the

        // meeting day for next month

        $first_of_next_month = mktime(0,0,0,$_POST['month'] + 1,1,$_POST['year']);

        $next_month_nyphp = strtotime('fourth thursday',$first_of_next_month);

        print "NYPHP Meeting next month: ";

        print date('l, F j, Y', $next_month_nyphp);

    }

}

?>

The show_form( ) function in Example 9-16 uses date( ) to set the form element defaults to the current time and date. Some fancy footwork is required to calculate the correct minute value. Since the choices in the minute menu are each multiples of 5 (such as 00, 05, 10, 15, and so on), the default value has to be a multiple of 5 too. If the current minutes value (what date('i') reports) is something like 27, then it needs to be bumped down to 25 so it's a valid choice. The expression $minute_mod_five = $this_minute % 5; sets $minute_mod_five to the remainder of dividing $this_minute by 5. If $this_minute is 27, $minute_mod_5 is set to 2. Subtracting 2 from $this_minute makes it 25, an appropriate default value.

The process_form( ) does the actual date and time math. First, the submitted hour parameter is converted into the correct 24-hour value. This is necessary because mktime( ) expects hours in the range of 0-23, not 1-12. Then, process_form( ) creates the epoch timestamps it needs with mktime( ) and strtotime( ). Based on the relationship between $midnight and $month_nyphp, it prints an appropriate message describing the next NYPHP meeting.

If the user-entered date is after the current month's meeting day, process_form( ) figures out the next month's meeting day by obtaining the epoch timestamp for the first of the next month with mktime( ) and then feeding that to strtotime( ) to get the epoch timestamp of the fourth Thursday of the next month. This series of calculations takes advantage of a handy feature of mktime( ): it automatically handles month or day values that are too big.

The epoch timestamp for the first day of the next month is calculated by this line:

$first_of_next_month = mktime(0,0,0,$_POST['month'] + 1,1,$_POST['year']);

If the submitted month is 10 and the submitted year is 2004, then the call to mktime( ) is mktime(0,0,0,11,1,2005): midnight on November 1, 2005. But what if the submitted month is 12? Then the call to mktime( ) is mktime(0,0,0,13,1,2005). There is no thirteenth month in 2005, but mktime( ) interprets this as meaning "the first month of the next year." Similarly, if you tell mktime( ) to find the epoch timestamp for noon on the 32nd day of March, it returns the value corresponding to noon on April 1st.

    Previous Table of Contents Next