Slicing a time range into parts Slicing a time range into parts php php

Slicing a time range into parts


I would use a different approach, and I will change the rateTable representation based of a couple of considerations.

  • The $rateTable describe intervals, why don't you encode them properly?
  • What happens on the frontiers (Tuesday and Monday in my example use the two different approaches to the boundary definition);
  • The results you get are of a comparable type but use a different representation.
  • 23:59:59=> seems an hack to me. I can't explain right now but I've a bell ringing in the back of the head telling me to watch out for it.

Last but not least, my personal experience let me say that if you can't wrap your head on an algorithm it is likely that your co-workers will have the same difficulties (even if you succeed and resolve the issues) and the code will be a primary source of bug. If you find a simpler and efficient solution it will be a gain of time, money and headaches. Maybe it will be a gain even if the solution is not so efficient.

$rateTable = array(    'Monday' => array (        array('start'=>'00:00:00','stop'=>'07:59:59','multiplier'=>1.5),        array('start'=>'08:00:00','stop'=>'16:59:59','multiplier'=>1),        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)    ),    'Tuesday'=> array (        array('start'=>'00:00:00','stop'=>'08:00:00','multiplier'=>1.5),        array('start'=>'08:00:00','stop'=>'17:00:00','multiplier'=>1),        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)    ));function map_shift($shift, $startTime, $stopTime){    if ($startTime >= $shift['stop'] or $stopTime <= $shift['start']) {        return;    }    return array(        'start'=> max($startTime, $shift['start']),        'stop' => min($stopTime, $shift['stop']),        'multiplier' => $shift['multiplier']    );}function bill($day, $start, $stop){    $report = array();    foreach($day as $slice) {        $result = map_shift($slice, $start, $stop);        if ($result) {           array_push($report,$result);        }    }    return $report;}/* examples */var_dump(bill($rateTable['Monday'],'08:05:00','18:05:00'));var_dump(bill($rateTable['Monday'],'08:05:00','12:00:00'));var_dump(bill($rateTable['Tuesday'],'07:15:00','19:30:00'));var_dump(bill($rateTable['Tuesday'],'07:15:00','17:00:00'));

At the very least you need a function to convert the original format to the new one.

$oldMonday = array (   '00:00:00'=>1.5,   '08:00:00'=>1,   '17:00:00'=>1.5,   '23:59:59'=>1);function convert($array) {    return array_slice(        array_map(           function($start,$stop, $multiplier)            {               return compact('start', 'stop','multiplier');           },           array_keys($array),           array_keys(array_slice($array,1)),           $array),        0,        -1);}var_dump(convert($oldMonday));

And yes, you could do the conversion on the fly with

bill(convert($oldRateTable['Tuesday']),'07:15:00','17:00:00');

but if you care a bit of performances...


Here's my method

I converted everything to seconds to make it a lot easier.

Here's the rate table indexed by seconds. Theres only 3 time slots for monday

// 0-28800 (12am-8am) = 1.5// 28800-61200 (8am-5pm) = 1// 61200-86399 (5pm-11:50pm) = 1.5$rate_table = array(    'monday' => array (        '28800' => 1.5,        '61200' => 1,        '86399' => 1.5    ));

It uses this function to convert hh:mm:ss to seconds

function time2seconds( $time ){    list($h,$m,$s) = explode(':', $time);    return ((int)$h*3600)+((int)$m*60)+(int)$s;}

This is the function that returns a rate table

function get_rates( $start, $end, $rate_table ) {    $day = strtolower( date( 'l', strtotime( $start ) ) );    // these should probably be pulled out and the function    // should accept integers and not time strings    $start_time = time2seconds( end( explode( 'T', $start ) ) );    $end_time = time2seconds( end( explode( 'T', $end ) ) );    $current_time = $start_time;    foreach( $rate_table[$day] as $seconds => $multiplier ) {        // loop until we get to the first slot        if ( $start_time < $seconds ) {            //$rate[ $seconds ] = ( $seconds < $end_time ? $seconds : $end_time ) - $current_time;            $rate[] = array (                'start' => $current_time,                'stop' => $seconds < $end_time ? $seconds : $end_time,                'duration' => ( $seconds < $end_time ? $seconds : $end_time ) - $current_time,                'multiplier' => $multiplier            );            $current_time=$seconds;            // quit the loop if the next time block is after clock out time            if ( $current_time > $end_time ) break;        }    }    return $rate;}

Here's how you use it

$start = '2010-05-03T07:00:00';$end = '2010-05-03T21:00:00';print_r( get_rates( $start, $end, $rate_table ) );

returns

Array(    [0] => Array        (            [start] => 25200            [stop] => 28800            [duration] => 3600            [multiplier] => 1.5        )    [1] => Array        (            [start] => 28800            [stop] => 61200            [duration] => 32400            [multiplier] => 1        )    [2] => Array        (            [start] => 61200            [stop] => 75600            [duration] => 14400            [multiplier] => 1.5        ))

Basically the code loops over the rate table and finds how many seconds from the given time slot belong to each rate.


I would suggest something like

get total time to allocate (workstop - workstart) find the start slot (the last element where time < workstart)and how much of start slot is billable, reduce time left to allocatemove to next slotwhile you have time left to allocate   if the end time is in the same slot       get the portion of the time slot that is billable   else       the whole slot is billable       reduce the time to allocate by the slot time    (build your output array) and move to the next slotloop while

It might be easier to convert all your times to seconds internally to make the days/hours/minutes calculations easier to handle.