PHP 将两个独立的相互冲突的日期范围组合成唯一的对
PHP combine two seperate conflicting date ranges into unique pairs
第一个:
- 2014-04-05 至 2014-06-27
- 2014-06-28 至 2014-10-19
设置二:
- 2014-04-05 至 2014-05-02
- 2014-05-03 至 2014-05-31
- 2014-06-01 至 2014-10-19
我需要输出的是:
- 2014-04-05 至 2014-05-02
- 2014-05-03 至 2014-05-31
- 2014-06-01 至 2014-06-27
- 2014-06-28 至 2014-10-19
我尝试使用一个函数来检查重叠:
!($lhs['RecordOnset'] > $rhs['RecordOffset'] || $lhs['RecordOffset'] < $rhs['RecordOnset'])
并使用 for 循环检查重叠:
for($i = 1; $i < sizeof($arr1); $i++) {
for($j = 1; $j < sizeof($arr2); $j++) {
$record = $arr1[$i];
if($result = $this->intersects($arr1[$i], $arr2[$j])) {
// $result;
}
}
}
我遇到的问题是当我打破日期范围时,它不会检查循环时创建的新范围。我无法对此使用 SQL,因此我必须想出一个编程解决方案。我尝试了几种不同的方法,包括一些 foreach 循环。
数据以日期格式接收,如数组所示:
$arr1 = array(array('start'=>'04/05/2014', 'end'=> '2014-06-27'), array('start'=>'2014-06-28', 'end'=> '2014-10-19'));
$arr2 = array(array('start'=>'04/05/2014', 'end'=> '2014-05-02'), array('start'=>'2014-05-03', 'end'=> '2014-05-31'),array('start'=>'2014-06-01', 'end'=> '2014-10-19'));
第二对将是一个单独的数组,因为它可能具有相同的键。
非常感谢任何指导或帮助。 PHP 的日期范围在线资源非常有限。
这是我的解决方案:
<?php
$array1 = array(
array('s'=>'2014-04-05','e'=>'2014-06-27'),
array('s'=>'2014-06-28','e'=>'2014-10-19')
);
$array2 = array(
array('s'=>'2014-04-05','e'=>'2014-05-02'),
array('s'=>'2014-05-03','e'=>'2014-05-31'),
array('s'=>'2014-06-01','e'=>'2014-10-19')
);
//merge arrays together
$merged_array = array_merge($array1,$array2);
//filter out duplicate start dates
$filtered_array = array();
foreach($merged_array as $k=>$v){
if(!isset($filtered_array[ $v['s'] ] )){
$filtered_array[ $v['s'] ] = $v;
}
//if the end date is before the currently saved end date (for this start date) then use it
if( strtotime($v['e']) < strtotime($filtered_array[ $v['s'] ]['e']) ){
$filtered_array[ $v['s'] ] = $v;
}
}
//reset the array to zero based
$filtered_array = array_values($filtered_array);
//sort the array by start date
$tmp = array();
foreach($filtered_array as $k=>$v){
$tmp[$k] = $v['s'];
}
array_multisort($tmp,SORT_ASC,$filtered_array);
//end date overlap checking
foreach($filtered_array as $k=>$v){
//if the end date is after (or equal to) the "next" start date, then make that end date the "yesterday" of the next start date
if( isset($filtered_array[$k+1]['s']) && strtotime($v['e']) >= strtotime($filtered_array[$k+1]['s']) ){
$yesterday = strtotime($filtered_array[$k+1]['s']) - 1;
$yesterday = date("Y-m-d",$yesterday);
$filtered_array[$k]['e'] = $yesterday;
}
}
echo '<pre>',print_r($filtered_array),'</pre>';
/*
Array
(
[0] => Array
(
[s] => 2014-04-05
[e] => 2014-05-02
)
[1] => Array
(
[s] => 2014-05-03
[e] => 2014-05-31
)
[2] => Array
(
[s] => 2014-06-01
[e] => 2014-06-27
)
[3] => Array
(
[s] => 2014-06-28
[e] => 2014-10-19
)
)
*/
准备中
$arr1 = array(
array('start'=>'2014-04-05', 'end'=> '2014-06-27'),
array('start'=>'2014-06-28', 'end'=> '2014-10-19'),
);
$arr2 = array(
array('start'=>'2014-04-05', 'end'=> '2014-05-02'),
array('start'=>'2014-05-03', 'end'=> '2014-05-31'),
array('start'=>'2014-06-01', 'end'=> '2014-10-21')
);
// merge arrays
$all = array_merge($arr1,$arr2);
// divide start-dates and end-dates into two arrays
$starts = array();
$ends = array();
foreach($all as $date){
$starts[] = $date['start'];
$ends[] = $date['end'];
}
// Remove duplicates and "sort ASC"
$starts = array_unique($starts);
natsort($starts);
$ends = array_unique($ends);
natsort($ends);
echo '<pre>';
var_dump($starts,$ends);
echo '</pre>';
输出
array(4) {
[0]=>
string(10) "2014-04-05"
[3]=>
string(10) "2014-05-03"
[4]=>
string(10) "2014-06-01"
[1]=>
string(10) "2014-06-28"
}
array(5) {
[2]=>
string(10) "2014-05-02"
[3]=>
string(10) "2014-05-31"
[0]=>
string(10) "2014-06-27"
[1]=>
string(10) "2014-10-19"
[4]=>
string(10) "2014-10-21"
}
好的。现在我们需要循环数组 $starts
:对于每个 start
找到最接近的 end
然后更多 start
。这样做:
$ranges = array();
foreach($starts as $start){
$start_time = strtotime($start);
foreach($ends as $end){
$end_time = strtotime($end);
if ($start_time>$end_time) continue;
else{
$ranges[$end] = $start;
break;
}
}
}
// "combine"
$result = array();
foreach($ranges as $end=>$start) {
$result[] = array('start' => $start, 'end' => $end);
}
// print final result
foreach($result as $item){
echo $item['start'].' To '.$item['end'].'<br/>';
}
输出:
2014-04-05 To 2014-05-02
2014-05-03 To 2014-05-31
2014-06-01 To 2014-06-27
2014-06-28 To 2014-10-19
你需要什么。
备注
关于循环中的这一行:
$ranges[$end] = $start;
我们可以有这样的情况:
2014-04-03 To 2014-05-02
2014-04-04 To 2014-05-02
2014-04-05 To 2014-05-02
但这是错误的。只需要最后一个范围 2014-04-05 To 2014-05-02
。和行:
$ranges[$end] = $start;
用相同的键重写值=> 最终将正确 2014-04-05
设置为键 2014-05-02
。
用法:$output = mergeRanges($input);
此方法最初设计用于合并任何类型的数字范围,包括时间戳并支持任何类型的重叠。它在输入中获取一个对象数组,其中包含可自定义的 "from" 和 "to" 键。
/**
* @param $ranges
* @param string $keyFrom
* @param string $keyTo
*
* @return array
*/
function mergeRanges($ranges, $keyFrom = 'from', $keyTo = 'to')
{
// Split from / to values.
$arrayFrom = [];
$arrayTo = [];
foreach ($ranges as $date)
{
$arrayFrom[] = $date->$keyFrom;
$arrayTo[] = $date->$keyTo;
}
// Sort ASC.
natsort($arrayFrom);
natsort($arrayTo);
$ranges = [];
// Iterate over start dates.
foreach ($arrayFrom as $indexFrom => $from)
{
// Get previous entry.
$previousEntry = end($ranges);
// Find associated default "to" value to "from" one.
$to = $arrayTo[$indexFrom];
// If we have a previous entry and "to" is greater than
// current "from" value.
if (isset($previousEntry->to) && $from < $previousEntry->to + 1)
{
// Do nothing if this range is completely covered
// by the previous one.
if ($to > $previousEntry->to)
{
// We just change te "to" value of previous range,
// so we don't create a new entry.
$previousEntry->to = $to;
}
}
else
{
// Create a new range entry.
$ranges[] = (object) [
$keyFrom => $from,
$keyTo => $to,
];
}
}
return $ranges;
}
示例:
$input = [
// One day.
(object) [
'title' => 'One day.',
'from' => 1560816000,
'to' => 1560902399,
],
// Same day, inner period
(object) [
'title' => 'Same day, inner period',
'from' => 1560816000 + 1000,
'to' => 1560902399 - 1000,
],
// Just before midnight
(object) [
'title' => 'Just before midnight',
'from' => 1560816000 - 1000,
'to' => 1560816000 + 1000,
],
// Just after midnight
(object) [
'title' => 'Just after midnight',
'from' => 1560902399 - 1000,
'to' => 1560902399 + 1000,
],
// Other period before
(object) [
'title' => 'Other period before',
'from' => 1560902399 - 100000,
'to' => 1560902399 - 100000 + 5000,
],
// Other period after
(object) [
'title' => 'Other period after',
'from' => 1560816000 + 100000,
'to' => 1560902399 + 100000 + 5000,
],
];
结果:
Array
(
[0] => Array
(
[from] => 2019-06-17 22:13:19
[to] => 2019-06-17 23:36:39
)
[1] => Array
(
[from] => 2019-06-18 01:43:20
[to] => 2019-06-19 02:16:39
)
[2] => Array
(
[from] => 2019-06-19 05:46:40
[to] => 2019-06-20 07:09:59
)
)
第一个:
- 2014-04-05 至 2014-06-27
- 2014-06-28 至 2014-10-19
设置二:
- 2014-04-05 至 2014-05-02
- 2014-05-03 至 2014-05-31
- 2014-06-01 至 2014-10-19
我需要输出的是:
- 2014-04-05 至 2014-05-02
- 2014-05-03 至 2014-05-31
- 2014-06-01 至 2014-06-27
- 2014-06-28 至 2014-10-19
我尝试使用一个函数来检查重叠:
!($lhs['RecordOnset'] > $rhs['RecordOffset'] || $lhs['RecordOffset'] < $rhs['RecordOnset'])
并使用 for 循环检查重叠:
for($i = 1; $i < sizeof($arr1); $i++) {
for($j = 1; $j < sizeof($arr2); $j++) {
$record = $arr1[$i];
if($result = $this->intersects($arr1[$i], $arr2[$j])) {
// $result;
}
}
}
我遇到的问题是当我打破日期范围时,它不会检查循环时创建的新范围。我无法对此使用 SQL,因此我必须想出一个编程解决方案。我尝试了几种不同的方法,包括一些 foreach 循环。
数据以日期格式接收,如数组所示:
$arr1 = array(array('start'=>'04/05/2014', 'end'=> '2014-06-27'), array('start'=>'2014-06-28', 'end'=> '2014-10-19'));
$arr2 = array(array('start'=>'04/05/2014', 'end'=> '2014-05-02'), array('start'=>'2014-05-03', 'end'=> '2014-05-31'),array('start'=>'2014-06-01', 'end'=> '2014-10-19'));
第二对将是一个单独的数组,因为它可能具有相同的键。
非常感谢任何指导或帮助。 PHP 的日期范围在线资源非常有限。
这是我的解决方案:
<?php
$array1 = array(
array('s'=>'2014-04-05','e'=>'2014-06-27'),
array('s'=>'2014-06-28','e'=>'2014-10-19')
);
$array2 = array(
array('s'=>'2014-04-05','e'=>'2014-05-02'),
array('s'=>'2014-05-03','e'=>'2014-05-31'),
array('s'=>'2014-06-01','e'=>'2014-10-19')
);
//merge arrays together
$merged_array = array_merge($array1,$array2);
//filter out duplicate start dates
$filtered_array = array();
foreach($merged_array as $k=>$v){
if(!isset($filtered_array[ $v['s'] ] )){
$filtered_array[ $v['s'] ] = $v;
}
//if the end date is before the currently saved end date (for this start date) then use it
if( strtotime($v['e']) < strtotime($filtered_array[ $v['s'] ]['e']) ){
$filtered_array[ $v['s'] ] = $v;
}
}
//reset the array to zero based
$filtered_array = array_values($filtered_array);
//sort the array by start date
$tmp = array();
foreach($filtered_array as $k=>$v){
$tmp[$k] = $v['s'];
}
array_multisort($tmp,SORT_ASC,$filtered_array);
//end date overlap checking
foreach($filtered_array as $k=>$v){
//if the end date is after (or equal to) the "next" start date, then make that end date the "yesterday" of the next start date
if( isset($filtered_array[$k+1]['s']) && strtotime($v['e']) >= strtotime($filtered_array[$k+1]['s']) ){
$yesterday = strtotime($filtered_array[$k+1]['s']) - 1;
$yesterday = date("Y-m-d",$yesterday);
$filtered_array[$k]['e'] = $yesterday;
}
}
echo '<pre>',print_r($filtered_array),'</pre>';
/*
Array
(
[0] => Array
(
[s] => 2014-04-05
[e] => 2014-05-02
)
[1] => Array
(
[s] => 2014-05-03
[e] => 2014-05-31
)
[2] => Array
(
[s] => 2014-06-01
[e] => 2014-06-27
)
[3] => Array
(
[s] => 2014-06-28
[e] => 2014-10-19
)
)
*/
准备中
$arr1 = array(
array('start'=>'2014-04-05', 'end'=> '2014-06-27'),
array('start'=>'2014-06-28', 'end'=> '2014-10-19'),
);
$arr2 = array(
array('start'=>'2014-04-05', 'end'=> '2014-05-02'),
array('start'=>'2014-05-03', 'end'=> '2014-05-31'),
array('start'=>'2014-06-01', 'end'=> '2014-10-21')
);
// merge arrays
$all = array_merge($arr1,$arr2);
// divide start-dates and end-dates into two arrays
$starts = array();
$ends = array();
foreach($all as $date){
$starts[] = $date['start'];
$ends[] = $date['end'];
}
// Remove duplicates and "sort ASC"
$starts = array_unique($starts);
natsort($starts);
$ends = array_unique($ends);
natsort($ends);
echo '<pre>';
var_dump($starts,$ends);
echo '</pre>';
输出
array(4) {
[0]=>
string(10) "2014-04-05"
[3]=>
string(10) "2014-05-03"
[4]=>
string(10) "2014-06-01"
[1]=>
string(10) "2014-06-28"
}
array(5) {
[2]=>
string(10) "2014-05-02"
[3]=>
string(10) "2014-05-31"
[0]=>
string(10) "2014-06-27"
[1]=>
string(10) "2014-10-19"
[4]=>
string(10) "2014-10-21"
}
好的。现在我们需要循环数组 $starts
:对于每个 start
找到最接近的 end
然后更多 start
。这样做:
$ranges = array();
foreach($starts as $start){
$start_time = strtotime($start);
foreach($ends as $end){
$end_time = strtotime($end);
if ($start_time>$end_time) continue;
else{
$ranges[$end] = $start;
break;
}
}
}
// "combine"
$result = array();
foreach($ranges as $end=>$start) {
$result[] = array('start' => $start, 'end' => $end);
}
// print final result
foreach($result as $item){
echo $item['start'].' To '.$item['end'].'<br/>';
}
输出:
2014-04-05 To 2014-05-02
2014-05-03 To 2014-05-31
2014-06-01 To 2014-06-27
2014-06-28 To 2014-10-19
你需要什么。
备注 关于循环中的这一行:
$ranges[$end] = $start;
我们可以有这样的情况:
2014-04-03 To 2014-05-02
2014-04-04 To 2014-05-02
2014-04-05 To 2014-05-02
但这是错误的。只需要最后一个范围 2014-04-05 To 2014-05-02
。和行:
$ranges[$end] = $start;
用相同的键重写值=> 最终将正确 2014-04-05
设置为键 2014-05-02
。
用法:$output = mergeRanges($input);
此方法最初设计用于合并任何类型的数字范围,包括时间戳并支持任何类型的重叠。它在输入中获取一个对象数组,其中包含可自定义的 "from" 和 "to" 键。
/**
* @param $ranges
* @param string $keyFrom
* @param string $keyTo
*
* @return array
*/
function mergeRanges($ranges, $keyFrom = 'from', $keyTo = 'to')
{
// Split from / to values.
$arrayFrom = [];
$arrayTo = [];
foreach ($ranges as $date)
{
$arrayFrom[] = $date->$keyFrom;
$arrayTo[] = $date->$keyTo;
}
// Sort ASC.
natsort($arrayFrom);
natsort($arrayTo);
$ranges = [];
// Iterate over start dates.
foreach ($arrayFrom as $indexFrom => $from)
{
// Get previous entry.
$previousEntry = end($ranges);
// Find associated default "to" value to "from" one.
$to = $arrayTo[$indexFrom];
// If we have a previous entry and "to" is greater than
// current "from" value.
if (isset($previousEntry->to) && $from < $previousEntry->to + 1)
{
// Do nothing if this range is completely covered
// by the previous one.
if ($to > $previousEntry->to)
{
// We just change te "to" value of previous range,
// so we don't create a new entry.
$previousEntry->to = $to;
}
}
else
{
// Create a new range entry.
$ranges[] = (object) [
$keyFrom => $from,
$keyTo => $to,
];
}
}
return $ranges;
}
示例:
$input = [
// One day.
(object) [
'title' => 'One day.',
'from' => 1560816000,
'to' => 1560902399,
],
// Same day, inner period
(object) [
'title' => 'Same day, inner period',
'from' => 1560816000 + 1000,
'to' => 1560902399 - 1000,
],
// Just before midnight
(object) [
'title' => 'Just before midnight',
'from' => 1560816000 - 1000,
'to' => 1560816000 + 1000,
],
// Just after midnight
(object) [
'title' => 'Just after midnight',
'from' => 1560902399 - 1000,
'to' => 1560902399 + 1000,
],
// Other period before
(object) [
'title' => 'Other period before',
'from' => 1560902399 - 100000,
'to' => 1560902399 - 100000 + 5000,
],
// Other period after
(object) [
'title' => 'Other period after',
'from' => 1560816000 + 100000,
'to' => 1560902399 + 100000 + 5000,
],
];
结果:
Array
(
[0] => Array
(
[from] => 2019-06-17 22:13:19
[to] => 2019-06-17 23:36:39
)
[1] => Array
(
[from] => 2019-06-18 01:43:20
[to] => 2019-06-19 02:16:39
)
[2] => Array
(
[from] => 2019-06-19 05:46:40
[to] => 2019-06-20 07:09:59
)
)