Find contiguous date ranges between two dates, excluding dates from a blacklist array
Asked Answered
S

2

1

For a hotel management system, I have the following php array which contains dates on which a hotel room is booked. New guests can't be booked into the room on these dates.

Array([0] => '2017-02-23' 
  [1] => '2017-02-24' 
  [2] => '2017-04-01' 
  [3] => '2017-04-02' 
  [4] => '2017-04-03' 
  [5] => '2017-04-04' 
  [6] => '2017-04-05' 
  [7] => '2017-04-06' 
  [8] => '2017-04-07' 
  [9] => '2017-04-08' 
  [10] => '2017-04-09' 
  [11] => '2017-04-10' 
  [12] => '2017-04-11' 
  [13] => '2017-04-12' 
  [14] => '2017-04-13' 
  [15] => '2017-04-14' 
  [16] => '2017-04-15' 
  [17] => '2017-04-16'      
  [18] => '2017-04-17' 
  [19] => '2017-04-18' 
  [20] => '2017-04-19' 
  [21] => '2017-04-20' 
  [22] => '2017-04-21' 
  [23] => '2017-04-22' 
  [24] => '2017-04-23' 
  [25] => '2017-04-24' 
  [26] => '2017-04-25' 
  [27] => '2017-04-26' 
  [28] => '2017-04-27' 
  [29] => '2017-04-28' 
  [30] => '2017-04-29' 
  [31] => '2017-04-30'
 ) 

This array tell us that the room is reserved 2 different periods:

  1. 2017-02-23 to 2017-02-24
  2. 2017-04-01 to 2017-04-30

I would like to find the date ranges where the room is available, within a certain broad window.

For example, if someone wanted to stay in the room from 2017-02-15 to 2017-05-07, then I would like the system to return the following date ranges for availability:

  1. 2017-02-15 to 2017-02-22
  2. 2017-02-25 to 2017-03-31
  3. 2017-05-01 to 2017-05-07

If someone want to stay in the room from 2017-02-22 to 2017-03-30, then I would like the system to return the following date ranges for availability:

  1. 2017-02-25 to 2017-03-30
Smutchy answered 24/2, 2017 at 10:25 Comment(0)
F
0

With the help of a couple array functions and a couple foreach loops to populate, filter then format the results, I believe I have satisfied the brief.

Code: (Demo)

function fillDateRange($a, $b, $x = 0, $dates = []) {
    while (end($dates) != $b && $x = array_push($dates, date("Y-m-d", strtotime("$a +$x day"))));
    return $dates;
}

$booked = [
    '2017-02-03', '2017-02-24', '2017-04-01', '2017-04-02',
    '2017-04-03', '2017-04-04', '2017-04-05', '2017-04-06',
    '2017-04-07', '2017-04-08', '2017-04-09', '2017-04-10',
    '2017-04-11', '2017-04-12', '2017-04-13', '2017-04-14',
    '2017-04-15', '2017-04-16', '2017-04-17', '2017-04-18',
    '2017-04-19', '2017-04-20', '2017-04-21', '2017-04-22',
    '2017-04-23', '2017-04-24', '2017-04-25', '2017-04-26',
    '2017-04-27', '2017-04-28', '2017-04-29', '2017-04-30'
];

$search = fillDateRange('2017-02-15', '2017-05-07');  // pre-validated user input

// remove all dates from $search where exist in $booked...
$vacant = array_diff($search, $booked);

// group consecutive days
$date_checker = date("Y-m-d", strtotime("{$vacant[0]} -1 day"));
$x = 0;
foreach ($vacant as $date) {
    if ($date != date("Y-m-d", strtotime("$date_checker +1 day"))) {
        ++$x;
    }
    $grouped[$x][] = $date;
    $date_checker = $date;
}

echo "Array of vacant date ranges:\n";
foreach ($grouped as $group) {
    $vacant_ranges[] = current($group) . " to " . end($group);
}
var_export($vacant_ranges);

Output:

Array of vacant date ranges:
array (
  0 => '2017-02-15 to 2017-02-23',
  1 => '2017-02-25 to 2017-03-31',
  2 => '2017-05-01 to 2017-05-07',
)
Foote answered 2/3, 2017 at 15:42 Comment(0)
F
0

I've decided to revisit this question with the goal of putting script performance first. My earlier answer is probably easier to read/maintain, but it performs several loops and uses bloated and multidimensional arrays prior to generating its result.

Instead, the below script will iterate the dates between the user-defined start and end dates (inclusive) and another loop to search the blacklist array for disqualified dates. For best performance, the blacklist array will be "consumed"/reduced to minimize the cycles needed for subsequent searches. In other words, every search of the blacklist are will make it smaller and smaller.

Input:

$date = '2017-02-15';  // start date
$end = '2017-05-07';

$bookings = [          // blacklist
    '2017-02-03',
    '2017-02-24',
    '2017-04-01',
    '2017-04-02',
    '2017-04-03',
    '2017-04-04',
    '2017-04-05',
    '2017-04-06',
    '2017-04-07',
    '2017-04-08',
    '2017-04-09',
    '2017-04-10',
    '2017-04-11',
    '2017-04-12',
    '2017-04-13',
    '2017-04-14',
    '2017-04-15',
    '2017-04-16',
    '2017-04-17',
    '2017-04-18',
    '2017-04-19',
    '2017-04-20',
    '2017-04-21',
    '2017-04-22',
    '2017-04-23',
    '2017-04-24',
    '2017-04-25',
    '2017-04-26',
    '2017-04-27',
    '2017-04-28',
    '2017-04-29',
    '2017-04-30',
];

Code: (Demo)

function isBooked(array &$bookings, string $date): bool
{
    foreach ($bookings as $i => $taken) {
        if ($date > $taken) {
            unset($bookings[$i]);
        } else {
            return $date === $taken;
        }
    }
    return false;
}

$result = [];
$temp = [];
while ($date <= $end) {
    $exclude = isBooked($bookings, $date);
    if ($temp) {
        if ($exclude) {
            $result[] = implode(' to ', $temp);
            $temp = [];
        } elseif ($date != date('Y-m-d', strtotime($temp[1] . ' +1 day'))) {
            $result[] = implode(' to ', $temp);
            $temp = [$date, $date];
        } else {
            $temp[1] = $date;
        }
    } elseif (!$exclude) {
        $temp = [$date, $date];
    }
    $date = date('Y-m-d', strtotime($date . ' +1 day'));
}
if ($temp) {
    $result[] = implode(' to ', $temp);
}
var_export($result);

Output:

array (
  0 => '2017-02-15 to 2017-02-23',
  1 => '2017-02-25 to 2017-03-31',
  2 => '2017-05-01 to 2017-05-07',
)
Foote answered 1/10, 2023 at 20:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.