使用 PHP 检测日历事件重叠冲突

Posted

技术标签:

【中文标题】使用 PHP 检测日历事件重叠冲突【英文标题】:Detect calendar event overlap conflicts using PHP 【发布时间】:2016-03-24 13:03:59 【问题描述】:

我正在开发一项功能,用于检查外部事件是否与内部事件发生冲突(在日历应用中)。流程如下:

我的应用程序创建了一系列可能的事件(称为$internalEvents) 我从 Google 日历、iCloud 等日历(称为 $externalEvents)获取外部事件。这些是类型为 busy 的现有事件。 现在我必须检查内部和外部事件是否存在任何冲突。正如您在下面看到的那样,我尝试了一些方法,但这到目前为止还不是正确的或防弹的。

我试图将它尽可能地减少到最低限度。这是数据输入:

$internalEvents = array(
    array(
        "start" => "03/29/2016 12:00:00",
        "end" => "03/29/2016 13:00:00"
    ),
    array(
        "start" => "03/29/2016 12:30:00",
        "end" => "03/29/2016 13:30:00"
    ),
    array(
        "start" => "03/29/2016 13:00:00",
        "end" => "03/29/2016 14:00:00"
    ),
    array(
        "start" => "03/29/2016 13:30:00",
        "end" => "03/29/2016 14:50:00"
    ),
    array(
        "start" => "03/29/2016 14:00:00",
        "end" => "03/29/2016 15:00:00"
    ),
    array(
        "start" => "03/29/2016 14:30:00",
        "end" => "03/29/2016 15:30:00"
    ),
    array(
        "start" => "03/29/2016 15:00:00",
        "end" => "03/29/2016 16:00:00"
    ),
    array(
        "start" => "03/29/2016 15:30:00",
        "end" => "03/29/2016 16:30:00"
    ),
    array(
        "start" => "03/29/2016 16:00:00",
        "end" => "03/29/2016 17:00:00"
    )
);

$externalEvents = array(
    array(
        "start" => "03/29/2016 08:00:00",
        "end" => "03/29/2016 12:00:00",
        "type" => "busy"
    ),
    array(
        "start" => "03/29/2016 15:30:00",
        "end" => "03/29/2016 16:00:00",
        "type" => "busy"
    ),
    array(
        "start" => "03/29/2016 13:30:00",
        "end" => "03/29/2016 14:15:00",
        "type" => "busy"
    )
);

现在我尝试通过比较内部事件和所有外部事件来找出任何类型的冲突:

foreach($internalEvents as $internalEvent) 

    $internalEventStart = new DateTime($internalEvent['start']);
    $internalEventEnd = new DateTime($internalEvent['end']);

    $result = true;

    echo "\nverifying " . $internalEventStart->format('Y-m-d H:i') . " - " . $internalEventEnd->format('Y-m-d H:i') . "\n";

    foreach($externalEvents as $externalEvent) 
        $externalEventStart = new DateTime($externalEvent['start']);
        $externalEventEnd = new DateTime($externalEvent['end']);

        // check if there are conflicts between internal and external events
        if ($internalEventStart >= $externalEventStart && $internalEventStart <= $externalEventEnd) 
            $result = false;
            echo "   problem 1: event is between busy time: " . "\n";
        

        if ($internalEventStart >= $externalEventStart && $internalEventStart <= $externalEventEnd && $externalEventEnd <= $internalEventEnd) 
            $result = false;
            echo "   problem 2: event starts during busy time: " . "\n";
        

        if ($internalEventStart <= $externalEventStart && $externalEventStart <= $internalEventEnd && $internalEventEnd <= $externalEventEnd) 
            $result = false;
            echo "   problem 3: event stops during busy time: " . "\n";
        

        if (($internalEventStart <= $externalEventStart) && ($externalEventStart <= $externalEventEnd) && ($externalEventEnd <= $internalEventEnd)) 
            $result = false;
            echo "   problem 4: event during busy time: " . "\n";
        

        if (($internalEventStart <= $internalEventEnd) && ($internalEventEnd <= $externalEventStart) && ($externalEventStart <= $externalEventEnd)) 
            $result = false;
            echo "   problem 5: event during busy time: " . "\n";
        
    

    if($result) 
        echo "   result: OK\n";
     else 
        echo "   result: NOT OK \n";
    

我正在寻找一种可以发现任何可能的事件重叠冲突的算法。任何提示都非常感谢。

运行代码可以在here(IDEone.com)找到。

【问题讨论】:

您的实际代码有效吗?我已经增加了输出时间,这对我来说似乎不连贯。检查我在这个pastebin 上的结果:我用 模棱两可的结果和 标记了不正确的结果。相反,如果结果正确,您能否更好地解释碰撞标准?我打算如果有重叠就会发生碰撞......让我知道。 @fusion3k 它曾经使用一组事件,但是当我更改事件时,我意识到它实际上不起作用。这就是我需要社区帮助的原因。您的发现似乎是正确的。 【参考方案1】:

