douzong6649 2016-03-24 13:03
浏览 23
已采纳

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

I am working on a feature that checks if external events collide with internal events (in a calendar app). The process looks like this:

  • My application creates an array of possible events (called $internalEvents)
  • I source external events from calendars such as Google Calendar, iCloud, etc. (called $externalEvents). These are existing events with the type busy.
  • Now I have to check if there is any kind of conflict regarding internal and external events. I tried something as you can see below but this is by far not correct or bulletproof.

I tried to reduce it to the minimum as much as possible. This is the data input:

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

Now I try to find any kind of conflict by comparing the internal event to all external events:

foreach($internalEvents as $internalEvent) {

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

    $result = true;

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

    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: " . "
";
        }

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

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

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

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

    if($result) {
        echo "   result: OK
";
    } else {
        echo "   result: NOT OK 
";
    }
}

I am looking for an algorithm that can find any possible event overlap conflict. Any hint is highly appreciated.

The running code can be found here (IDEone.com).

  • 写回答

1条回答 默认 最新

  • doushengyou2617 2016-03-24 16:03
    关注

    When two events collide? See this schema:

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

    As you can see, there is collision only when the start of event C is before the end of event E and the end of event E is after the start of event C. Knowing this helps to find an efficient and short method to compare events.

    About the code, a preliminary note: you convert the dates in ISO 8601 before comparing it multiple times in your code, so why do not create a function for this?

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

    This function returns an associative array with 'start' and 'end' in your format. I have added a third key, 'print', to use during debug. Note that in the print I consider only hours:minutes (all the dates in your array sample are from same day), but the comparison is made on complete dates. You can omit this 'print' key or replace it with preferred output format.

    You execute two nested foreach, and for each loop recalculate the date format. With your array samples, you call DateTime/DateTime::format 36 times. By creating a temporary array with all converted $externalEvents, we can reduce these calls to 12. So, before starting foreach() loop, we use array_map with above custom function and $externalEvents array to create an array with formatted dates:

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

    Then, we start the main foreach() loop on $internalEvents:

    foreach( $internalEvents as $internalEvent )
    {
        $internalDates = eventsToDate( $internalEvent );
    
        echo $internalDates['print'] . PHP_EOL;
        $result = True;
        foreach( $externalDates as $externalDate )
        {
    

    At this point, we compare dates. As mentioned above, we compare start with end and end with start. To simplify next comparison, we use strcmp, a php function that “returns < 0 if str1 is less than str2, > 0 if str1 is greater than str2, and 0 if they are equal”:

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

    Now, the comparison:

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

    And, finally, we can print the result:

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

    <kbd>eval.in demo</kbd>

    Note: with above comparison, with first $internalEvent we obtain following result:

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

    Instead, if you want this result:

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

    You have to replace above if condition with this:

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

    Above code will work, if you want more details about kind of collision, you can test other combinations of start/end inside if condition.


    Alternative: Return Colliding Events

    If — instead of printing results — you want catch colliding events, you can replace nested foreach() with array_filter in this way:

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

    At this point, colliding events are in array $result. Obviously, is the array is empty, there are not collisions.


    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 易语言把MYSQL数据库中的数据添加至组合框
  • ¥20 求数据集和代码#有偿答复
  • ¥15 关于下拉菜单选项关联的问题
  • ¥20 java-OJ-健康体检
  • ¥15 rs485的上拉下拉,不会对a-b<-200mv有影响吗,就是接受时,对判断逻辑0有影响吗
  • ¥15 使用phpstudy在云服务器上搭建个人网站
  • ¥15 应该如何判断含间隙的曲柄摇杆机构,轴与轴承是否发生了碰撞?
  • ¥15 vue3+express部署到nginx
  • ¥20 搭建pt1000三线制高精度测温电路
  • ¥15 使用Jdk8自带的算法,和Jdk11自带的加密结果会一样吗,不一样的话有什么解决方案,Jdk不能升级的情况