Be warned, the majority of the mathematically calculated examples have a hard limit of 2038-01-18
dates and will not work with fictional dates.
As there was a lack of DateTime
and DateInterval
based examples, I wanted to provide a multi-purpose function that satisfies the OP's need and others wanting compound elapsed periods, such as 1 month 2 days ago
. Along with a bunch of other use cases, such as a limit to display the date instead of the elapsed time, or to filter out portions of the elapsed time result.
Additionally the majority of the examples assume elapsed is from the current time, where the below function allows for it to be overridden with the desired end date.
/**
* multi-purpose function to calculate the time elapsed between $start and optional $end
* @param string|null $start the date string to start calculation
* @param string|null $end the date string to end calculation
* @param string $suffix the suffix string to include in the calculated string
* @param string $format the format of the resulting date if limit is reached or no periods were found
* @param string $separator the separator between periods to use when filter is not true
* @param null|string $limit date string to stop calculations on and display the date if reached - ex: 1 month
* @param bool|array $filter false to display all periods, true to display first period matching the minimum, or array of periods to display ['year', 'month']
* @param int $minimum the minimum value needed to include a period
* @return string
*/
function elapsedTimeString($start, $end = null, $limit = null, $filter = true, $suffix = 'ago', $format = 'Y-m-d', $separator = ' ', $minimum = 1)
{
$dates = (object) array(
'start' => new DateTime($start ? : 'now'),
'end' => new DateTime($end ? : 'now'),
'intervals' => array('y' => 'year', 'm' => 'month', 'd' => 'day', 'h' => 'hour', 'i' => 'minute', 's' => 'second'),
'periods' => array()
);
$elapsed = (object) array(
'interval' => $dates->start->diff($dates->end),
'unknown' => 'unknown'
);
if ($elapsed->interval->invert === 1) {
return trim('0 seconds ' . $suffix);
}
if (false === empty($limit)) {
$dates->limit = new DateTime($limit);
if (date_create()->add($elapsed->interval) > $dates->limit) {
return $dates->start->format($format) ? : $elapsed->unknown;
}
}
if (true === is_array($filter)) {
$dates->intervals = array_intersect($dates->intervals, $filter);
$filter = false;
}
foreach ($dates->intervals as $period => $name) {
$value = $elapsed->interval->$period;
if ($value >= $minimum) {
$dates->periods[] = vsprintf('%1$s %2$s%3$s', array($value, $name, ($value !== 1 ? 's' : '')));
if (true === $filter) {
break;
}
}
}
if (false === empty($dates->periods)) {
return trim(vsprintf('%1$s %2$s', array(implode($separator, $dates->periods), $suffix)));
}
return $dates->start->format($format) ? : $elapsed->unknown;
}
One thing to note - the retrieved intervals for the supplied filter values do not carry over to the next period. The filter merely displays the resulting value of the supplied periods and does not recalculate the periods to display only the desired filter total.
Usage
For the OP's need of displaying the highest period (as of 2015-02-24).
echo elapsedTimeString('2010-04-26');
/** 4 years ago */
To display compound periods and supply a custom end date (note the lack of time supplied and fictional dates).
echo elapsedTimeString('1920-01-01', '2500-02-24', null, false);
/** 580 years 1 month 23 days ago */
To display the result of filtered periods (ordering of array doesn't matter).
echo elapsedTimeString('2010-05-26', '2012-02-24', null, ['month', 'year']);
/** 1 year 8 months ago */
To display the start date in the supplied format (default Y-m-d) if the limit is reached.
echo elapsedTimeString('2010-05-26', '2012-02-24', '1 year');
/** 2010-05-26 */
There are bunch of other use cases. It can also easily be adapted to accept unix timestamps and/or DateInterval objects for the start, end, or limit arguments.