skip to Main Content

I have an array that have start_date, end_date and price Eg.

/* date formate 'yyyy-mm-dd' */

$existingRanges = [
  ['start_date' => '2023-11-22', 'end_date' => '2023-11-30', 'price' => 200],
  ['start_date' => '2023-12-10', 'end_date' => '2023-12-14', 'price' => 100],
  ['start_date' => '2023-12-15', 'end_date' => '2023-12-20', 'price' => 100],
  ['start_date' => '2023-12-21', 'end_date' => '2023-12-27', 'price' => 100],
  ['start_date' => '2024-01-05', 'end_date' => '2024-01-05', 'price' => 600],
  ['start_date' => '2024-01-06', 'end_date' => '2024-01-07', 'price' => 700],
  ['start_date' => '2024-01-08', 'end_date' => '2024-01-20', 'price' => 700],
  ['start_date' => '2024-01-21', 'end_date' => '2024-01-31', 'price' => 100],
];

I want here to merge all the dates which are next to each other but have the same prices ie 2024-01-06 to 2024-01-07 and 2024-01-08 to 2024-01-20 these two date ranges are next to each other and their prices are the same so it should be 2024-01-06 to 2024-01-20 and price 700.

Array example of my results.

$results = [
  ['start_date' => '2023-11-22', 'end_date' => '2023-11-30', 'price' => 200],
  ['start_date' => '2023-12-10', 'end_date' => '2023-12-27', 'price' => 100],
  ['start_date' => '2024-01-05', 'end_date' => '2024-01-05', 'price' => 600],
  ['start_date' => '2024-01-06', 'end_date' => '2024-01-20', 'price' => 700],
  ['start_date' => '2024-01-21', 'end_date' => '2024-01-31', 'price' => 100],
];

The code/logic I tried with

function mergeDateRanges($p_arrDateranges) {
  // sort by start date
  /* usort($p_arrDateranges, function($a1, $a2) {
    return $a1['start_date'] === $a2['start_date'] ? 0 : ($a1['start_date'] < $a2['start_date'] ? -1 : 1);
   }); */

   $finalArray = [];
   $arrMerged = array();
   $arrLastDR = null;
   foreach ($p_arrDateranges as $arrDR) {

     if ($arrLastDR === null) {
       $arrLastDR = $arrDR;
       continue;
     }

     if(!$arrMerged) {
      array_push($arrMerged, $arrLastDR);
     }

     $endDate = DateTime::createFromFormat('Y-m-d', $arrLastDR['end_date']);
     $sLastDateE_1 = $endDate->modify('+1 day')->format('Y-m-d');

     if (strtotime($arrDR['start_date']) < strtotime($sLastDateE_1) && $arrDR['price'] == $arrLastDR['price']) {
       array_push($finalArray, [
         'start_date' => $arrDR['start_date'], 
         'end_date' => $arrLastDR['end_date'], 
         'price' => 0
       ]);
     } else {
       array_push($finalArray, $arrDR);
     }

     $arrLastDR = $arrDR;
     array_push($arrMerged, $arrLastDR);
     //print_r($arrLastDR);
 }

 return $finalArray;
}

mergeDateRanges($existingRanges);

This is returning different results So please help me to solve this issue.

2

