KeySee
KeySee

Reputation: 780

Delete timeslots from time range in php

I was trying to display free timeslots.

Given the data below, basically we need to find a way to display not booked timeslots during opening hours. Seems very easy to do as a human, but programming it...honestly I'm just going crazy :D

// open hours   --------++++--++++-----  [[08:00,12:00], [14:00,18:00]]  
// booked slots ---------++----+-------  [[09:00,11:00], [15:00,16:00]]   
// expected     --------+--+--+-++-----  [[08,09], [11,12], [14,15], [16,18]]  

Just for clarity I omitted minutes, which in real program will be present.

I have prepared a fiddle to start with: https://ideone.com/Z9pPi3

<?php     
$opening_hours   = [['08:00','12:00'], ['14:00','18:00']];
$occupied_slots  = [['09:30','14:00'], ['15:10','16:35']];
$expected_result = [['08:00','09:30'], ['11:00','12:00'], ['14:00','15:10'], ['16:35','18:00']];
$valid_timeslots = [];

# - - - - - - - - helper functions

function timestring_to_time($hh_mm) {
    return (int) strtotime("1970-01-01 $hh_mm");
}

function timestring_diff($hh_mm_start, $hh_mm_end) {
    return abs(timestring_to_time($hh_mm_end) - timestring_to_time($hh_mm_start));
}

# find empty timeslots during opening hours given occupied slots
# H E R E   G O E S   T H E   M A G I C

var_dump($valid_timeslots);

I tried to solve with if/else method but it's not working actually...there is a need of some kind recursion function.

Upvotes: 1

Views: 612

Answers (1)

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92467

Here is my solution - I assume that first hour in time-interval like ['08:00','12:00'] is always smaller than second hour. Instead of using your timestring_to_time and timestring_diff I write my own procedures to convert time to number - timeToNum and numToTime (you can easily extend them to include seconds: num=3600*hour + 60*min + sec, sec=num%60, h=floor(num/3600), min=floor((num-h*3600)/60) ):

<?php

$opening_hours   = [['08:00','12:00'], ['14:00','18:00']];
$occupied_slots  = [['09:30','11:00'], ['15:10','16:35']];
$expected_result = [['08:00','09:30'], ['11:00','12:00'], ['14:00','15:10'], ['16:35','18:00']];
$valid_timeslots = [];

#find empty timeslots during opening hours given occupied slots

 function timeToNum($time) {
    preg_match('/(\d\d):(\d\d)/', $time, $matches);
    return 60*$matches[1] + $matches[2];
 }

 function numToTime($num) {
    $m  = $num%60;
    $h = intval($num/60) ;
    return ($h>9? $h:"0".$h).":".($m>9? $m:"0".$m);

 }

 // substraction interval $b=[b0,b1] from interval $a=[a0,a1]
 function sub($a,$b) 
 {
     // case A: $b inside $a
     if($a[0]<=$b[0] and $a[1]>=$b[1]) return [ [$a[0],$b[0]], [$b[1],$a[1]] ];

     // case B: $b is outside $a
     if($b[1]<=$a[0] or $b[0]>=$a[1]) return [ [$a[0],$a[1]] ];

     // case C: $a inside $b
     if($b[0]<=$a[0] and $b[1]>=$a[1]) return [[0,0]]; // "empty interval"

     // case D: left end of $b is outside $a
     if($b[0]<=$a[0] and $b[1]<=$a[1]) return [[$b[1],$a[1]]];

     // case E: right end of $b is outside $a
     if($b[1]>=$a[1] and $b[0]>=$a[0]) return [[$a[0],$b[0]]];
 }

 // flat array and change numbers to time and remove empty (zero length) interwals e.g. [100,100]
 // [[ [167,345] ], [ [433,644], [789,900] ]] to [ ["07:00","07:30"], ["08:00","08:30"], ["09:00","09:30"] ] 
 // (number values are not correct in this example)
 function flatAndClean($interwals) {
     $result = [];
     foreach($interwals as $inter) {
         foreach($inter as $i) {
             if($i[0]!=$i[1]) {
                 //$result[] = $i;
                 $result[] = [numToTime($i[0]), numToTime($i[1])];
             }
         }
     }
     return $result;
 }

 // calculate new_opening_hours = old_opening_hours - occupied_slot
 function cutOpeningHours($op_h, $occ_slot) {
    foreach($op_h as $oh) {
        $ohn = [timeToNum($oh[0]), timeToNum($oh[1])];
        $osn = [timeToNum($occ_slot[0]), timeToNum($occ_slot[1])];
        $subsn[] = sub($ohn, $osn);
    }
    return $subsn;
 }


 $oph = $opening_hours;
 foreach($occupied_slots as $os) {
     $oph = flatAndClean(cutOpeningHours($oph, $os ));
 }

 $valid_timeslots = $oph;

 var_dump(json_encode(["result"=>$valid_timeslots]));

Working example HERE.

Algorithm

Calculate $new_opening_hours from $old_opening_hours by substract one occupied slot from it. And repeat that operation for every slot (each time getting new oppening hours array)

To make substraction of two interwals I :

  1. convert time like "08:30" to number 08*60+30 = 510
  2. Solve problem of substract two interwals [a0,a1]-[b0,b1] which can have 5 cases (look on sub function) - as example [500,800]-[600,700] = [ [500,600], [700,800]],
  3. for each opening hours substract given occupied_slot and then clean and flat result to calc NEW openning hours

You can improve a little this solution by not conver time-number on each iteration but do it for each input data at the beginning and conver number-time for each output data at the end. And probably you can reduce number of conditions in sub function - however current version is very clear.

Upvotes: 2

Related Questions