0

I have a php function to calculate timestamp (start date) to current (end) date that will return 1hr 30min 2s format. What I want to achieve is to calculate only from 8AM to 5PM of a working day. Anything that goes beyond will not be counted. Here is the php code I have.

class duration_computation {
   function duration( $time ) {
      $d[0] = array(1, "s");
      $d[1] = array(60, "min");
      $d[2] = array(3600, "hr");
      $d[3] = array(86400, "dy");
      $d[4] = array(604800, "wk");
      $d[5] = array(2592000, "mth");
      $d[6] = array(31104000, "yr");

      $numbers = array();

      $result = "";
      $now = time();
      $time_difference = ( $now - $time );
      $seconds_left = $time_difference;

      for ( $i = 6; $i > -1; $i-- ) {
          $numbers[$i] = intval( $seconds_left / $d[$i][0] );
          $seconds_left -= ( $numbers[$i] * $d[$i][0] );
          if ( $numbers[$i] != 0 ) {
          $result.= abs($numbers[$i]) . "" . $d[$i][1] . (($numbers[$i]>1)?'':'') ." ";
          }
      }
      return $result;
   }
}

$duration = new duration_computation();
echo $duration->duration($trail->duration);

Tepken Vannkorn
  • 9,648
  • 14
  • 61
  • 86
Je Bor
  • 31
  • 1

1 Answers1

1

Forget about date(), strtotime(), time(), etc. function, use DateTime :

Use example :

$from = '2013-09-06 15:45:32';
$to   = '2013-09-14 21:00:00';
echo some_func_name($from, $to);

Output :

1 day, 22 hours, 14 minutes, 28 seconds

Function :

function some_func_name($from, $to) {
    $workingDays = [1, 2, 3, 4, 5]; # date format = N
    $workingHours = ['from' => ['08', '00'], 'to' => ['17', '00']];

    $start = new DateTime($from);
    $end = new DateTime($to);

    $startP = clone $start;
    $startP->setTime(0, 0, 0);
    $endP = clone $end;
    $endP->setTime(23, 59, 59);
    $interval = new DateInterval('P1D');
    $periods = new DatePeriod($startP, $interval, $endP);

    $sum = [];
    foreach ($periods as $i => $period) {
        if (!in_array($period->format('N'), $workingDays)) continue;

        $startT = clone $period;
        $startT->setTime($workingHours['from'][0], $workingHours['from'][1]);
        if (!$i && $start->diff($startT)->invert) $startT = $start;

        $endT = clone $period;
        $endT->setTime($workingHours['to'][0], $workingHours['to'][1]);
        if (!$end->diff($endT)->invert) $endT = $end;

        #echo $startT->format('Y-m-d H:i') . ' - ' . $endT->format('Y-m-d H:i') . "\n"; # debug

        $diff = $startT->diff($endT);
        if ($diff->invert) continue;
        foreach ($diff as $k => $v) {
            if (!isset($sum[$k])) $sum[$k] = 0;
            $sum[$k] += $v;
        }
    }

    if (!$sum) return 'ccc, no time on job?';

    $spec = "P{$sum['y']}Y{$sum['m']}M{$sum['d']}DT{$sum['h']}H{$sum['i']}M{$sum['s']}S";
    $interval = new DateInterval($spec);
    $startS = new DateTime;
    $endS = clone $startS;
    $endS->sub($interval);
    $diff = $endS->diff($startS);

    $labels = [
        'y' => 'year',
        'm' => 'month',
        'd' => 'day',
        'h' => 'hour',
        'i' => 'minute',
        's' => 'second',
    ];
    $return = [];
    foreach ($labels as $k => $v) {
        if ($diff->$k) {
            $return[] = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
        }
    }

    return implode(', ', $return);
}

This function can be shorter/better; but that is your job now ;)

Glavić
  • 42,781
  • 13
  • 77
  • 107
  • I've been searching for an answer to this problem and this is by far the cleanest solution out there! The only thing it doesn't seem to be able to handle is working hours of night shift workers, ie that span two days like 22:00 - 06:00. My current strategy is to breakup the hours into 2 days and run this function twice, however I was wondering if you had a cleaner solution? – Tim Jun 18 '15 at 06:49