Answers


  1. If the time comparison of the previous end date equals current start date, along with the price check, then only update the end date of $arrLastDR to the current one’s end date(we can call this merging).

    If they don’t match, this merge segment was only valid till this index. So, add that to $finalArray and start a new $arrLastDR with the current one.


    So change the below

    if(!$arrMerged) {
          array_push($arrMerged, $arrLastDR);
         }
    
         $endDate = DateTime::createFromFormat('Y-m-d', $arrLastDR['end_date']);
         $sLastDateE_1 = $endDate->modify('+1 day')->format('Y-m-d');
    
         if (strtotime($arrDR['start_date']) < strtotime($sLastDateE_1) && $arrDR['price'] == $arrLastDR['price']) {
           array_push($finalArray, [
             'start_date' => $arrDR['start_date'], 
             'end_date' => $arrLastDR['end_date'], 
             'price' => 0
           ]);
         } else {
           array_push($finalArray, $arrDR);
         }
    
         $arrLastDR = $arrDR;
         array_push($arrMerged, $arrLastDR);
    

    to

     $endDate = DateTime::createFromFormat('Y-m-d', $arrLastDR['end_date']);
         $sLastDateE_1 = $endDate->modify('+1 day')->format('Y-m-d');
    
         if (strtotime($arrDR['start_date']) == strtotime($sLastDateE_1) && $arrDR['price'] == $arrLastDR['price']) {
            $arrLastDR['end_date'] = $arrDR['end_date'];
         }else{
            array_push($finalArray, $arrLastDR);
            $arrLastDR = $arrDR;
         }
         
        }
        
        if($arrLastDR !== null){
            array_push($finalArray, $arrLastDR);
        }
    

    Live Demo

    Note: No need of any explicit sorting since you already confirmed that the input is in a sorted manner.

    Login or Signup to reply.
  2. Here is an alternate solution:

    <?php
    
    /*
    
    Question Author: Hola
    Question Answerer: Jacob Mulquin
    Question: How to combine date ranges in PHP
    URL: https://stackoverflow.com/questions/77555919/how-to-combine-date-ranges-in-php
    Tags: php, algorithm
    
    */
    
    $existingRanges = [
      ['start_date' => '2023-11-22', 'end_date' => '2023-11-30', 'price' => 200],
      ['start_date' => '2023-12-10', 'end_date' => '2023-12-14', 'price' => 100],
      ['start_date' => '2023-12-15', 'end_date' => '2023-12-20', 'price' => 100],
      ['start_date' => '2023-12-22', 'end_date' => '2023-12-27', 'price' => 100], // I modified this one to demonstrate non-contiguous date ranges with same price
      ['start_date' => '2024-01-05', 'end_date' => '2024-01-05', 'price' => 600],
      ['start_date' => '2024-01-06', 'end_date' => '2024-01-07', 'price' => 700],
      ['start_date' => '2024-01-08', 'end_date' => '2024-01-20', 'price' => 700],
      ['start_date' => '2024-01-21', 'end_date' => '2024-01-31', 'price' => 100],
    ];
    
    function mergeDateRangesByPrice($ranges)
    {
      $output = [];
      $tempRange = [];
      foreach ($ranges as $i => $range) {
    
        if (empty($tempRange)) {
          $tempRange = $range;
          continue;
        }
    
        // If a new price is found, lock in the tempRange and re-assign
        if ($range['price'] !== $tempRange['price']) {
          $output[] = $tempRange;
          $tempRange = $range;
    
          // If this is the last element but a new price, add to output array
          if ($i == count($ranges)-1) {
            $output[] = $range;
          }
          continue;
        }
    
        $rangeStartDate = strtotime($range['start_date']);
        $tempRangeEndDatePlusOneDay = strtotime("+1 day", strtotime($tempRange['end_date']));
    
        // Contiguous end date/start date, update the end_date of the tempRange
        if ($tempRangeEndDatePlusOneDay === $rangeStartDate) {
          $tempRange['end_date'] = $range['end_date'];
          continue;
        }
        
        // Non-contiguous end date/start date, lock in the tempRange and reassign    
        $output[] = $tempRange;
        $tempRange = $range;
      }
      return $output;
    } 
    
    $output = mergeDateRangesByPrice($existingRanges);
    
    var_export($output);
    

    Yields:

    array (
      0 => 
      array (
        'start_date' => '2023-11-22',
        'end_date' => '2023-11-30',
        'price' => 200,
      ),
      1 => 
      array (
        'start_date' => '2023-12-10',
        'end_date' => '2023-12-20',
        'price' => 100,
      ),
      2 => 
      array (
        'start_date' => '2023-12-22',
        'end_date' => '2023-12-27',
        'price' => 100,
      ),
      3 => 
      array (
        'start_date' => '2024-01-05',
        'end_date' => '2024-01-05',
        'price' => 600,
      ),
      4 => 
      array (
        'start_date' => '2024-01-06',
        'end_date' => '2024-01-20',
        'price' => 700,
      ),
      5 => 
      array (
        'start_date' => '2024-01-21',
        'end_date' => '2024-01-31',
        'price' => 100,
      ),
    )
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search