Search code examples
phparraysdategroupinghyphenation

Group and reduce a flat array of dates into a delimited string with hyphenated day ranges


I am trying to come up with the most efficient and best way to accomplish this somewhat of a complex situation. I know that I could build this solution using probably around 5 if else statements, maybe more - however there must be a better way to accomplish what I want to.

So here's what I am trying to do. I have an events page on my website, and what I want to do is display the dates in a minimalistic way when possible. What I mean is the following:

Say I have 3 dates: May 5, May 6, May 7. I want to display it as: May 5 - 7.

However, there will be situations where the dates may be: May 5, May 7. In this case I would like to display it as: May 5 & 7.

However, there may also be situations where the dates may be: May 25, June 2. In this case I would like to display it as: May 25 & June 2.

However! There also may be situations where the dates may be: May 25, May 26, June 2. In this case it should display as: May 25 - 26 & June 2

Of course, there could just be a single date as well. But one other thing, it could be possible that there could be more than 3 dates as well, so it would be nice if it could work regardless of how many dates there are (IE loop through an array).

I know that we are suppose to make an attempt and show some code to debug, however I don't even know where to start with this, if this is too much for someone to put together - just giving me an idea of how to do something like this efficiently would be a huge help.


Solution

  •   //input data: sorted list of dates
      $dates = array('May 5','May 6','May 7','May 30','Jun 2','Jun 3','Dec 11','Dec 12','Dec 14');      
      array_push($dates,false); //add an extra value so the last range gets printed
    
      //initialize min & previous date as first date
      $min_date = array_shift($dates);
      $prev_date = $min_date; 
      $counter = 0; //keep count of # of days between min and max
    
      $formatted_dates = array();
    
      foreach($dates as $date) {
        //if the difference in number of days between current date and min date
        //is greater than counted number of days then we capture the current range
        //and start a new one by resetting $min_date to $date and $counter to 0
        if(!$date || ($counter + 1) < diff_in_days($min_date,$date)) {
          if($counter == 0) { //format for 1 date
            $formatted_dates[] = $min_date;
          } 
          elseif($counter == 1) { //format for 2 dates
            $formatted_dates[] = "$min_date & $prev_date";
          }
          elseif($counter > 1) { //format for > 2 dates
            $formatted_dates[] = "$min_date - $prev_date";
          }
    
          $counter = 0;
          $min_date = $date;
        } 
        else {
          $counter++;
        }
    
        $prev_date = $date;
      }
    
      //may also want to verify that neither formatted date contains an '&'
      //so you don't end up with "May 11 & May 12 & June 1 & June 2" which may be confusing
      if(count($formatted_dates) == 2) {
        print implode(' & ',$formatted_dates);
      }
      else {
        print implode("\n",$formatted_dates);
      }
    
      function diff_in_days($day1,$day2) {
        $datetime1 = new DateTime($day1);
        $datetime2 = new DateTime($day2);
        $interval = $datetime1->diff($datetime2);
        $ret = (int) $interval->format('%a');
        return $ret;
      }
    

    Output

    May 5 - May 7
    May 30
    Jun 2 & Jun 3
    Dec 11 & Dec 12
    Dec 14