Dates and times are all over the place in a web application. In a shopping cart, you need to handle shipping dates of products. In a forum, you need to keep track of when messages are posted. In all sorts of applications, you need to keep track of the last time each user logged in so that you can tell them things like “15 new messages were posted since you last logged in.”
Handling dates and times properly in your programs is more complicated than handling strings or numbers. A date or a time is not a single value but a collection of values—month, day, and year, for example, or hour, minute, and second. Because of this, doing math with them can be tricky. Instead of just adding or subtracting entire dates and times, you have to consider their component parts and what the allowable values for each part are. Hours go up to 12 (or 24), minutes and seconds go up to 59, and not all months have the same number of days.
To ease this burden, PHP provides you with a class, DateTime,
that encapsulates all the information about a specific point in
time. With the methods of this class, you can print out a date or time
in the format of your choice, add or subtract two dates, and work with
time intervals.
In this book, the phrase time parts (or date parts or time-and-date parts) means an array or group of time-and-date components such as day, month, year, hour, minute, and second. Formatted time string (or formatted date string, etc.) means a string that contains some particular grouping of time-and-date parts—for example “Thursday, October 20, 2016” or “3:54 p.m.”
The simplest display of date or time is telling your users what time it is. For this, use the format() method of a DateTime object, as shown in Example 15-1.
$d=newDateTime();'It is now: ';$d->format('r');"\n";
At noon on October 20, 2016, Example 15-1 would print:
It is now: Thu, 20 Oct 2016 12:00:00 +0000
When you create a DateTime object, the time or date to store inside it is provided to the object constructor. If no argument is provided, as in Example 15-1, the current date and time are used. The format string passed to the format() method controls how the date and time are formatted for printing.
Individual letters in the format string translate into certain time values. Example 15-2 prints out a month, day, and year.
$d=newDateTime();$d->format('m/d/y');
At noon on October 20, 2016, Example 15-2 would print:
10/20/16
In Example 15-2, the m becomes the
month (10), the d becomes the day of the month
(20), and the y becomes the two-digit year (04).
Because the slash is not a format character that format() understands, it is left alone in the string that
format() returns.
Table 15-1 lists all of the special characters that
DateTime::format() understands.
| Type | Format character | Description | Range/example |
|---|---|---|---|
| Day | j | Day of the month; numeric | 1–31 |
| Day | d | Day of the month; numeric; leading zeros | 01–31 |
| Day | S | English ordinal suffix for day of month, text | st, th, nd, rd |
| Day | z | Day of the year; numeric | 0–365 |
| Day | w | Day of the week; numeric; 0 == Sunday | 0–6 |
| Day | N | Day of the week; numeric; 1 == Monday | 1–7 |
| Day | D | Abbreviated weekday name; text | Mon–Sun |
| Day | l | Full weekday name; text | Monday–Sunday |
| Week | W | Week number in the year (ISO-8601); numeric; leading zeros; week 01 is the first week that has at least four days in the current year; Monday is the first day of the week | 01-53 |
| Month | M | Abbreviated month name; text | Jan |
| Month | F | Full month name; text | January–December |
| Month | n | Month; numeric | 1–12 |
| Month | m | Month; numeric; leading zeros | 01–12 |
| Month | t | Month length in days; numeric | 28–31 |
| Year | y | Year; without century; numeric | 00–99 |
| Year | Y | Year; including century; numeric | 0000–9999 |
| Year | o | Year (ISO-8601); including century; numeric; year that the current week number (W) belongs to | 0000–9999 |
| Year | L | Leap year flag; 1 == yes | 0,1 |
| Hour | g | Hour; 12-hour clock; numeric | 1–12 |
| Hour | h | Hour; 12-hour clock; numeric; leading zeros | 01–12 |
| Hour | G | Hour; 24-hour clock; numeric | 0–23 |
| Hour | H | Hour; 24-hour clock; numeric; leading zeros | 00–23 |
| Hour | a | a.m. or p.m. designation | am, pm |
| Hour | A | A.M. or P.M. designation | AM, PM |
| Minute | i | Minutes; numeric; leading zeros | 00–59 |
| Second | s | Seconds; numeric; leading zeros | 00–59 |
| Second | u | Microseconds; numeric; leading zeros | 000000–999999 |
| Timezone | e | Timezone identifier; text | From supported timezones |
| Timezone | T | Timezone abbreviation; text | GMT, CEST, MDT, etc. |
| Timezone | O | Timezone difference to UTC in hours with sign; text | -1100 – +1400 |
| Timezone | P | Timezone difference to UTC in hours with sign and colon; text | -11:00 – +14:00 |
| Timezone | Z | Timezone difference to UTC in seconds; numeric | -39600–50400 |
| Other | I | Daylight Saving Time flag; 1 == yes | 0,1 |
| Other | B | Swatch Internet Time; numeric | 000–999 |
| Other | c | ISO-8601 formatted date; text | 2016-10-20T12:33:56+06:00 |
| Other | r | RFC-2822 formatted date; text | Thu, 20 Oct 2016 12:33:56 +0600 |
| Other | U | Seconds since 12:00:00 a.m. UTC on Jan 1, 1970 | 1476945236 |
To create a DateTime object that represents a specific time, pass that time as a first argument to the constructor. This argument is a string indicating the date and time you want the object to represent. A DateTime object understands a very wide variety of format strings. Whatever you are dealing with probably works, but an exhaustive list of all the possible formats is available at http://www.php.net/datetime.formats.
Example 15-3 shows a few date-and-time formats that the DateTime constructor understands.
DateTime understands// If only a time is supplied, the current date is used for day/month/year$a=newDateTime('10:36 am');// If only a date is supplied, the current time is used for hour/minute/second$b=newDateTime('5/11');$c=newDateTime('March 5th 2017');$d=newDateTime('3/10/2018');$e=newDateTime('2015-03-10 17:34:45');// DateTime understands microseconds$f=newDateTime('2015-03-10 17:34:45.326425');// Epoch timestamp must be prefixed with @$g=newDateTime('@381718923');// Common log format$h=newDateTime('3/Mar/2015:17:34:45 +0400');// Relative formats, too!$i=newDateTime('next Tuesday');$j=newDateTime("last day of April 2015");$k=newDateTime("November 1, 2012 + 2 weeks");
At noon on October 20, 2016, the full dates and times that would end up in the variables in Example 15-3 would be:
Thu, 20 Oct 2016 10:36:00 +0000 Wed, 11 May 2016 00:00:00 +0000 Sun, 05 Mar 2017 00:00:00 +0000 Sat, 10 Mar 2018 00:00:00 +0000 Tue, 10 Mar 2015 17:34:45 +0000 Tue, 10 Mar 2015 17:34:45 +0000 Fri, 05 Feb 1982 01:02:03 +0000 Tue, 03 Mar 2015 17:34:45 +0400 Tue, 25 Oct 2016 00:00:00 +0000 Thu, 30 Apr 2015 00:00:00 +0000 Thu, 15 Nov 2012 00:00:00 +0000
If you have discrete date-and-time parts, such as those submitted from form elements in which a user can specify month, day, and year or hour, minute, and second, you can also pass them to the the setTime() and setDate() methods to adjust the time and date stored inside the DateTime object.
Example 15-4 shows setTime() and setDate() at work.
// $_POST['mo'], $_POST['dy'], and $_POST['yr']// contain month number, day, and year submitted// from a form//// $_POST['hr'], $_POST['mn'] contain// hour and minute submitted from a form// $d contains the current time, but soon that will// be overridden$d=newDateTime();$d->setDate($_POST['yr'],$_POST['mo'],$_POST['dy']);$d->setTime($_POST['hr'],$_POST['mn']);$d->format('r');
If $_POST['yr'] is 2016, $_POST['mo'] is 5, $_POST['dy'] is 12, $_POST['hr'] is 4, and $_POST['mn'] is 15, then Example 15-4 prints:
Thu, 12 May 2016 04:15:00 +0000
Even though $d is initialized to the current date and time when Example 15-4 is run, the calls to setDate() and setTime() change what’s stored inside the object.
The DateTime object tries to be as accommodating as possible when parsing incoming data. Sometimes this is helpful, but sometimes it is not. For example, consider what you think should happen if, in Example 15-4, $_POST['mo'] is 3 and $_POST['dy'] is 35. It can never be the 35th of March. That doesn’t bother DateTime, though. It considers March 35 to be the same as April 4 (March 31 is the last day of March, so March 32 is the next day (April 1), March 33 is April 2, March 34 is April 3, and March 35 is April 4). Calling $d->setDate(2016, 3, 35) gives you a DateTime object set to April 4, 2016.
For stricter validation of days and months, use checkdate() on the month, day, and year first. It tells you whether the provided month and day are valid for the provided year, as shown in Example 15-5.
if(checkdate(3,35,2016)){"March 35, 2016 is OK";}if(checkdate(2,29,2016)){"February 29, 2016 is OK";}if(checkdate(2,29,2017)){"February 29, 2017 is OK";}
In Example 15-5, only the second call to checkdate() returns true. The first fails because March always has fewer than 35 days, and the third fails because 2017 is not a leap year.
Once you’ve got a DateTime object that represents a particular point in time, it’s straightforward to do date or time calculations. You might want to give a user a set of dates or times to choose from in a menu. Example 15-6 displays an HTML <select> menu where each choice is a day. The first choice is the date corresponding to the first Tuesday after the program is run. The subsequent choices are every other day after that.
In Example 15-6, the modify() method changes the date inside the DateTime object at each pass through the loop. The modify() method accepts a string holding one of the relative date/time formats described at http://www.php.net/datetime.formats.relative and adjusts the object accordingly. In this case, +2 day bumps it forward two days each time.
On October 20, 2016, Example 15-6 would print:
<select name='day'> <option>Tuesday October 25th</option> <option>Thursday October 27th</option> <option>Saturday October 29th</option> <option>Monday October 31st</option> </select>
The DateTime object’s diff() method tells you the difference between two dates. It returns a DateInterval object, which encapsulates the interval between the dates. Example 15-7 checks whether a given birthdate means someone is over 13 years old.
$now=newDateTime();$birthdate=newDateTime('1990-05-12');$diff=$birthdate->diff($now);if(($diff->y>13)&&($diff->invert==0)){"You are more than 13 years old.";}else{"Sorry, too young.";}
In Example 15-7, the call to $birthdate->diff($now) returns a new DateInterval object. This object’s properties describe the interval between $birthdate and $now. The y property is the number of years and the invert property is 0 when the difference is a positive amount (the invert property would be 1 if $birthdate were after $now). The other properties are m (months), d (days in the month), h (hours), i (minutes), s (seconds), and days (total number of days between the two dates).
Dates and times are, unfortunately, not just collections of hours, minutes, seconds, months, days, and years. To be complete, they must also include a timezone. “Noon on October 20, 2016” is not the same instant in time in New York City as it is in London.
The PHP engine must be configured with a default timezone to use. The easiest way to do this is to set the date.timezone configuration parameter in your PHP configuration file.1 If you can’t adjust the file, call the date_default_timezone_set() function in your program before you do any date or time manipulation. In PHP 7, the engine defaults to the UTC timezone if you don’t specify your own default value.
There is a big list of possible timezone values that the PHP engine understands. Instead of using your local timezone, however, a convention that often makes software development easier is to set the timezone to UTC, the code for Coordinated Universal Time. This is the time at zero degrees longitude and doesn’t adjust in the summer for a Daylight Saving Time setting. Although you have to do a little mental math to convert a UTC timestamp that appears, say, in a log file to your local time, using UTC makes it easier to work with time data that could be coming from multiple servers located in different timezones. It also avoids confusion during the switch to and from Daylight Saving Time because the apparent “clock time” doesn’t change.
This chapter covered:
DateTime object’s format() methodformat() understandsUTC is a convenient default timezone1 “Modifying PHP Configuration Directives” explains how to adjust configuration parameters.