Search code examples
phpalgorithmdatecalendar

Check if date/period is inside another date(s)/period - is more elegant solution possible?


I was wondering if there is an elegant solution to this programming problem.

There is a date of an event that meets the following conditions:

1) it can be 'from' - 'to'
2) OR it can be 'from' 
   AND (repeating at defined intervals n times OR indefinitely). 
   The interwals can be day/week/month or year

There is a participant on that event. (S)He may have a period/date attached, when (s)he is unavailable.

1) the date can be 'from' - 'to'
2) the date can be only 'from'
3) the date can be only 'to'

I need to check if participant's 'unavailable' date - if present - overlaps in any way with event date.

For now it's a hotchpotch or conditions. Code's not finished, I'll have to add more. Language - PHP. I'm usding DateTime PHP object for comparing dates.

Can this be improved somehow or conditions are the only way to go in this case? I don't need an exact code, just an idea.

    if($event->recurring === '0') {
        // Non recurring event
        if($eventEndValid && $userFromValid && $userToValid) { $user->available = ($userFrom < $eventStart && $userTo < $eventStart) || ($userFrom >= $eventEnd && $userTo > $eventEnd); } 
        else if($userFromValid && $userToValid) { $user->available = ($userFrom < $eventStart) && ($userTo <= $eventStart); }
        else if(!$eventEndValid && !$userToValid) { $user->available = false; }
        else if(!$eventEndValid && !$userFromValid) { $user->available = $userTo <= $eventStart; } 
        else if($eventEndValid && !$userToValid) { $user->available = $userFrom >= $eventEnd; } 
        else if($eventEndValid && !$userFromValid) { $user->available = $userTo <= $eventStart; }
    } else {
        $dIntStr = 'P';
        switch($event->recurring_frequency) {
            case '1':
                $dIntStr .= '1D';
                break;
            case '2':
                $dIntStr .= '1W';
                break;
            case '3':
                $dIntStr .= '1M';
                break;
            case '4':
                $dIntStr .= '1Y';
                break;
        }
        // Case 1
        if(!$userToValid) { $user->available = false; }
        if((!$userFromValid && $userToValid)) { $user->available = $userTo <= $eventStart; }

        // Case 2 - number of intervals is unlimited
        if($event->recurring_frequency === '0') {
            if($userToValid && $userTo <= $eventStart) { $user->available = true; } 
            else if($userTo > $eventStart) {
                // If unavailability is between the intervals then true
                $user->available = true;
            } 
            else { $user->available = false; }
        }


        // Case 2 - number of intervals is limited
        $dateInterval = DateInterval($dIntStr);
        if($event->recurring_frequency === '0') {

        }


    }

Solution

  • You usually can use functions to shorten the code. However i'd think a class is more suitable for this:

    class DateRange{
      public $from, $to;
    
      public function inBetween($outer){
        return $this->from >= $outer->from && $this->to <= $outer->to;
      }
    
      function __construct($from, $to){
        if($a = $from->getTimestamp() == $b = $to->getTimestamp()){
          throw new \Exception('A period cannot be the same time');
        } else {
          if($a < $b){
            $this->from = $a;
            $this->to = $b;
          } else {
            $this->from = $b;
            $this->to = $a;
          }
        }
      }
    }
    
    $today    = new Datetime();
    $lastyear = (new Datetime())->modify('-1 year');
    $from     = (new Datetime())->modify('-9 month');
    
    $outer    = new DateRange($lastyear, $today);
    $inner    = new DateRange($from, $today);
    
    if($inner->inBetween($outer)){
      echo 'it is';
    } else {
      echo 'it isnt';
    }
    

    You can add more methods like inBetween() as you see fit this way.