3. Dates and Times

Introduction

// PHP's date / time suport is quite extensive, and appears grouped into three areas of
// functionality:
//
// * UNIX / C Library [libc]-based routines, which include [among others]:
//   - localtime, gmtime
//   - strftime, strptime, mktime
//   - time, getdate, gettimeofday, 
//
// * PHP 'native' functions, those date / time routines released in earlier versions,
//   and which otherwise provide 'convenience' functionality; these include:
//   - date
//   - strtotime
//
// * 'DateTime' class-based. This facility appears [according to the PHP documentation]
//   to be extremely new / experimental, so whilst usage examples will be provided, they
//   should not be taken to be 'official' examples, and obviously, subject to change.
//   My own impression is that this facility is currently only partially implemented,
//   so there is limited use for these functions. The functions included in this group
//   are some of the 'date_'-prefixed functions; they are, however, not used standalone,
//   but as methods in conjunction with an object. Typical usage:
//
//     $today = new DateTime();             // actually calls: date_create($today, ...);
//     echo $today->format('U') . "\n";     // actually calls: date_format($today, ...);
//
// Also worth mentioning is the PEAR [PHP Extension and Repository] package, 'Calendar',
// which offers a rich set of date / time manipulation facilities. However, since it is
// not currently shipped with PHP, no examples appear

// Helper functions for performing date arithmetic 

function dateOffset()
{
  static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800);
  $delta = 0;

  foreach (func_get_args() as $arg)
  {
    $kv = explode('=', $arg);
    $delta += $kv[1] * $tbl[strtolower(substr($kv[0], 0, 3))];
  }

  return $delta;
}

function dateInterval($intvltype, $timevalue)
{
  static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800);
  return (int) round($timevalue / $tbl[strtolower(substr($intvltype, 0, 3))]);
}

// ----------------------------

// Extract indexed array from 'getdate'
$today = getdate();
printf("Today is day %d of the current year\n", $today['yday']);

// Extract indexed, and associative arrays, respectively, from 'localtime'
$today = localtime();
printf("Today is day %d of the current year\n", $today[7]);

$today = localtime(time(), TRUE);
printf("Today is day %d of the current year\n", $today['tm_yday']);

Finding Today's Date

define(SEP, '-');

// ------------

$today = getdate();

$day = $today['mday'];
$month = $today['mon'];
$year = $today['year'];

// Either do this to use interpolation:
$sep = SEP;
echo "Current date is: {$year}{$sep}{$month}{$sep}{$day}\n";

// or simply concatenate:
echo 'Current date is: ' . $year . SEP . $month . SEP . $day . "\n";

// ------------

$today = localtime(time(), TRUE);

$day = $today['tm_mday'];
$month = $today['tm_mon'] + 1;
$year = $today['tm_year'] + 1900;

printf("Current date is: %4d%s%2d%s%2d\n", $year, SEP, $month, SEP, $day);

// ------------

$format = 'Y' . SEP . 'n' . SEP . 'd';

$today = date($format);

echo "Current date is: {$today}\n";

// ------------

$sep = SEP;

$today = strftime("%Y$sep%m$sep%d");

echo "Current date is: {$today}\n";

Converting DMYHMS to Epoch Seconds

$timestamp = mktime($hour, $min, $sec, $month, $day, $year);

$timestamp = gmmktime($hour, $min, $sec, $month, $day, $year);

Converting Epoch Seconds to DMYHMS

$dmyhms = getdate();            // timestamp: current date / time

$dmyhms = getdate($timestamp);  // timestamp: arbitrary

$day = $dmyhms['mday'];
$month = $dmyhms['mon'];
$year = $dmyhms['year'];

$hours = $dmyhms['hours'];
$minutes = $dmyhms['minutes'];
$seconds = $dmyhms['seconds'];

Adding to or Subtracting from a Date

// Date arithmetic is probably most easily performed using timestamps [i.e. *NIX Epoch
// Seconds]. Dates - in whatever form - are converted to timestamps, these are
// arithmetically manipulated, and the result converted to whatever form required.
// Note: use 'mktime' to create timestamps properly adjusted for daylight saving; whilst
// 'strtotime' is more convenient to use, it does not, AFAIK, include this adjustment

$when = $now + $difference;
$then = $now - $difference;

// ------------

$now = mktime(0, 0, 0, 8, 6, 2003);

$diff1 = dateOffset('day=1'); $diff2 = dateOffset('weeks=2');

