如何在PHP中合并日期范围

4
我有一个数组,其中包含start_date、end_date和price。例如:
/* 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],
];

我想在这里合并所有相邻且价格相同的日期,例如2024-01-062024-01-072024-01-082024-01-20。这两个日期范围是相邻的,并且它们的价格相同,所以应该是2024-01-062024-01-20,价格为700。
以下是我的结果数组示例。
$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],
];

我尝试过的代码/逻辑
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);

这个返回了不同的结果,所以请帮我解决这个问题。

1
所以可以假设数组元素已按开始日期排序,对吗? - undefined
1
是的,你说得对。 - undefined
1
这次我给你点赞,因为你展示了你所尝试的东西。看吧,我们是有注意到的。 - undefined
3个回答

2
如果前一个结束日期的时间比较等于当前的开始日期,并且价格检查也通过,那么只更新$arrLastDR的结束日期为当前的结束日期(我们可以称之为合并)。
如果它们不匹配,这个合并段只在此索引之前有效。因此,将其添加到$finalArray中,并以当前的开始日期开始一个新的$arrLastDR。
所以请修改以下内容。
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);

 $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);
    }

现场演示

注意:由于您已确认输入已按排序方式排列,因此无需进行任何显式排序。


2
这里有一个替代方案:
<?php

/*

Question Author: Hola
Question Answerer: Jacob Mulquin
Question: How to combine date ranges in PHP
URL: https://dev59.com/GoX9WowBVUcc3sd7s_3V
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);

产量:

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,
  ),
)

1
function mergeDateRanges($p_arrDateranges) {
  // Sort by start date
  usort($p_arrDateranges, function($a, $b) {
    return strtotime($a['start_date']) - strtotime($b['start_date']);
  });

  $finalArray = [];

  foreach ($p_arrDateranges as $arrDR) {
    // If $finalArray is empty, add the first date range
    if (empty($finalArray)) {
      $finalArray[] = $arrDR;
      continue;
    }

    // Get the last date range in $finalArray
    $lastDR = end($finalArray);

    // Check if the current date range is adjacent and has the same price
    if (
      $arrDR['price'] === $lastDR['price'] &&
      strtotime($arrDR['start_date']) <= strtotime($lastDR['end_date']) + 86400 // Adding one day in seconds
    ) {
      // Merge the date ranges
      $lastDR['end_date'] = max($lastDR['end_date'], $arrDR['end_date']);
      $finalArray[count($finalArray) - 1] = $lastDR;
    } else {
      // Add the current date range to $finalArray
      $finalArray[] = $arrDR;
    }
  }

  return $finalArray;
}

$existingRanges = [
  // ... (your existing date ranges)
];

$results = mergeDateRanges($existingRanges);

print_r($results);

我做了一些改动,你也可以采用这种方法!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接