0

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

dpDesignz
  • 1,909
  • 10
  • 34
  • 70
  • Instead of this much code use `datetime()`:- https://3v4l.org/7fOM9 – Alive to die - Anant Aug 19 '19 at 04:29
  • whats an "absolute" minute vs a "standard" minute? –  Aug 19 '19 at 04:30
  • @AnantSingh---AlivetoDie I'm not sure I understand. That's what I am using? – dpDesignz Aug 19 '19 at 04:32
  • @tim I wasn't sure how to explain it. 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 could be 2 days, 48 hours, 2880 minutes, or 172800 seconds. – dpDesignz Aug 19 '19 at 04:35
  • So what exactly doesn't work? – Nick Aug 19 '19 at 05:21
  • @Nick DateDiff will only return the value for the absolute time. If the difference is 15 minutes and I want to return that in seconds, it can't because there's no value in the seconds (`->format(%s)` or `->s`). This is easily enough fixed for seconds, minutes, and hours, but not so much for days, months, and years from what I can work out. – dpDesignz Aug 19 '19 at 05:24
  • Yup, I understand that, my question is, what in your code doesn't work and you need help with? Can you provide some sample input/output data and explain where the results are different from what you expect? – Nick Aug 19 '19 at 05:26
  • @Nick thanks, I've added some more information in my OP – dpDesignz Aug 19 '19 at 05:35
  • Please refer this answer. it will answer your question. https://stackoverflow.com/questions/365191/how-to-get-time-difference-in-minutes-in-php#answer-12382882 – Sangita Kendre Aug 19 '19 at 06:06
  • What result do you expect for `friendlyDtmDiff('2020-02-28 00:00:00', '2020-03-01 12:34:56', 'l', 'H');`? – Nick Aug 19 '19 at 06:09
  • @Nick 61 hours (I'm wanting to round if possible) – dpDesignz Aug 19 '19 at 06:16
  • @SangitaKendre thank you, that solves my problem up to days, but sadly if I'm also wanting to take months and years into account it doesn't look like it will work? I may have to rethink how my function will work. I may have to end up putting minimum limits depending on the size of the difference. – dpDesignz Aug 19 '19 at 06:27
  • @dpDesignz with `length= 'l'` wouldn't you want 60 hours, 34 minutes and 56 seconds? – Nick Aug 19 '19 at 06:33
  • @Nick sorry, I missed the `l`. Yes that's correct. – dpDesignz Aug 19 '19 at 06:34
  • it will work with moths and years difference also as minutes will calculated on total number of days difference – Sangita Kendre Aug 19 '19 at 06:43
  • @SangitaKendre oh of course! Silly me. Thank you. That solves my minutes issue. Just trying to wrap my head around the rest now. :) – dpDesignz Aug 19 '19 at 06:45
  • Possible duplicate of [Difference between 2 dates in seconds](https://stackoverflow.com/questions/5988450/difference-between-2-dates-in-seconds) – Will Aug 19 '19 at 08:10
  • @Will thanks, I've worked out the seconds and minutes part. Just working on the rest of them. Will post my finished code once finished in case someone else is looking for the same thing. :-) – dpDesignz Aug 19 '19 at 08:11

1 Answers1

1

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

Demo on 3v4l.org

Nick
  • 138,499
  • 22
  • 57
  • 95
  • you're a legend! Thank you so much! Sucks how it looks so simple when you see it done right. I was halfway there, but your code is certainly much tidier than mine was getting. Thank you! – dpDesignz Aug 19 '19 at 08:44
  • 1
    @dpDesignz if you do have any questions about how it works feel free to ping me in the comments. – Nick Aug 19 '19 at 09:32