echo 'Today is:                 ' . date('Y-m-d', $now) . "\n";
echo 'One day in the future is: ' . date('Y-m-d', $now + $diff1) . "\n";
echo 'Two weeks in the past is: ' . date('Y-m-d', $now - $diff2) . "\n";

// ----------------------------

// Date arithmetic performed using a custom function, 'dateOffset'. Internally, offset may
// be computed in one of several ways:
// * Direct timestamp manipulation - fastest, but no daylight saving adjustment 
// * Via 'date' built-in function - slower [?], needs a base time from which to
//   compute values, but has daylight saving adjustment 
// * Via 'strtotime' built-in function - as for 'date'
// * Via 'DateTime' class
//
// Approach used here is to utilise direct timestamp manipulation in 'dateOffset' [it's
// performance can also be improved by replacing $tbl with a global definition etc],
// and to illustrate how the other approaches might be used 

// 1. 'dateOffset'

$birthtime = mktime(3, 45, 50, 1, 18, 1973);

$interval = dateOffset('day=55', 'hours=2', 'min=17', 'sec=5');

$then = $birthtime + $interval;

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 2. 'date'

// Base values, and offsets, respectively
$hr = 3; $min = 45; $sec = 50; $mon = 1; $day = 18; $year = 1973;

$yroff = 0; $monoff = 0; $dayoff = 55; $hroff = 2; $minoff = 17; $secoff = 5;

// Base date
$birthtime = mktime($hr, $min, $sec, $mon, $day, $year, TRUE);

$year = date('Y', $birthtime) + $yroff;
$mon = date('m', $birthtime) + $monoff;
$day = date('d', $birthtime) + $dayoff;

$hr = date('H', $birthtime) + $hroff;
$min = date('i', $birthtime) + $minoff;
$sec = date('s', $birthtime) + $secoff;

// Offset date
$then = mktime($hr, $min, $sec, $mon, $day, $year, TRUE);

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 3. 'strtotime'

// Generate timestamp whatever way is preferable
$birthtime = mktime(3, 45, 50, 1, 18, 1973);
$birthtime = strtotime('1/18/1973 03:45:50');

$then = strtotime('+55 days 2 hours 17 minutes 2 seconds', $birthtime);

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 4. 'DateTime' class

$birthtime = new DateTime('1/18/1973 03:45:50');
$then = new DateTime('1/18/1973 03:45:50');
$then->modify('+55 days 2 hours 17 minutes 2 seconds');

printf("Birthtime is: %s\nthen is:      %s\n", $birthtime->format(DATE_RFC1123), $then->format(DATE_RFC1123));

Difference of Two Dates

// Date intervals are most easily computed using timestamps [i.e. *NIX Epoch
// Seconds] which, of course, gives the interval result is seconds from which
// all other interval measures [days, weeks, months, years] may be derived.
// Refer to previous section for discussion of daylight saving and other related
// problems

$interval_seconds = $recent - $earlier;

// ----------------------------

// Conventional approach ...
$bree = strtotime('16 Jun 1981, 4:35:25');
$nat = strtotime('18 Jan 1973, 3:45:50');

// ... or, with daylight saving adjustment
$bree = mktime(4, 35, 25, 6, 16, 1981, TRUE);
$nat = mktime(3, 45, 50, 1, 18, 1973, TRUE);

$difference = $bree - $nat;

// 'dateInterval' custom function computes intervals in several measures given an
// interval in seconds. Note, 'month' and 'year' measures not provided
printf("There were %d seconds between Nat and Bree\n", $difference);
printf("There were %d weeks between Nat and Bree\n", dateInterval('weeks', $difference));
printf("There were %d days between Nat and Bree\n", dateInterval('days', $difference));
printf("There were %d hours between Nat and Bree\n", dateInterval('hours', $difference));
printf("There were %d minutes between Nat and Bree\n", dateInterval('mins', $difference));

Day in a Week/Month/Year or Week Number

// 'getdate' accepts a timestamp [or implicitly calls 'time'] and returns an array of
// date components. It returns much the same information as 'strptime' except that
// the component names are different

$today = getdate();

$weekday = $today['wday'];
$monthday = $today['mday'];
$yearday = $today['yday'];

$weeknumber = (int) round($yearday / 7.0);

// Safter method of obtaining week number
$weeknumber = strftime('%U') + 1;

// ----------------------------

define(SEP, '/');

$day = 16;
$month = 6;
$year = 1981;

$timestamp = mktime(0, 0, 0, $month, $day, $year);

$date = getdate($timestamp);

$weekday = $date['wday'];
$monthday = $date['mday'];
$yearday = $date['yday'];