当两个事件发生冲突时?请参阅此架构:

                              ----E----                 CS/EE   CE/ES
                        --N--                             <       <
                                        --N--             >       >
                           --C--                          <       >
                                     --C--                <       >
                                --C--                     <       >
                             -----C-----                  <       >
                    ··················································
                    E  = Main Event
                    N  = Not Collide Event
                    C  = Collide Event
                    CS = Compare Event Start
                    EE = Main Event End
                    CE = Compare Event End
                    ES = Main Event Start

如你所见,只有当事件 C 的开始在事件 E 的结束之前并且事件 E 的结束在事件 C 的开始之后才会发生碰撞。了解这一点有助于找到一种有效且简短的方法来解决比较事件。

关于代码,初步说明:您在代码中多次比较之前转换了 ISO 8601 中的日期,那么为什么不为此创建一个函数呢?

function eventsToDate( $row )

    $retval = array( 'start' => date_create( $row['start'] )->format('Y-m-d H:i:s'), 'end' => date_create( $row['end'] )->format('Y-m-d H:i:s') );
    $retval['print'] = sprintf( '%s-%s', substr( $retval['start'],-8,5 ), substr( $retval['end'],-8,5 ) );
    return $retval;

此函数以您的格式返回一个带有“开始”和“结束”的关联数组。我添加了第三个键“打印”,以在调试期间使用。请注意,在打印中我只考虑小时:分钟(数组样本中的所有日期都来自同一天),但比较是在完整日期上进行的。您可以省略此“打印”键或将其替换为首选输出格式。

您执行两个嵌套的foreach,并为每个循环重新计算日期格式。使用您的数组样本,您调用 DateTime/DateTime::format 36 次。通过创建一个包含所有转换的 $externalEvents 的临时数组,我们可以将这些调用减少到 12 个。因此,在开始 foreach() 循环之前,我们使用带有上述自定义函数的 array_map$externalEvents 数组来创建一个具有格式化日期的数组:

$externalDates = array_map( 'eventsToDate', $externalEvents );

然后,我们在$internalEvents 上启动主foreach() 循环:

foreach( $internalEvents as $internalEvent )

    $internalDates = eventsToDate( $internalEvent );

    echo $internalDates['print'] . php_EOL;
    $result = True;
    foreach( $externalDates as $externalDate )
    

此时,我们比较日期。如上所述,我们将 start 与 end 和 end 与 start 进行比较。为了简化下一个比较,我们使用strcmp,一个 php 函数,“如果 str1 小于 str2,则返回 0,如果它们相等,则返回 0”:

        $startCmp = strcmp( $internalDates['start'], $externalDate['end'] );
        $endCmp   = strcmp( $internalDates['end'],   $externalDate['start']   );

现在,比较:

        if( $startCmp<0 && $endCmp>0 )
        
            $result = False;
            echo "           $externalDate['print'] COLLIDE\n";
        
        else
        
            echo "           $externalDate['print'] OK\n";
        
    

最后,我们可以打印结果:

    echo "Result: " . ( $result ? 'OK' : 'NOT OK') . "\n\n";

eval.in demo

注意:通过上面的比较,第一个$internalEvent我们得到如下结果:

12:00-13:00
           08:00-12:00 OK
           15:30-16:00 OK
           13:30-14:15 OK
Result: OK

相反,如果你想要这个结果:

12:00-13:00
           08:00-12:00 COLLIDE
           15:30-16:00 OK
           13:30-14:15 OK
Result: NOT OK

您必须将上述if 条件替换为:

         if( $startCmp<=0 && $endCmp>=0 )

上面的代码可以工作,如果你想了解更多关于碰撞类型的细节,你可以在if条件中测试其他的开始/结束组合。


替代方案:返回碰撞事件

如果——而不是打印结果——你想捕捉碰撞事件,你可以用array_filter替换嵌套的foreach()

$result = array_filter
(
    $externalDates, 
    function( $row ) use( $internalDates )
    
        $startCmp = strcmp( $internalDates['start'], $row['end'] );
        $endCmp   = strcmp( $internalDates['end'],   $row['start']   );
        return( $startCmp<0 && $endCmp>0 );
    
);

此时,碰撞事件在数组$result 中。很明显,就是数组是空的,没有碰撞。


阅读更多关于strcmp() 阅读更多关于array_map() 阅读更多关于array_filter()

【讨论】:

非常感谢fusion3k!我需要一些时间来仔细研究它,但它看起来很棒!我会回来找你的

以上是关于使用 PHP 检测日历事件重叠冲突的主要内容,如果未能解决你的问题,请参考以下文章

当我通过点击创建 UIView 时,如何不让它们重叠?

停止视图在相对布局中水平重叠

面临禁用时日历图标在另一个日历弹出窗口上重叠的问题

阻止WPF控件在MouseMove事件上重叠

在relativelayout中布局,两个控件重叠,放在后面得viewpager点击滑动事件都无效了

zTree节点重叠或者遮挡