I'm trying to create a function that I can use to find the difference between 2 DateTimes, but I want to be able to return the absolute value of each one.
Say the difference is 15 minutes 20 seconds, I want to be able to output 15 minutes, 20 seconds, or 920 seconds. And this will also expand into days, months, years. So 2 days 12 minutes could be 2 days, 48 hours, 2892 minutes, or 173520 seconds.
I originally started with my own code but wanted to account for proper differences when bringing days, months, and years into account, so I changed to using DateDiff which I've now found doesn't work how I want.
This is my code so far
define( 'MINUTE_IN_SECONDS', 60 );
define( 'HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS );
define( 'DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS );
define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS );
define( 'MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS );
define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS );
/**
* Output a length between times in friendly format
*
* @param string $date1 Date/Time from in Y-m-d H:i:s format.
* @param string $date2 Date/Time to in Y-m-d H:i:s format.
* @param string $length Length of output. Set to "l" for detailed down to the second. Defaults to short if nothing entered.
* @param string $format Format to return the output in. Based on {@link https://www.php.net/manual/en/dateinterval.format.php PHP Date Interval Format}.
* If no format is set, the function will try assume the best output
*
* @return string Will return the requested output.
*/
function friendlyDtmDiff($date1, $date2, $length = '', $format = '') {
// Create DateTime for diff()
$dt1 = new \DateTime($date1);
$dt2 = new \DateTime($date2);
// Check if $dt2 ($date2) is before $dt1 ($date1) and
// swap the values if they are, also outputing a negative sign
if($dt2 < $dt1) {
$dtHolder = $dt1->format('Y-m-d H:i:s');
$dt1 = new \DateTime($dt2->format('Y-m-d H:i:s'));
$dt2 = new \DateTime($dtHolder);
$r = '-';
} else {
$r = '';
}
// Set the interval
$interval = $dt1->diff($dt2);
// Assume best output options
if(empty($format) || $format == '') {
// Difference in seconds
$diffSecs = $dt2->getTimestamp() - $dt1->getTimestamp();
if($diffSecs > YEAR_IN_SECONDS) { // Assume Years
$format = 'y';
} else if($diffSecs > MONTH_IN_SECONDS) { // Assume Months
$format = 'm';
} else if($diffSecs > DAY_IN_SECONDS) { // Assume Days
$format = 'd';
} else if($diffSecs > HOUR_IN_SECONDS) { // Assume Hours
$format = 'h';
} else if($diffSecs > MINUTE_IN_SECONDS) { // Assume Minutes
$format = 'i';
} else {// Assume seconds
$format = 's';
}
}
switch ($format) {
// Return seconds
case 's':
return $interval->format('%s seconds');
break;
// Return seconds padded
case 'S':
return $interval->format('%S seconds');
break;
// Return minutes
case 'i':
return ($length == 'l') ? $r . $interval->format('%i minutes and %s seconds') : $r . $interval->format('%i minutes') ;
break;
// Return minutes padded
case 'I':
return ($length == 'l') ? $r . $interval->format('%I minutes and %S seconds') : $r . $interval->format('%I minutes') ;
break;
// Return hours
case 'h':
return ($length == 'l') ? $r . $interval->format('%h hours, %i minutes, and %s seconds') : $r . $interval->format('%h hours') ;
break;
// Return hours padded
case 'H':
return ($length == 'l') ? $r . $interval->format('%H hours, %I minutes, and %S seconds') : $r . $interval->format('%H hours') ;
break;
// Return days
case 'a':
case 'd':
return ($length == 'l') ? $r . $interval->format('%d days, %h hours, %i minutes, and %s seconds') : $r . $interval->format('%d days') ;
break;
// Return days padded
case 'D':
return ($length == 'l') ? $r . $interval->format('%D days, %H hours, %I minutes, and %S seconds') : $r . $interval->format('%D days') ;
break;
// Return months
case 'm':
return ($length == 'l') ? $r . $interval->format('%m months, %d days, %h hours, %i minutes, and %s seconds') : $r . $interval->format('%m months') ;
break;
// Return months padded
case 'M':
return ($length == 'l') ? $r . $interval->format('%M months, %D days, %H hours, %I minutes, and %S seconds') : $r . $interval->format('%M months') ;
break;
default:
return 'not available';
break;
}
}
This is an example of code
$dtmNow = new \DateTime();
$authExpireDtm = new \DateTime();
$authExpireDtm->modify('+15 minutes');
echo friendlyDtmDiff($dtmNow->format('Y-m-d H:i:s'), $authExpireDtm->format('Y-m-d H:i:s'), 'l', 's');
Which I want to output 900 seconds
, but it outputs 0 seconds
and this is the object that the $interval
has
DateInterval Object ( [y] => 0 [m] => 0 [d] => 0 [h] => 0 [i] => 15 [s] => 0 [f] => 0 [weekday] => 0 [weekday_behavior] => 0 [first_last_day_of] => 0 [invert] => 0 [days] => 0 [special_type] => 0 [special_amount] => 0 [have_weekday_relative] => 0 [have_special_relative] => 0 )
Which as you can see has nothing (0
) under the s
item, so of course my function is returning 0 seconds
. I'm wanting to know how to get it to return the 900
seconds. I know it's easy enough when comparing time (hours, minutes seconds), but I want to get it accurate when converting days, months, years.
So if I put in 2020-02-28 00:00:00
and 2020-03-01 00:00:00
it will return 172800 seconds
or 2880 minutes
, but if I put in 2019-02-28 00:00:00
and 2019-03-01 00:00:00
it will return 86400 seconds
or 1440 minutes
Is anyone able to point me in the right direction to achieve the result I'm after
Here's a function that does 99% of what you want (it doesn't implement rounding when length != 'l'
, and it also doesn't remove the s
from e.g. 1 years
). 0 values are not output, although you could add a parameter to control that.
function friendlyDtmDiff($date1, $date2, $length = '', $format = '') {
// Create DateTime for diff()
$dt1 = new \DateTime($date1);
$dt2 = new \DateTime($date2);
// Create intervals
if ($dt1 < $dt2) {
$sign = '';
$interval = $dt1->diff($dt2);
}
else {
$sign = '-';
$interval = $dt2->diff($dt1);
}
// Output format (minimum 2 digits for upper case formats)
$of = ($format < 'a') ? '%02d' : '%d';
// generate output using an array of terms to be imploded
$output = array();
// create time components
switch ($format) {
case 'Y':
case 'y':
$years = $interval->y;
if ($years) $output[] = sprintf("$of years", $years);
if ($length != 'l') break;
$interval->y = 0;
case 'M':
case 'm':
$months = $interval->y * 12 + $interval->m;
if ($months) $output[] = sprintf("$of months", $months);
if ($length != 'l') break;
$interval->m = $interval->y = 0;
case 'D':
case 'd':
$days = ($interval->y * 12 + $interval->m) * 30 + $interval->d;
if ($days) $output[] = sprintf("$of days", $days);
if ($length != 'l') break;
$interval->d = $interval->m = $interval->y = 0;
case 'H':
case 'h':
$hours = (($interval->y * 12 + $interval->m) * 30 + $interval->d) * 24 + $interval->h;
if ($hours) $output[] = sprintf("$of hours", $hours);
if ($length != 'l') break;
$interval->h = $interval->d = $interval->m = $interval->y = 0;
case 'I':
case 'i':
$minutes = ((($interval->y * 12 + $interval->m) * 30 + $interval->d) * 24 + $interval->h) * 60 + $interval->i;
if ($minutes) $output[] = sprintf("$of minutes", $minutes);
if ($length != 'l') break;
$interval->i = $interval->h = $interval->d = $interval->m = $interval->y = 0;
case 'S':
case 's':
$seconds = (((($interval->y * 12 + $interval->m) * 30 + $interval->d) * 24 + $interval->h) * 60 + $interval->i) * 60 + $interval->s;
if ($seconds) $output[] = sprintf("$of seconds", $seconds);
break;
default:
return 'Invalid format';
break;
}
// put the output string together
$last = array_pop($output);
return $sign . (count($output) ? implode(', ', $output) . ' and ' : '') . $last;
}
Example usage:
echo friendlyDtmDiff('2020-02-28 00:00:00', '2020-03-01 12:00:56', '', 'h') . PHP_EOL;
echo friendlyDtmDiff('2020-02-28 00:00:00', '2020-03-01 12:00:56', 'l', 'h') . PHP_EOL;
echo friendlyDtmDiff('2020-02-28 00:00:00', '2020-03-01 12:08:56', 'l', 'h') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 'y') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 'm') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 'd') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 'h') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 'i') . PHP_EOL;
echo friendlyDtmDiff('2018-12-28 00:00:00', '2020-04-11 04:08:56', 'l', 's') . PHP_EOL;
Output:
60 hours
60 hours and 56 seconds
60 hours, 8 minutes and 56 seconds
1 years, 3 months, 14 days, 4 hours, 8 minutes and 56 seconds
15 months, 14 days, 4 hours, 8 minutes and 56 seconds
464 days, 4 hours, 8 minutes and 56 seconds
11140 hours, 8 minutes and 56 seconds
668408 minutes and 56 seconds
40104536 seconds