Приглашаем посетить
Романтизм (19v-euro-lit.niv.ru)

Dates and Times in PHP

Previous
Table of Contents
Next

Dates and Times in PHP

It is important to understand the various facilities available in PHP to obtain, manipulate, and format date/time values. The following section examines how PHP typically stores time information.

Timestamps in PHP

The time function in PHP, in addition to the various file functions that provide time-based information about files (such as filemtime), returns a 32-bit integer value, usually called a timestamp. The value represents the number of seconds that have elapsed since midnight on January 1, 1970 (sometimes referred to as "the Epoch"). This starting time is represented by the value 0; the maximum positive 32-bit integer value of 2147483647 represents the evening of January 18, 2038.

Without some sort of processing of the integer value, the information is of limited use:

<?php

  $now = time();
  echo "The time is now: $now\n";

?>

The output would be something like this:

The time is now: 1108437029

We can use the date or strftime functions, discussed in the section "Outputting Formatted Dates and Times," to make this value more usable.

Getting the Date and Time

PHP provides a number of functions to get timestamp values or otherwise learn about the current time or some other date/time value.

time

As mentioned previously, the time function is the most common way to obtain the current date and time timestamp in PHP5. You typically pass the returned 32-bit integer value to some other function to use it.

mktime and gmmktime

When you do not want the current time, however, you can use the mktime function, which takes all the values seen in a common timestamp and returns the equivalent 32-bit timestamp value representing that date.

To use this function, you specify the following components in this order: hours, minutes, seconds, month, day, and year:

$time = mktime(15,23,00,1,1,1990);

The resulting timestamp can then be given to formatting functions and others shown elsewhere in this section.

As a slight variation on this function, PHP provides the gmmktime function. This function assumes that the time you are giving it is in Greenwich mean time (GMT), and it then returns a timestamp representing the current time in the time zone in which PHP thinks it is currently operating. (It queries the operating system.) You can see the difference between these functions in the following code sample, assuming the server is operating in the Pacific standard time zone in North America (PST):

<?php

  $ltime = mktime(15,23,00,1,1,1990);
  $gtime = gmmkttime mktime(15,23,00,1,1,1990);

  echo date('r', $ltime); echo "<br/>\n";
  echo date('r', $gtime); echo "<br/>\n";

?>

The output of this is as follows:

Mon, 1 Jan 1990 15:23:00 -0800
Mon, 1 Jan 1990 07:23:00 -0800

The mktime function assumes that the date/time value given is that of the current time zone, whereas the gmmktime function assumes that the given time is GMT and converts into the local time zone (by subtracting 8 hours, as shown in the preceding output).

microtime

A variation on the time function is the microtime function, which returns a string containing two values:

'microseconds seconds'

The second part of the string is just the output of the time functionthe number of seconds since January 1, 1970. The first part provides some extra resolution on this time and shows the number of microseconds for the current time, usually represented as a floating-point number:

0.70690800 1108337906

This shows that 706908 microseconds have passed since the timestamp represented by the second value. You can easily get this integer value through the following code:

<?php

  $str = microtime();
  $vals = explode(' ', $str);  // no need to worry about mbcs
  $ival = (int)(($float)$vals[0] * 1000000);

  // $ival now contains number of usecs that have passed!

?>

There is an optional parameter to the microtime function that, when set to TRUE, tells it to return the time value as a floating-point number. We can use this to crudely measure the execution time of functions, such as queries and other operations about whose efficiency you might worry:

<?php

  $conn = @new mysqli(...);
  if (mysqli_connect_errno() !== 0)
    throw new DatabaseErrorException(mysqli_connect_error());

  $query = "SELECT * FROM Users";

  $start = microtime(TRUE);
  $results = $conn->query($query);
  $end = microtime(TRUE);
  if ($results === FALSE)
  {
    $msg = $conn->error;
    $conn->close();
    throw new DatabaseErrorException(mysqli_connect_error());
  }

  // show how long the query took to execute.
  $total = $end  start;
  echo "The query took a total of {$total}s to execute<br/>\n";

?>

The timing information this provides is extremely crude and should be considered with a grain of salt. It merely measures the clock time elapsed while executing a function; it does not consider such things as server load, network traffic, or disk activity. Given that web application end users do not typically care about such things, however, the timing information can provide a useful idea of how long they are waiting for pages to load.

strtotime