$weeknumber = (int) round($yearday / 7.0);

$weeknumber = strftime('%U', $timestamp) + 1;

// Interpolate ...
$sep = SEP;
echo "{$month}{$sep}{$day}{$sep}{$year} was a {$date['weekday']} in week {$weeknumber}\n";

// ... or, concatenate
echo $month . SEP . $day . SEP . $year . ' was a ' . $date['weekday']
     . ' in week ' . $weeknumber . "\n";

Parsing Dates and Times from Strings

// 'strtotime' parses a textual date expression by attempting a 'best guess' at
// the format, and either fails, or generates a timestamp. Timestamp could be fed
// into any one of the various functions; example:
$timestamp = strtotime('1998-06-03'); echo strftime('%Y-%m-%d', $timestamp) . "\n";

// 'strptime' parses a textual date expression according to a specified format,
// and returns an array of date components; components can be easily dumped
print_r(strptime('1998-06-03', '%Y-%m-%d'));

// ----------------------------

// Parse date string according to format
$darr = strptime('1998-06-03', '%Y-%m-%d');

if (!empty($darr))
{
  // Show date components in 'debug' form
  print_r($darr);

  // Check whether there was a parse error i.e. one or more components could not
  // be extracted from the string
  if (empty($darr['unparsed']))
  {
    // Properly parsed date, so validate required components using, 'checkdate'
    if (checkdate($darr['tm_mon'] + 1, $darr['tm_mday'], $darr['tm_year'] + 1900))
      echo "Parsed date verified as correct\n";
    else
      echo "Parsed date failed verification\n";
  }
  else
  {
    echo "Date string parse not complete; failed components: {$darr['unparsed']}\n";
  }
}
else
{
  echo "Date string could not be parsed\n";
}

Printing a Date

// 'date' and 'strftime' both print a date string based on:
// * Format String, describing layout of date components
// * Timestamp [*NIX Epoch Seconds], either given explicitly, or implictly
//   via a call to 'time' which retrieves current time value

$ts = 1234567890;

date('Y/m/d', $ts); 
date('Y/m/d', mktime($h, $m, $s, $mth, $d, $y, $is_dst)); 

date('Y/m/d');         // same as: date('Y/m/d', time());

// ------------

$ts = 1234567890;

strftime('%Y/%m/%d', $ts);
strftime('%Y/%m/%d', mktime($h, $m, $s, $mth, $d, $y, $is_dst));

strftime('%Y/%m/%d');  // same as: strftime('%Y/%m/%d', time());

// ----------------------------

// 'mktime' creates a local time timestamp
$t = strftime('%a %b %e %H:%M:%S %z %Y', mktime(3, 45, 50, 1, 18, 73, TRUE));
echo "{$t}\n";

// 'gmmktime' creates a GMT time timestamp
$t = strftime('%a %b %e %H:%M:%S %z %Y', gmmktime(3, 45, 50, 1, 18, 73));
echo "{$t}\n";

// ----------------------------

// 'strtotime' parses a textual date expression, and generates a timestamp 
$t = strftime('%A %D', strtotime('18 Jan 1973, 3:45:50'));
echo "{$t}\n";

// This should generate output identical to previous example
$t = strftime('%A %D', mktime(3, 45, 50, 1, 18, 73, TRUE));
echo "{$t}\n";

High-Resolution Timers

// PHP 5 and above can use the built-in, 'microtime'. Crude implementation for ealier versions:
// function microtime() { $t = gettimeofday(); return (float) ($t['sec'] + $t['usec'] / 1000000.0); } 

// ------------

$before = microtime();

$line = fgets(STDIN);

$elapsed = microtime() - $before;

printf("You took %.3f seconds\n", $elapsed);

// ------------

define(NUMBER_OF_TIMES, 100);
define(SIZE, 500);

for($i = 0; $i < NUMBER_OF_TIMES; $i++)
{
  $arr = array();
  for($j = 0; $j < SIZE; $j++) $arr[] = rand();

  $begin = microtime();
  sort($arr);
  $elapsed = microtime() - $begin;

  $total_time += $elapsed;
}

printf("On average, sorting %d random numbers takes %.5f seconds\n", SIZE, $total_time / (float) NUMBER_OF_TIMES);

Short Sleeps

// Low-resolution: sleep time specified in seconds
sleep(1);

// High-resolution: sleep time specified in microseconds [not reliable under Windows]
usleep(250000);

Program: hopdelta

// @@INCOMPLETE@@
// @@INCOMPLETE@@