There is an array of periods of dates without taking into account time (for clarity, made dates as strings, but in fact timestamps):

$items = [ ['fromDate' => '2016-01-01', 'toDate' => '2016-01-12'], ['fromDate' => '2016-02-02', 'toDate' => '2016-02-12'], ['fromDate' => '2016-03-01', 'toDate' => '2016-03-01'], ['fromDate' => '2016-01-05', 'toDate' => '2016-01-16'], // ... ]; 

Question : how to transform $ items so that to merge collisions among dates? For example, there is a period 01.01.2016 — 10.01.2016 and 05.01.2016 — 12.01.2016 , then we should get one instead of two elements - 01.01.2016 — 12.01.2016 .

Another example: 01.03.2017 — 04.03.2017 and 05.03.2017 — 05.03.2017 at 01.03.2017 — 05.03.2017 .

After combining, the new range can also be merged with other elements. At the output, we should get the same $ items array only with merged collisions, if any.

  • write algorithm) cream by month number? - Jean-Claude
  • @ Jean-Claude Why? Entirely by date. 01.03.2013-01.01.2014 and 02.01.2014-02.10.2014 should also merge. - GroZa
  • write clear rules for date creations in one, otherwise, in one example, the date intersects and merges, in another the dates do not intersect, but they also merge. - Jean-Claude
  • @ Jean-Claude only 2 rules: 1. periods intersect (partially or one absorbs the other); 2. periods follow each other with a break per day (one ends on the 1st, the second begins on the 2nd) - GroZa
  • I tried it, but something is not enough brains to be honest ( - GroZa

3 answers 3

Something like this

 function cmp($a, $b) { if ($a["from"] == $b["from"]) { if ($a["to"] == $b["to"]) { return 0; return ($a["to"] < $b["to"]) ? -1 : 1; } return ($a["from"] < $b["from"]) ? -1 : 1; } } if (count($items) == 0) return null; usort($items, "cmp"); $res = array(); $curItem = $items[0]; foreach ($items as $item) { if ($curItem['to'] + 3600 * 24 /*1 day*/ >= $item['from']) $curItem['to'] = $item['to']; else { $res[] = $curItem; $curItem = $item; } } $res[] = $curItem; return $res; 
  1. Sort the source array
  2. Take the first element of the array
  3. If the next element can be glued with the current one - we stick it together
  4. Otherwise, we add the current element to the resulting array and take the following
  • Thank. It looks logical. I completely forgot about sorting! Therefore it was necessary to do nested loops and because of this and did not work. Tomorrow morning I will check and tick off) - GroZa
  • @GroZa Earned? - Anton Shchyrov
  • In general, true, but there are shoals. Updated the answer. - GroZa

If you don't count at all and just sort by month, choosing min-max then:

 $struct = []; $items = [ ['fromDate' => '2016-01-01', 'toDate' => '2016-01-12'], ['fromDate' => '2016-02-02', 'toDate' => '2016-02-12'], ['fromDate' => '2016-03-01', 'toDate' => '2016-03-01'], ['fromDate' => '2016-01-05', 'toDate' => '2016-01-16'] ]; //объединим все даты в 1 массив $dates = array_merge(array_column($items , 'fromDate') , array_column($items, 'toDate')); //разложим все даты по месяцам array_map(function($date)use(&$struct){ $m = new DateTime($date); $struct[$m->format('m')][] = $date; }, $dates); //выбираем min max дату в месяце $items = array_map(function($range){ return [ 'fromDate' => min($range),'toDate' => max($range) ]; }, $struct); print_r($items); 

at the exit

  [01] => Array ( [fromDate] => 2016-01-01 [toDate] => 2016-01-16 ) [02] => Array ( [fromDate] => 2016-02-02 [toDate] => 2016-02-12 ) [03] => Array ( [fromDate] => 2016-03-01 [toDate] => 2016-03-01 ) 

In connection with the conditions clarified in the dialogue, I update the post, now it is absorbed regardless of the month, the entire range of dates is taken into account.

 $items = [ ['fromDate' => '2016-01-01', 'toDate' => '2016-01-12'], ['fromDate' => '2016-02-02', 'toDate' => '2016-02-12'], ['fromDate' => '2016-02-12', 'toDate' => '2016-02-22'], ['fromDate' => '2016-03-01', 'toDate' => '2016-03-01'], ['fromDate' => '2016-01-03', 'toDate' => '2016-01-10'], ['fromDate' => '2016-01-05', 'toDate' => '2016-01-16'], ['fromDate' => '2013-10-10', 'toDate' => '2013-12-13'], ['fromDate' => '2012-01-01', 'toDate' => '2015-03-10'], ]; $startDates = array_column($items , 'fromDate'); array_walk($items, function(&$item)use($startDates , &$items){ //диапазон всех дат входящих в период $end = new DateTime($item['toDate']); $period = new DatePeriod( new DateTime($item['fromDate']), new DateInterval('P1D'), $end->modify("+1 day"), DatePeriod::EXCLUDE_START_DATE ); //итератор периода foreach ($period as $date) { $search = $date->format('Ym-d'); //ищем наличие пересечения, если оно есть удаляем диапазон из массива , //если в удаляемом массиве конечная дата выше , обновляем ее $id = array_search($search , $startDates); if($id !== false){ if($item['toDate'] < $items[$id]['toDate']){ $item['toDate'] = $items[$id]['toDate']; } unset($items[$id]); } } }); print_r($items); 
  • Thank. But for months is not suitable. Let's say that there is such a range 10.10.2013-23.12.2013 and such 01.01.2012-10.03.2015 and, it is logical that the second should absorb the first one. - GroZa
  • But it solves your problem - Redr01d
  • Above corrected the answer) was unfinished - GroZa
  • Or in one month there may be 2-3 non-intersecting ranges. And the search for the maximum date and the minimum will unite them. And in general, where is the month? The range can go for years. - GroZa
  • Well, initially the question was not put correctly, on the issue it is not clear what you want to get in the end. But according to your logic, in the end you will always have only the absolute minimum and maximum. . - Redr01d

Thanks to all of you, thanks to you, it turned out well for yourself:

  function mergeChunks($items) { $sorter = function ($a, $b) { if ($a['from'] == $b['from']) { return 0; } return $a['from'] < $b['from'] ? -1 : 1; }; usort($items, $sorter); $sortBox = []; for ($i = 0; $i < count($items) - 1; $i++) { $item1 = $items[$i]; $item2 = $items[$i + 1]; if (strtotime($item1['to']) >= strtotime($item2['from']) - 86400) { $item1['to'] = $item1['to'] >= $item2['to'] ? $item1['to'] : $item2['to']; $sortBox[] = $item1; $sortBox = array_merge($sortBox, array_slice($items, $i + 2)); return mergeChunks($sortBox); } else { $sortBox[] = $item1; if ($i == count($items) - 2) { $sortBox[] = $item2; } } } usort($sortBox, $sorter); return $sortBox; } $items = mergeChunks([ ['from' => '2016-01-01', 'to' => '2016-01-05'], ['from' => '2016-01-06', 'to' => '2016-03-15'], ['from' => '2016-02-01', 'to' => '2016-02-15'], ['from' => '2016-07-02', 'to' => '2016-08-15'], ]); print_r($items); // Результат: // ['from' => '2016-01-01', 'to' => '2016-03-15'], // ['from' => '2016-07-02', 'to' => '2016-08-15'],