When someone gives you a date, time, or timestamp in some sort of string format, you can use the strtotime function, which goes to Herculean lengths to try to parse it and determine the correct value. For most common formats and standardized formats, the function performs exceedingly well:

<?php

  // yyyy-mm-dd
  $time = strtotime('2004-12-25');
  // mm/dd/yyyy
  $time = strtotime('12/25/2004');
  // RFC 2822 formatted date
  $time = strtotime('Mon, 13 Feb 1995 20:45:54 EST');
  // ISO 8601 formatted date
  $time = strtotime('2001-09-08T13:11:56-09:00');
  // it even understands some English words!
  $time = strtotime('yesterday');

?>

Many Internet technologies use the RFC 2822 date format. The ISO 8601 format was specified by the International Standards Organization in an attempt to merge and reduce the myriad possible date/time formats currently in use.

The strtotime function obviously cannot parse all possible values given to it, especially when some ambiguity exists:

<?php

  $time = strtotime('03/02/04');
  $time = strtotime('Thursday, June 22nd');

?>

If we were in Europe and intended the first of these two dates to be February 3, 2004, the strtotime function would yield the incorrect valueit would treat this as March 2, 2004. Likewise, for the second value, strtotime has no year information, so it simply does the best it canassuming the date is June 22 of the current year (even if it is not a Thursday).

One interesting side effect (which again might be a problem for European programmers) is what happens if you ask the strtotime function to parse the date '14/5/99', which is usually interpreted in mainland Europe as 14 May 1999.

The strtotime function actually generates 5 February 2000 as the result, because it treats the first value as the month, and because there are more than 12 months (14 was specified), it actually "rolls over" the date into the next year and then subtracts 12 from the month to get a remainder of 2February.

You can also provide the strtotime function an optional timestamp as the second parameter, in which case the string is interpreted to be relative to that timestamp, as in the following example:

<?php

  // returns now + 96 hours
  $time = strtotime('+4 days', time());

?>

In general, you should not use the strtotime function for mission-critical date processing code because it can be difficult to guarantee that the results will be what you expect. At the very least, you should validate in your code that the result seems reasonable or sane. Also, remember that this function can be quite inefficient (and should be avoided in performance-sensitive areas).

geTDate

The getdate function returns the current date and time in an array rather than as a timestamp. It returns an associative array with the following keys:

$time = strtotime('2005-09-12 21:11:15');
getdate($time) returns:

Array
(
  'seconds' => 15,
  'minutes' => 11,
  'hours' => 21,
  'mday' => 12,              // day of the month.
  'wday' => 1,               // day of the week (Sunday == 0)
  'mon' => 9,                // integer month value (Jan == 1)
  'year' => 2005,
  'yday' => 254,             // day of the year
  'weekday' => 'Monday',
  'month' => 'September',
  0 => 1126584675            // time() timestamp for this date
)

The weekday and month values are always returned in the internal language of PHP (that is, U.S. English) and will not be translated even after calls to the setlocale function. (See Chapter 21, "Advanced Output and Output Buffering.")

This function assumes that the given timestamp is in the current time zone.

Validating Date and Time

After you have been given a date or time value, you should verify its validity. Validating a time is usually not difficult because you can quickly break down the time string and check the individual values:

<?php
//
// returns TRUE if the time is valid, or false otherwise
// times can be HH:mm[:ss], where [:ss] is optional
//
function is_valid_time($in_time, $in_allow_24hr)

  $parts = explode(':', $time);   // no mbcs in times

  // hrs
  if ($in_allow_24hr)
  {
    if ($parts[0] < 0 || $parts[0] > 24)
      return FALSE;
  }
  else
  {
    if ($parts[0] < 0 || $parts[0] > 12)
      return FALSE;
  }

  // min -- must be specified!
  if (!isset($parts[1]) || ($parts[1] < 0 || $parts[1] > 59))
    return FALSE;

  // sec -- optional.
  if (isset($parts[2]) && ($parts[2] < 0 || $parts[2] > 59))
    return FALSE;
  return TRUE;
}

$time = '23:15:44';
echo is_valid_time($time);

?>

Validating a date can be a bit more difficult. Whereas some months have 31 days (in the modern Gregorian calendar, at least), others only have 30, and the month of February can have either 28 or 29, depending on whether it is a leap year.

