Search code examples
phparrayspseudocode

Find total hours from multi-dimensional array with start_time and end_time using PHP?


I am trying to calculate the correct total hours using start_time and end_time

Here the algorithm I am using which has been provided by @Pilan:

  1. For each set of Time-Bookings do the following
  2. Find the smallest start_time
  3. Add the duration between start_time and end_time to a sum
  4. Find the next smallest Time-Booking by start_time
  5. IF start_time < previous_end_time subtract difference from sum END IF
  6. Add duration between start_time and end_time
  7. Jump to 4 until there is no matching element left.

Using the above I have managed to create below code :

<?php
class Helper 
{
    
     public static function minToHour($minutes)
    {
        if (empty($minutes)) {
            return 0;
        }
        $hours = floor($minutes / 60);
        $min = $minutes - ($hours * 60);

        return $hours . ":" . $min;
    }
public static function getMinsBetweenTwoTimes($start, $end)
    {
        $datetime1 = strtotime($start);
        $datetime2 = strtotime($end);
        $interval = abs($datetime2 - $datetime1);
        $minutes = round($interval / 60);
        return $minutes;
    }
   
}
$array =array (
  '2020-07-14' => 
  array (
  
    array (
      'start_time' => '09:00:00',
      'end_time' => '13:00:00',
      'hours' => '4 hours 0 mins',
    ),
    1 => 
    array (
      'start_time' => '13:30:00',
      'end_time' => '16:30:00',
      'hours' => '3 hours 0 mins',
    ),
    2 => 
    array (
      'start_time' => '09:00:00',
      'end_time' => '14:00:00',
      'hours' => '5 hours 0 mins',
    ),
  ),
  '2020-07-15' => 
  array (

    array (
      'start_time' => '13:30:00',
      'end_time' => '17:00:00',
      'hours' => '3 hours 30 mins',
    ),
    1 => 
    array (
      'start_time' => '09:00:00',
      'end_time' => '14:00:00',
      'hours' => '5 hours 0 mins',
    ),
  ),
);

  $newArray = [];
            foreach ($array as $key => $dateArr) {
                array_multisort(
                    array_map('strtotime', array_column($dateArr, 'start_time')),
                    array_column($dateArr, 'start_time'),
                    $dateArr
                );
                $newArray[$key] [] = $dateArr;
            }
            $sumArr = [];
            foreach ($newArray as $date => $items) {
                $sum = 0;
                foreach ($items[0] as $key => $item) {
                    $sum += Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
                    if ($key > 0) {
                        $previous_end_time = $items[0][$key - 1]['end_time'] ?? null;
                        if (!empty($previous_end_time)) {
                            if (($item['start_time']) < strtotime($previous_end_time)) {
                                $sum -= Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
                            }
                        }
                    }
                }
                $newArray[$date] ['total_attended_hours'] = Helper::minToHour($sum);
            }
            echo "<pre>";
            print_r($newArray);
            exit;
?>

Playground sample code link


Solution

  • This is my solution to your problem.

    I don't know if I have understand it correctly but basically this is the algorithm:

    1- Convert all times strings to integers and sort each date list of attended periods.

    2- Combine overlaping periods for each date, for exampe if one period goes from 9 to 12 and other from 11h to 13h, it is combined in a single period from 9h to 13h.

    3- Sum all the attehnded hours for each date.

    <?php
    
    $array = [
        '2020-07-14' =>[
            [
                'start_time' => '09:00:00',
                'end_time' => '13:00:00',
                'hours' => '4 hours 0 mins',
            ],
            [
                'start_time' => '13:30:00',
                'end_time' => '16:30:00',
                'hours' => '3 hours 0 mins',
            ],
    
            [
                'start_time' => '09:00:00',
                'end_time' => '14:00:00',
                'hours' => '5 hours 0 mins',
            ],
            [
                'start_time' => '17:00:00',
                'end_time' => '18:00:00',
                'hours' => '1 hours 0 mins',
            ]
        ],
        '2020-07-15' => [
            [
                'start_time' => '09:00:00',
                'end_time' => '14:00:00',
                'hours' => '5 hours 0 mins',
            ],
            [
                'start_time' => '13:30:00',
                'end_time' => '17:00:00',
                'hours' => '4 hours 30 mins',
            ]
        ],
    ];
    
    // Convert all times strings into integers and sort each day list
    // by the start time
    $r = parseTimesAndSort($array);
    
    // Combine overlaping periods in a single period
    $r = flatternOverlaps($r);
    
    // Sum all the periods in each date
    $r = sumPeriods($r);
    
    // Applly the result to the original array
    foreach($r as $date => $item){
        $array[$date]['total_attended_hours'] = $item;
    }
    
    print_r($array);
    
    
    /**
     * Given a time string returns the number of seconds from 00:00:00 as integer.
     * example: 09:30:10 => 34210 (9*3600 + 30*60 + 10)
     * @param $time
     * @return int
     */
    function timeToSeconds($time){
        $list = explode(":", $time);
        return $list[0] * 3600 + $list[1] * 60 + $list[2];
    }
    
    
    /**
     * Given an integer as seconds returns the time string in 00:00:00 format.
     * example: 34210 => 09:30:10
     * @param $value
     * @return string
     */
    function secondsToTime($value){
        $hours = floor($value/3600);
        $min = floor(($value%3600) / 60);
        $secods = floor($value % 60);
    
        return str_pad($hours, 2, "0", STR_PAD_LEFT)
            .":".str_pad($min, 2, "0", STR_PAD_LEFT)
            .":".str_pad($secods, 2, "0", STR_PAD_LEFT);
    }
    
    /**
     * Function to compare two periods
     * @param $a
     * @param $b
     * @return int
     */
    function sortByStartTime($a, $b){
        if ($a['start_time'] == $b['start_time']){
            return 0;
        }
        return $a['start_time'] < $b['start_time'] ? -1 : 1;
    }
    
    /**
     * Parses the periods string times to integers and sorts them
     * @param $array
     * @return array
     */
    function parseTimesAndSort($array){
        $r = [];
        foreach($array as $date => $list){
            $current = [];
            foreach($list as $item){
                $current[] = [
                    'start_time' => timeToSeconds($item['start_time']),
                    'end_time' => timeToSeconds($item['end_time']),
                ];
            }
            usort($current, 'sortByStartTime');
            $r[$date] = $current;
        }
        return $r;
    }
    
    /**
     * Finds overlapping periods and combines them
     * @param $array
     * @return array
     */
    function flatternOverlaps($array){
        $r = [];
        foreach($array as $date => $list){
            $currentList = [];
            $prev = null;
            foreach($list as $item){
                if ($prev && $item['start_time'] < $prev['end_time']){
                    if ($item['end_time'] > $prev['end_time']) {
                        $prev['end_time'] = $item['end_time'];
                    }
                }
                else{
                    $currentList[] = $item;
                }
    
                // Point prev to the last item in the current list
                $prev = &$currentList[count($currentList)-1];
            }
            unset($prev);
            $r[$date] = $currentList;
        }
    
        return $r;
    }
    
    /**
     * Sums the periods of each date
     * @param $array
     * @return array
     */
    function sumPeriods($array){
        $r = [];
        foreach($array as $date => $list){
            $seconds = array_reduce($list, function($carry, $item){ return $carry + $item['end_time'] - $item['start_time']; }, 0);
            $r[$date] = secondsToTime($seconds);
        }
        return $r;
    }
    

    https://paiza.io/projects/lq_rgfR9DptBC7Ors-Xorw