FORMATTING TEXT AND DATES 391 Period Specifier Description Example %c Number without leading zero 1, 9 %d With leading zero 01, 25 %e Without leading zero 1, 25 Day of month %D With English text suffix 1st, 25th %W Full text Monday, Thursday Weekday name %a Abbreviated name, three letters Mon, Thu %H 24-hour clock with leading zero 01, 23 %k 24-hour clock without leading zero 1, 23 %h 12-hour clock with leading zero 01, 11 Hour %l (lowercase “L”) 12-hour clock without leading zero 1, 11 Minutes %i With leading zero 05, 25 Seconds %S With leading zero 08, 45 AM/PM %p As explained earlier, when using a function in a SQL query, assign the result to an alias using the AS keyword. Referring to Table 14-1, you can format the date in the created column of the blog table in a common U.S. style and assign it to an alias like this: DATE_FORMAT(created, '%c/%e/%Y') AS date_created To format the same date in European style, reverse the first two specifiers like this: DATE_FORMAT(created, '%e/%c/%Y') AS date_created If you use the original column name as the alias, it converts the dates to strings, which frequently plays havoc with the sort order. CHAPTER 14 392 PHP Solution 14-2: Formatting a MySQL date or timestamp This PHP solution formats the dates in the blog entry management page from Chapter 13. 1. Open blog_list_mysqli.php or blog_list_pdo.php in the admin folder, and locate the SQL query. It looks like this: $sql = 'SELECT * FROM blog ORDER BY created DESC'; Change it like this: $sql = 'SELECT article_id, title, DATE_FORMAT(created, "%a, %b %D, %Y") AS date_created FROM blog ORDER BY created DESC'; I used single quotes around the whole SQL query, so the format string inside DATE_FORMAT() needs to be in double quotes. Make sure there is no gap before the opening parenthesis of DATE_FORMAT(). The format string begins with %a, which displays the first three letters of the weekday name. If you use the original column name as the alias, the ORDER BY clause sorts the dates in reverse alphabetical order: Wed, Thu, Sun, and so on. Using a different name as the alias ensures that the dates are still ordered chronologically. 2. In the first table cell in the body of the page, change $row['created'] to $row['date_created'] to match the alias in the SQL query. 3. Save the page, and load it into a browser. The dates should now be formatted as shown in Figure 14-4. Experiment with other specifiers to suit your preferences. Figure 14-4. The MySQL timestamps are now nicely formatted. Updated versions of blog_list_mysqli.php and blog_list_pdo.php are in the ch14 folder. Adding to and subtracting from dates When working with dates, its often useful to add or subtract a specific time period. For instance, you may want to display items that have been added to the database within the past seven days or stop displaying articles that havent been updated for three months. MySQL makes this easy with DATE_ADD() and DATE_SUB(). Both functions have synonyms called ADDDATE() and SUBDATE(), respectively. Download from Wow! eBook <www.wowebook.com> FORMATTING TEXT AND DATES 393 The basic syntax is the same for all of them and looks like this: DATE_ADD( date , INTERVAL value interval_type ) When using these functions, date can be the column containing the date you want to alter, a string containing a particular date (in YYYY-MM-DD format), or a MySQL function, such as NOW(). INTERVAL is a keyword followed by a value and an interval type, the most common of which are listed in Table 14-2. Table 14-2. Most frequently used interval types with DATE_ADD() and DATE_SUB() Interval type Meaning Value format DAY Days Number DAY_HOUR Days and hours String presented as 'DD hh' WEEK Weeks Number MONTH Months Number QUARTER Quarters Number YEAR Years Number YEAR_MONTH Years and months String presented as 'YY-MM' The interval types are constants, so dont add “S” to the end of DAY, WEEK, and so on to make them plural. One of the most useful applications of these functions is to display only the most recent items in a table. PHP Solution 14-3: Displaying items updated within the past week This PHP solution shows how to limit the display of database results according to a specific time interval. Use blog.php from PHP Solution 14-1. 1. Locate the SQL query in blog.php. It looks like this: $sql = 'SELECT * FROM blog ORDER BY created DESC'; Change it like this: $sql = 'SELECT * FROM blog WHERE updated > DATE_SUB(NOW(), INTERVAL 1 WEEK) ORDER BY created DESC'; This tells MySQL that you want only items that have been updated in the past week. 2. Save and reload the page in your browser. Depending on when you last updated an item in the blog table, you should see nothing or a limited range of items. If necessary, change the interval type to DAY or HOUR to test that the time limit is working. CHAPTER 14 394 3. Open blog_list_mysqli.php or blog_list_pdo.php, select an item that isnt displayed in blog.php, and edit it. Reload blog.php. The item that you have just updated should now be displayed. You can compare your code with blog_limit_mysqli.php and blog_limit_pdo.php in the ch14 folder. Inserting dates into MySQL MySQLs requirement for dates to be formatted as YYYY-MM-DD presents a headache for online forms that allow users to input dates. As you saw in Chapter 13, the current date and time can be inserted automatically by using a TIMESTAMP column or the MySQL NOW() function. Its when you need any other date that problems arise. If you can trust users to follow a set pattern for inputting dates, such as MM/DD/YYYY, you can use the explode() function to rearrange the date parts like this: if (isset($_POST['theDate'])) { $date = explode('/', $_POST['theDate']); $mysqlFormat = "$date[2]-$date[0]-$date[1]"; } This solution works, but as soon as someone deviates from the format, you end up with invalid dates in your database. Its better to ensure that dates are both valid and in the correct format. One way of doing so is to use a date picker widget that outputs the date in the ISO format, but widgets that rely on JavaScript are useless when visitors to your website have JavaScript disabled in their browsers. Eventually, this will become less of a problem when mainstream browsers support the new types of input fields specified in HTML5. To create a date input field, set the type attribute to date like this: <input name="departure" type="date" required id="departure"> As shown in Figure 14-5, Opera 10.62 automatically displays a date picker when you select this type of field. Browsers that dont understand the date type render the field as a normal text input field, so theres no need to wait for old browsers to die before you start using HTML5 form fields. Figure 14-5. Opera 10.62 automatically displays a date picker in an HTML5 form. FORMATTING TEXT AND DATES 395 Nevertheless, the most reliable method of gathering dates from an online form remains the use of separate input fields for month, day, and year. PHP Solution 14-4: Validating and formatting dates for MySQL input This PHP solution concentrates on checking the validity of a date and converting it to MySQL format. It s designed to be incorporated in an insert or update form of your own. 1. Create a page called date_converter.php, and insert a form containing the following code (or use date_converter_01.php in the ch14 folder): <form id="form1" method="post" action=""> <p> <label for="select">Month:</label> <select name="month" id="month"> <option value=""></option> </select> <label for="day">Date:</label> <input name="day" type="number" required id="day" max="31" min="1" maxlength="2"> <label for="year">Year:</label> <input name="year" type="number" required id="year" maxlength="4"> </p> <p> <input type="submit" name="convert" id="convert" value="Convert"> </p> </form> This code creates a drop-down menu called month and two input fields called day and year. The drop-down menu doesnt have any values at the moment, but it will be populated by a PHP loop. The day and year fields use the HTML5 number type and required attribute. The day field also has the max and min attributes to restrict the range to between 1 and 31. Browsers that support the new HTML5 form elements display number steppers alongside the fields and restrict the type and range of input. Other browsers render them as ordinary text input fields. For the benefit of older browsers, both have maxlength attributes that limit the number of characters accepted. 2. Amend the section that builds the drop-down menu like this: <select name="month" id="month"> <?php $months = array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug', ➥ 'Sep','Oct','Nov','Dec'); $thisMonth = date('n'); for ($i = 1; $i <= 12; $i++) { ?> <option value="<?php echo $i; ?>" <?php if ($i == $thisMonth) { echo ' selected'; } ?>> <?php echo $months[$i-1]; ?> </option> <?php } ?> </select> CHAPTER 14 396 This creates an array of month names and uses the date() function to find the number of the current month. A for loop then populates the menus <option> tags. I have set the initial value of $i to 1, because I want to use it for the value of the month. If the values of $i and $thisMonth are the same, the conditional statement inserts selected into the <option> tag. The final part of the script displays the name of the month by drawing it from the $months array. Because indexed arrays begin at 0, you need to subtract 1 from the value of $i to get the right month. 3. Save the page, and test it in a browser. In a browser that supports HTML5 form elements, it should look similar to Figure 14-6. In all browsers, the current month should be automatically displayed in the drop-down menu. Figure 14-6. Using separate input fields for date parts helps eliminate errors. 4. If you test the input fields, in most browsers, the Date field should accept no more than two characters, and the Year field a maximum of four. Even though this reduces the possibility of mistakes, you still need to validate the input and format the date correctly. 5. The code that performs all the checks is a custom function in utilitity_funcs.inc.php. It looks like this: function convertDateToMySQL($month, $day, $year) { $month = trim($month); $day = trim($day); $year = trim($year); $result[0] = false; if (empty($month) || empty($day) || empty($year)) { $result[1] = 'Please fill in all fields'; } elseif (!is_numeric($month) || !is_numeric($day) || !is_numeric($year)) { $result[1] = 'Please use numbers only'; } elseif (($month < 1 || $month > 12) || ($day < 1 || $day > 31) || ($year < 1000 || $year > 9999)) { $result[1] = 'Please use numbers within the correct range'; } elseif (!checkdate($month,$day,$year)) { $result[1] = 'You have used an invalid date'; } else { $result[0] = true; $result[1] = "$year-$month-$day"; } FORMATTING TEXT AND DATES 397 return $result; } The function takes three arguments: month, day, and year, all of which should be numbers. The first three lines of code trim any whitespace from either end of the input, and the next line initializes the first element of an array called $result. If the input fails validation, the first element of the array is false, and the second element contains an error message. If it passes validation, the first element of $result is true, and the second element contains the formatted date ready for insertion into MySQL. The series of conditional statements checks the input values to see if they are empty or not numeric. The third test looks for numbers within acceptable ranges. The range for years is dictated by the legal range for MySQL. In the unlikely event that you need a year out of that range, you must choose a different column type to store the data. By using a series of elseif clauses, this code stops testing as soon as it meets the first mistake. If the input has survived the first three tests, its then subjected to the PHP function checkdate(), which is smart enough to know when its a leap year and prevents mistakes such as September 31. Finally, if the input has passed all these tests, its rebuilt in the correct format for insertion into MySQL. 6. For testing purposes, add this code just below the form in the main body of the page: if (isset($_POST['convert'])) { require_once('utility_funcs.inc.php'); $converted = convertDateToMySQL($_POST['month'], $_POST['day'], $_POST['year']); if ($converted[0]) { echo 'Valid date: ' . $converted[1]; } else { echo 'Error: ' . $converted[1] . '<br>'; echo 'Input was: ' . $months[$_POST['month']-1] . ' ' . $_POST['day'] . ', ' . $_POST['year']; } } This checks whether the form has been submitted. If it has, it includes utility_funcs.inc.php (theres a copy in the ch14 folder) and passes the form values to the convertDateToMySQL() function, saving the result in $converted. If the date is valid, $converted[0] is true, and the formatted date is in $converted[1]. If the date cannot be converted to MySQL format, the else clause displays the error message stored in $converted[1], together with the original input. To display the correct value for the month, 1 is subtracted from the value of $_POST['month'], and the result is used as the key for the $months array. 7. Save the page, and test it by entering a date and clicking Convert. If the date is valid, you should see it converted to MySQL format, as shown in Figure 14-7. CHAPTER 14 398 Figure 14-7. The date has been validated and formatted for MySQL. Although the date shown at the bottom of Figure 14-7 doesnt use a leading zero for the month, its still valid. MySQL automatically adds the leading zero when storing the date. If you enter an invalid date, you should see an appropriate message instead (see Figure 14-8). Figure 14-8. The convertDateToMySQL() function rejects invalid dates. You can compare your code with date_converter_02.php in the ch14 folder. When creating an insert or update form for a table that requires a date from user input, add three fields for month, day, and year in the same way as in date_converter.php. Before inserting the form input into the database, include utilitity_funcs.inc.php (or wherever you decide to store the function), and use the convertDateToMySQL() function to validate the date parts and prepare them for insertion into the database. require_once('utility_funcs.inc.php'); $converted = convertDateToMySQL($_POST['month'], $_POST['day'], $_POST['year']); if ($converted[0]) { $date = $converted[1]; } else { $errors[] = $converted[1]; } If your $errors array has any elements, abandon the insert or update process, and display the errors. Otherwise, $date is safe to insert in the SQL query. FORMATTING TEXT AND DATES 399 The rest of this chapter is devoted to handling dates in PHP. Its an important but complex subject. I suggest that you skim through each section to familiarize yourself with PHPs date-handling functionality and return to this section when you need to implement a particular feature. Working with dates in PHP The way PHP handles dates and time underwent major changes in PHP 5.2 with the introduction of the DateTime and DateTimeZone classes. Further changes were introduced in PHP 5.3 through the addition of new DateTime methods and the DateInterval and DatePeriod classes. Prior to the changes, dates and time were handled exclusively as Unix timestamps—the number of seconds since midnight UTC (Coordinated Universal Time) on January 1, 1970. The new classes dont entirely replace the original ways of handling date and time information, but they are more flexible. PHP stores timestamps as 32-bit integers, restricting the upper limit of the range of available dates to January 2038. The new classes store date and time information internally as a 64-bit number, increasing the range from about 292 billion years in the past to the same number of years in the future. Table 14-3 summarizes the main date- and time-related classes and functions in PHP. Table 14-3. PHP date- and time-related classes and functions. Name Arguments Description Class DateTime Date string, DateTimeZone object Creates a time zone-sensitive object containing date and/or time information that can be used for calculations involving dates and times. DateTimeZone Time zone string Stores time zone information for use with DateTime objects. DateInterval Interval specification Represents a fixed amount of time in years, months, hours, etc. Requires PHP 5.3 or later. DatePeriod Start, interval, end/recurrence, options Calculates recurring dates over a set period or number of recurrences. Requires PHP 5.3 or later. Function time() None Generates a Unix timestamp for the current date and time. mktime() Hour, minute, second, month, date, year Generates a Unix timestamp for the specified date/time. CHAPTER 14 400 Name Arguments Description strtotime() Date string, timestamp Attempts to generate a Unix timestamp from an English textual description, such as “next Tuesday.” The returned value is relative to the second argument if supplied. date() Format string, timestamp Formats a date in English using the specifiers listed in Table 14-4. If the second argument is omitted, the current date and time are used. strftime() Format string, timestamp Same as date(), but uses the language specified by the system locale. All date and time information in PHP is stored according to the servers default time zone setting. Its common for web servers to be located in a different time zone from your target audience, so its useful to know how to change the default. Setting the default time zone The servers default time zone should normally be set in the date.timezone directive in php.ini; but if your hosting company forgets to do so, or you want to use a different time zone, you need to set it yourself. If your hosting company gives you control over your own version of php.ini, change the value of date.timezone there. That way, its automatically set for all your scripts. If your remote server runs Apache, you may be able to set a default time zone by putting the following in an .htaccess file in the site root: php_value date.timezone ' timezone ' Replace timezone with the correct setting for your location. You can find a full list of valid time zones at http://docs.php.net/manual/en/timezones.php. This works only if Apache has been set up to allow .htaccess to override default settings. If neither of those options is available to you, add the following at the beginning of any script that uses date or time functions (replacing timezone with the appropriate value): ini_set('date.timezone', ' timezone '); Creating a DateTime object To create a DateTime object, just use the new keyword followed by DateTime() like this: $now = new DateTime(); This creates an object that represents the current date and time according to the web servers clock and default time zone setting. . array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug', ➥ 'Sep','Oct','Nov','Dec'); $thisMonth = date('n');. $converted[1] . '<br>'; echo 'Input was: ' . $months[$_POST['month' ]-1 ] . ' ' . $_POST['day'] . ', ' . $_POST['year']; . working. CHAPTER 14 394 3. Open blog_list_mysqli .php or blog_list_pdo .php, select an item that isnt displayed in blog .php, and edit it. Reload blog .php. The item that you have just updated should