Fortunately, PHP provides a function called checkdate that correctly handles all of these issues. It takes a month, day, and year and returns a Boolean indicating whether this represents a real day, such as January 3, 1930, or an invalid one, such as February 29, 1931:

<?php

  echo checkdate(1, 3, 1930);     // TRUE ('1')
  echo checkdate(2, 29, 1931);    // FALSE ('')

?>

Comparing Dates and Times

One of the best things about working with dates and times as timestamps (32-bit integers) is that they are relatively easy to compare. If an integer value is greater than another, it represents a relatively later point in time. With this in mind, the following subsections examine some of the more common operations you might want to perform on various dates and times, such as comparing them, adding time periods, or converting values.

Matching a Date to a Range

To determine whether a date falls within a given range, we can write a simple function to perform this operation. For this example, assume that the dates are in database formatnamely, yyyy-mm-dd; you can write your function as follows:

//
// assumes dates are yyyy-mm-dd
//
function date_within_range($in_date, $in_start, $in_end)
{
  if ($in_date == '' or $in_start == '' or $in_end == '')
    throw new InvalidArgumentException();

  //
  // first step: convert all of them to timestamps.
  //
  $ts_test = yyyymmdd_to_timestamp($in_date);
  $ts_start = yyyymmdd_to_timestamp($in_start);
  $ts_end = yyyymmdd_to_timestamp($in_end, TRUE);

  return (($ts_test >= $ts_start) and ($ts_test <= $ts_end));
}

To do its work, this function requires a function to convert the given date into a timestamp. For this, we have written the yyyymmdd_to_timestamp function. This function takes the given date and returns a timestamp representing 12:00:00AM on that date. If you give it an optional second parameter (set to trUE), however, it returns the timestamp representing 11:59:59PM on that same day:

//
// converts the input in yyyy-mm-dd format to a
// timestamp.
//
// why not just use strtotime? Because this will be much
// faster if we are guaranteed the yyyy-mm-dd format.
//
function yyyymmdd_to_timestamp($in_date, $in_eod = FALSE)
{
  $parts = explode('-', $in_date);

  if ($in_eod === FALSE)
    return mktime(0, 0, 0, $parts[1], $parts[2], $parts[0]);
  else
    return mktime(23, 59, 59, $parts[1], $parts[2], $parts[0]);

}

After you have converted the dates into timestamps, the operation reduces to simple number crunching.

Similar Values

If we are given two timestamp values and want to determine whether they are the same date, we can write a function to determine such, as follows:

//
// given two timestamps, asks whether they are the same date.
//
function same_date($in_ts1, $in_ts2)
{
  return date('Y-m-d', $in_ts1) == date('Y-m-d', $in_ts2);
}

This rather simplistic function uses the date function to generate the date for the two values and compares them. Unfortunately, we can imagine the string generation and comparisons being somewhat inefficient, so we might try to be clever and write an improved version of this function using arithmetic instead.

For this new version, notice that there are 86,400 seconds in a day; if we round your timestamps to the nearest multiple of 86,400, we should be able to do a simple integer comparison, as follows:

//
// instead of using more expensive date function to generate
// printable date and then doing a strcmp, just do some
// math on the values instead.
//
function same_date_faster($in_ts1, $in_ts2)
{
  $days1 = floor($in_ts1 / SECONDS_IN_A_DAY);
  $days2 = floor($in_ts2 / SECONDS_IN_A_DAY);

  return $days1 == $days2;
}

If you think that this function is likely to be faster than the previous one, you are correct. It is about 40 percent faster.

We can write similar functions to determine whether two values lie within the same week:

//
// given two timestamps, asks if they are in the same
// week.
//
function same_week($in_ts1, $in_ts2)
{
  return date('W', $in_ts1) == date('W', $in_ts2);
}

The arithmetic-based version has to round to multiples of 7 times 86,400 (the number of seconds in a week):

//
// instead of using date(), we just use some numeric
// arithmetic. This speeds up the operation by about 20% to 25%
// on average.
//
function same_week_faster($in_ts1, $in_ts2)
{
  $week1 = floor($in_ts1 / (7 * SECONDS_IN_A_DAY));
  $week2 = floor($in_ts2 / (7 * SECONDS_IN_A_DAY));

  return $week1 == $week2;
}

In this case, however, because the date function returns an integer when you give it the formatting flag 'W', the new version is only 20 percent to 25 percent faster than the original.

Adding Periods to a Date

