I have a list of volunteer shifts. The shifts are time ranges- for example, 9AM-2PM and 12PM-5PM. I need to check by how many minutes multiple selected shifts overlap, and then throw an error if the overlap is too high.
I coded this for my website and it works great. Just purely out of intellectual curiosity, is there a faster and/or more organized way to code this?
Below is the code. I've modified it so that you can just copy/paste it into a PHP file and run it yourself. Sample data is included.
I am open to all feedback. In particular, I am looking for ways to code the main loop more elegantly. There's a pretty hard-to-read conditional in there right now. The purpose of the conditional is to prevent a pair of shifts from getting checked twice. That is, shift ID's 49/50 and 50/49 are going to have the same overlap, so they shouldn't be checked twice.
<?php
define('MAX_MINUTES_OF_SHIFT_OVERLAP_FOR_PUBLIC', 65);
function check_for_overlapping_shifts($list_of_shifts, $shift_ids_to_check)
{
if ( count($shift_ids_to_check) >= 2 )
{
$overlapping_shifts = array();
foreach ( $shift_ids_to_check as $outer_shift_id )
{
foreach ( $shift_ids_to_check as $inner_shift_id )
{
if (
$outer_shift_id != $inner_shift_id &&
! (
sql_search_result_array_by_key1_value1($overlapping_shifts, 'outer_shift_id', $inner_shift_id) &&
sql_search_result_array_by_key1_value1($overlapping_shifts, 'inner_shift_id', $outer_shift_id)
)
)
{
$outer_shift_start_time = strtotime($list_of_shifts[$outer_shift_id]['shift_start_time']);
$outer_shift_end_time = strtotime($list_of_shifts[$outer_shift_id]['shift_end_time']);
$inner_shift_start_time = strtotime($list_of_shifts[$inner_shift_id]['shift_start_time']);
$inner_shift_end_time = strtotime($list_of_shifts[$inner_shift_id]['shift_end_time']);
// https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
if ( $outer_shift_start_time <= $inner_shift_end_time && $outer_shift_end_time >= $inner_shift_start_time )
{
if ( $outer_shift_end_time > $inner_shift_start_time )
{
$minutes_of_overlap = ($outer_shift_end_time - $inner_shift_start_time) / 60;
}
else
{
$minutes_of_overlap = ($inner_shift_end_time - $outer_shift_start_time) / 60;
}
array_push($overlapping_shifts, array(
'outer_shift_id' => $outer_shift_id,
'inner_shift_id' => $inner_shift_id,
'outer_shift_name' => $list_of_shifts[$outer_shift_id]['shift_name'],
'inner_shift_name' => $list_of_shifts[$inner_shift_id]['shift_name'],
'minutes_of_overlap' => $minutes_of_overlap
));
}
}
}
}
var_export($overlapping_shifts);
echo "<br /><br />";
foreach ( $overlapping_shifts as $key => $shift )
{
if ( $shift['minutes_of_overlap'] > MAX_MINUTES_OF_SHIFT_OVERLAP_FOR_PUBLIC )
{
echo ($overlapping_shifts[$key]['outer_shift_name'] . " and " . $overlapping_shifts[$key]['inner_shift_name'] . " overlap by more than " . MAX_MINUTES_OF_SHIFT_OVERLAP_FOR_PUBLIC . " minutes (" . $overlapping_shifts[$key]['minutes_of_overlap'] . " minutes to be exact)<br />");
}
}
}
}
/* Input must be an array with 2 layers. First layer is just array keys in numerical order. Second layer is field => field value. This is a very common array format for SQL results. Example:
[0] =>
'group_id' => 5
'group_name' => 'Test Group 2'
[1] =>
'group_id' => 2
'group_name' => 'Test Group 1'
*/
// use === and !== in booleans to avoid having a '0' value act like a NULL value
function sql_search_result_array_by_key1_value1($array, $search_key, $search_value)
{
foreach ( $array as $key => $level2 )
{
if ( $level2[$search_key] == $search_value )
{
return TRUE;
}
}
return NULL;
}
// Example data
$list_of_shifts = array(
49 => array(
'shift_name' => 'A1 Fri Packet Pickup AM',
'shift_start_time' => '2017-12-08 11:00:00',
'shift_end_time' => '2017-12-08 15:30:00'
),
50 => array(
'shift_name' => 'A2 Fri Packet Pickup PM',
'shift_start_time' => '2017-12-08 15:00:00',
'shift_end_time' => '2017-12-08 18:30:00'
),
51 => array(
'shift_name' => 'B1 Sat Packet Pickup',
'shift_start_time' => '2017-12-09 05:30:00',
'shift_end_time' => '2017-12-09 09:30:00'
),
52 => array(
'shift_name' => 'B2 Sat Water Station 1',
'shift_start_time' => '2017-12-09 06:00:00',
'shift_end_time' => '2017-12-09 11:00:00'
),
53 => array(
'shift_name' => 'B3 Sat Water Station 2',
'shift_start_time' => '2017-12-09 06:00:00',
'shift_end_time' => '2017-12-09 11:00:00'
),
54 => array(
'shift_name' => 'C1 Sat Finish Line',
'shift_start_time' => '2017-12-09 06:30:00',
'shift_end_time' => '2017-12-09 11:00:00'
)
);
$shift_ids_that_user_submitted = array(49, 50, 51, 52, 53, 54);
check_for_overlapping_shifts($list_of_shifts, $shift_ids_that_user_submitted);
Output is:
array ( 0 => array ( 'outer_shift_id' => 49, 'inner_shift_id' => 50, 'outer_shift_name' => 'A1 Fri Packet Pickup AM', 'inner_shift_name' => 'A2 Fri Packet Pickup PM', 'minutes_of_overlap' => 30, ), 1 => array ( 'outer_shift_id' => 51, 'inner_shift_id' => 52, 'outer_shift_name' => 'B1 Sat Packet Pickup', 'inner_shift_name' => 'B2 Sat Water Station 1', 'minutes_of_overlap' => 210, ), 2 => array ( 'outer_shift_id' => 51, 'inner_shift_id' => 53, 'outer_shift_name' => 'B1 Sat Packet Pickup', 'inner_shift_name' => 'B3 Sat Water Station 2', 'minutes_of_overlap' => 210, ), 3 => array ( 'outer_shift_id' => 51, 'inner_shift_id' => 54, 'outer_shift_name' => 'B1 Sat Packet Pickup', 'inner_shift_name' => 'C1 Sat Finish Line', 'minutes_of_overlap' => 180, ), 4 => array ( 'outer_shift_id' => 52, 'inner_shift_id' => 53, 'outer_shift_name' => 'B2 Sat Water Station 1', 'inner_shift_name' => 'B3 Sat Water Station 2', 'minutes_of_overlap' => 300, ), 5 => array ( 'outer_shift_id' => 52, 'inner_shift_id' => 54, 'outer_shift_name' => 'B2 Sat Water Station 1', 'inner_shift_name' => 'C1 Sat Finish Line', 'minutes_of_overlap' => 270, ), 6 => array ( 'outer_shift_id' => 53, 'inner_shift_id' => 54, 'outer_shift_name' => 'B3 Sat Water Station 2', 'inner_shift_name' => 'C1 Sat Finish Line', 'minutes_of_overlap' => 270, ), )
B1 Sat Packet Pickup and B2 Sat Water Station 1 overlap by more than 65 minutes (210 minutes to be exact)
B1 Sat Packet Pickup and B3 Sat Water Station 2 overlap by more than 65 minutes (210 minutes to be exact)
B1 Sat Packet Pickup and C1 Sat Finish Line overlap by more than 65 minutes (180 minutes to be exact)
B2 Sat Water Station 1 and B3 Sat Water Station 2 overlap by more than 65 minutes (300 minutes to be exact)
B2 Sat Water Station 1 and C1 Sat Finish Line overlap by more than 65 minutes (270 minutes to be exact)
B3 Sat Water Station 2 and C1 Sat Finish Line overlap by more than 65 minutes (270 minutes to be exact)