Once again using your knowledge of timestamps and the number of seconds in a day, we can quickly write a function to add a specified number of days to a given timestamp, as follows:

//
// adds the given number of days to the given date/time. We
// will not let the value exceed the maximum timestamp.
//
function add_days_to_date($in_ts, $in_num_days)
{
  if ($in_ts + ($in_num_days * SECONDS_IN_A_DAY) > MAX_DATE)
    return MAX_DATE;
  else
    return $in_ts += $in_num_days * SECONDS_IN_A_DAY;
}

With this function, take care not to let the number of days added cause the timestamp to exceed its maximum value (of late January 18, 2038). Note that on 32-bit architectures, at least, the maximum positive integer value is represented by the hexadecimal value 0x7fffffff.

These are merely suggestions of some possible date/time comparison and manipulation functions, designed to give you an idea of how to approach the problem of writing your own.

Outputting Formatted Dates and Times

PHP has a number of functions for the format and output of dates as strings. The most commonly used of these is the date function.

The date and gmdate Functions

The date function (and its close cousin gmdate, which merely assumes that the specified timestamp is in GMT rather than the current time zone) allows for a high degree of flexibility in the output of date and time information. The function has the following parameter list:

string date($format[, $timestamp]);

The first parameter specifies the way in which we want the date information formatted; the second is optional and enables you to specify the timestamp to format for output. If omitted, the current time as returned by the time function is used.

The format string is basically a sequence of placeholder characters that represent various tidbits of information to be inserted, along with any extra whitespace or punctuation characters, which are all ignored. Table 26-1 lists the possible values of the placeholder characters.

Table 26-1. Format Placeholder Characters for the date() Function

Character

Output

Description

a

am or pm

Lowercase ante meridian (am) or post meridian (pm) value (12-hour time only).

A

AM or PM

Uppercase ante meridian (AM) or post meridian (PM) value (12-hour time only).

B

000 through 999

Swatch Internet Time value In this system, the day is divided into 1,000 equal parts (each of which is 1 minute, 26.4 seconds long).

c

Something like: 2001-09-08T13:11:56-09:00

The date printed in ISO 8601 format.

d

01 to 31

Day of the month, with leading zeros (if necessary).

D

Mon through Sun

Three-letter textual representation of the day of the week.

F

January through December

Full textual representation of the month name.

g

1 through 12

12-hour format of hour, without leading zeros.

G

0 through 23

24-hour format of hour, without leading zeros.

h

01 through 12

12-hour format of hour, with leading zeros.

H

00 through 23

24-hour format of hour, with leading zeros.

i

00 through 59

Minutes, with leading zeros.

I

1 if daylight savings time, else 0

Whether the date falls in daylight savings time.

j

1 to 31

Day of the month, without leading zeros.

l

Sunday through Saturday

Full textual representation of the day of the week.

L

1 if it is a leap year, else 0

Whether it is a leap year.

m

01 through 12

Numeric value of month, with leading zeros.

M

Jan through Dec

Three-letter textual representation of month.

n

1 through 12

Numeric value of month, without leading zeros.

O

Something similar to: +0200 or -0800

Difference from Greenwich mean time (GMT), in hours.

r

Something similar to: Mon, 13 Feb 1995 20:45:54 EST

The date output in RFC 2822 format.

s

00 through 59

Number of seconds, with leading zeros.

S

st, nd, rd, or th

The English ordinal suffix for the day of the month (commonly used with the j format character).

t

28 through 31

The number of days in the month.

T

Something such as: PST, EDT, CET

The abbreviated name for the time zone used on the server machine.

U

Output of time function

The number of seconds since January 1, 1970.

w

0 (Sunday) through 6 (Saturday)

Numeric value of the day of the week.

W

0 through 51 (30 is the thirtieth week in the year, and so on)

The ISO 8601 week number in the year, where weeks always start on a Monday.

y

Something like 75 or 04

A two-digit representation of the year.

Y

Something like 1950, 2049

Full numeric value of the year, four digits.

z

0 through 365

The day of the year (starting at zero).

Z

43200 through 43200

Time zone offset in seconds. West of GMT are negative values, east are positive.


These placeholder characters can be combined in any order in the format string to create the output string:

<?php

  $time = strtotime('2005-09-12 21:11:15');

  echo date('Y-m-d H:i:s');  // 2005-09-12 21:11:15
  echo date('l, F jS, Y');   // Monday, September 12th, 2005
  echo date('c');            // 2005-09-12T21:11:15-09:00
  echo date('r');            // Mon, 12 Sep 2005 21:11:15 -0700

?>

The date function only prints output in the locale of the PHP server (that is, U.S. English). To see formatted output that correctly honors the current locale, use the setlocale function and the strtotime function.

The strftime Function

The strftime function is similar in operation to the date function, except that it uses a slightly different implementation within PHP, and it honors locale settings correctly. Like the date function, it takes a format string and an optional timestamp as parameters, and prints the timestamp (or the current time) according to the format. Formats in the strftime function, however, are always prefixed with a % character so that they can be mixed into arbitrary output strings.

Table 26-2 lists possible conversion characters.

Table 26-2. Common Conversion Characters for the strftime() Function

Character

Description

%a

Abbreviated weekday name, according to the current locale.

%A

Full weekday name, as per the current locale.

%b

Abbreviated month name, as per the current locale.

%B

Full month name, as per the current locale.

%c

Preferred date and time representation, as per the current locale.

%C

Century number (year divided by 100, rounded down to an integer).

%d

The day of the month as a decimal number, with leading zero.

%D

Generates the same out put as %m/%d/%y

%e

The day of the month as a decimal numbersingle-digit month values are preceded by a space.

%H

The hour, specified as a decimal number using a 24-hour clock, with leading zero.

%I

The hour, specified as a decimal number using a 12-hour clock, with leading zero.

%j

The day of the year as a decimal number, with leading zeros (first day is 1).

%m

Month as a decimal number, with leading zero.

%M

The minute, specified as a decimal number, with leading zero.

%p

Either am or pm, depending on the given time, or the correct values for the current locale.

%r

The full time in AM or PM notation.

%R

The full time in 24-hour notation.

%S

The second, specified as a decimal number, with leading zero.

%T

The time output as %H:%M:%S.

%u

The weekday as a decimal number, with 1 being equal to Monday.

%x

The preferred date representation without time, for the current locale.

%X

The preferred time representation for the current locale, without the date.

%y

The year, specified as a two-digit number without century (00 to 99).

%Y

The year, specified as a full four-digit number, with century.

%z

The time zone.

%%

To print a '%' character in the output string.


The strftime function correctly honors the current locale as set by the setlocale function. Therefore, if you set the locale to French, the code

<?php

  $time = strtotime('2005-09-12 21:11:15');
  echo strftime('%c', $time);
  echo strftime('%d %B, %Y (%A)', $time);
  setlocale(LC_ALL, 'french');  // Windows
  setlocale(LC_ALL, 'fr_FR');   // Linux

  echo strftime('%c', $time);
  echo strftime('%d %B, %Y (%A)', $time);

?>

would output the following on some servers:

9/12/2005 9:11:15 PM
12 September, 2005 (Monday)
12/09/2005 21:11:15
12 septembre, 2005 (lundi)

The exact content of %c, %x, and %X formatted output depends not only on the current locale, but on how the current server operating system decides dates and times should be formatted.

A Problem with Timestamps

Recall from a previous discussion what the time function returns. The 32-bit integer timestamp values that it returns express dates between 1970 and 2037 (with a few extra days in 2038 for good measure). The implication that nothing happened before 1970 or will happen after 2037 might come as a shock to many people, especially those born before the Epoch and for those planning on living for a few more decades.

The problem is not as bad as it looks. Some operating systems support negative values for the timestamp, which means that dates as early as 1901 can be represented using the 32-bit timestamp, which helps significantly with the birth date problem. However, we still cannot represent dates after January 2038, and negative timestamp values are only permitted on some Unix systems. Servers running on Microsoft Windows cannot take advantage of this and are thus limited to the 68-year existence.

Unfortunately, no solution to this problem exists, so programmers are advised to use timestamps and calculations on them with caution. Fortunately, for databases, a much more usable range of dates is supported, as discussed in the following section. When you are in PHP scripts and need to work with dates and times, you can balance between being careful (most of the dates and times used in this book fit well within the range of 32-bit timestamps) and looking for new implementations of date and time functionality.

PEAR, the PHP Extension and Application Repository, has a Date class that supports dates and times over a significantly more useful range. Chapter 28, "Using PEAR," covers working with PEAR (including the Date class) in detail.


Previous
Table of Contents
Next