skip to Main Content

PHP Dates are wonderful things +1 year for example. However I’m getting myself in a bit of a mess. I have three dates spanning two months and I want to make an elegant string
"28, 29 February & 1 March" from an array of 3 dates in the form YYYYMMDD. The exact number of dates is not known, minimum 1 probable max 4

Other than some rather tortuous str_replace manipulation, how do I get from the output of this to my desired format?

NB: no more than 2 months, maybe all in one month.

$dates_array = ['20240228', '20240229', '20240301'];

foreach ($dates_array as $date) :
  $datetimes[]  = new DateTime($date);
endforeach;

// print_r($datetimes);

foreach ($datetimes as $key=>$date) :
  $month = $date->format('F');
  $day = $date->format('j');                                    
  $date_array[]= $day." ".$month." & ";
endforeach;

$date_string =  join( ' ', $date_array ) ;

echo $date_string;

// 28 February & 29 February & 1 March & 

5

Answers


  1. You can try this small script, I advise you to make a nice test to be sure it works with different use cases.

    <?php
    $dates = ['20240228', '20240229','20240301'];
    $countDates = count($dates) - 1;
    $output = [];
    
    foreach ($dates as $date) :
      $datetimes[]  = new DateTime($date);
    endforeach;
    
    foreach ($datetimes as $i => $datetime) :
      $dateOutput = [$datetime->format('j')];
      $isNextMonthTheSame = isset($datetimes[$i+1]) && $datetime->format('m') === $datetimes[$i+1]->format('m');
      if (!$isNextMonthTheSame) {
        $dateOutput []= ' '. $datetime->format('F');  
        $dateOutput []= ' & ';
      } else {
        $dateOutput []= ', ';
      }
      if ($i === $countDates) {
        array_pop($dateOutput);
      }
      $output []= $dateOutput;
    endforeach;
    
    echo implode(array_merge(...$output));
    

    PS: the spread operator (...) has been implemented in PHP 7.4:

    Login or Signup to reply.
  2. As you loop through the $datetimes you’d have to check whether the next value in the list is in the same month or not, in order to know whether or not to including the month name in the formatted output for that date or not.

    This is one way to do that. The broad assumption is that the dates will always be supplied in ascending order, but it does also check if the next date is in the same year, as well as the same month, in case there’s a large gap between dates.

    $dates_array = ['20241203', '20240115', '20240116', '20240228', '20240229', '20240301'];
    
    foreach ($dates_array as $date)
    {
      $datetimes[]  = new DateTime($date);
    }
    
    foreach ($datetimes as $key=>$date) 
    {
      $dateStr = $date->format('j');
      
      //check if there's another entry after this one
      if (isset($datetimes[$key+1]))
      {
          //check the if following entry is in the same month and year as this one
          if ($datetimes[$key+1]->format("ym") == $date->format("ym")) {
              $dateStr .= ",";
          }
          else
          {
              $dateStr .= " ".$date->format("F")." & ";
          }
      }
      else {
          $dateStr .= " ".$date->format("F");
      }
      
      $date_array[]= $dateStr;
    }
    
    $date_string =  join( ' ', $date_array ) ;
    
    echo $date_string;
    

    Live demo: https://3v4l.org/tWrhe

    Login or Signup to reply.
  3. I managed to create this:

    $dates = ['20240228', '20240229', '20240301'];
    
    function formatDates(array $datesInput) {
        foreach ($datesInput as $datesKey => $date) {
            $date  = new DateTime($date);
            $month = $date->format('F');
            $day   = $date->format('j');
            if ($datesKey == 0) {
                $datesOutput = $day;
            } elseif ($currentMonth <> $month) {
                $datesOutput .= " $currentMonth & $day";
            } else {
                $datesOutput .= ", $day";
            }
            $currentMonth = $month;
        }
        return "$datesOutput $month";
    }
    
    echo formatDates($dates);
    

    This returns:

    28, 29 February & 1 March
    

    It is not all that complicated. I did however put everything in a function because I would otherwise have to create quite a few global variables, and that’s seen as bad practice.

    For a demo see: https://3v4l.org/gUWih

    Login or Signup to reply.
  4. One more possible way to approach this, grouping the dates by month in an array first, and joining it all together with implode:

    $dates_array = ['20240228', '20240229', '20240301'];
    
    foreach ($dates_array as $date) :
      $datetimes[]  = new DateTime($date);
    endforeach;
    
    $datesByMonth = [];
    foreach ($datetimes as $key=>$date) :
      $datesByMonth[$date->format('F')][] = $date->format('j');
    endforeach;
    
    foreach($datesByMonth as $month => $dates) {
        $datesByMonth[$month] = implode(', ', $dates) . ' ' . $month;
    }
    
    echo implode(' & ', $datesByMonth);
    

    https://3v4l.org/6snHe

    Obviously won’t work if you had dates for the same month in different years, but I guess that’s not a requirement(?).

    Login or Signup to reply.
  5. Here is another stab at this:

    Iterate over the dates, in each iteration check if previous date is contiguous and belongs to same month.

    $dates_array = ['20240214', '20240228', '20240229', '20240301'];
    
    $groups = [];
    
    foreach( $dates_array as $i => $datestr) {
        $date = DateTimeImmutable::createFromFormat("Ymd", $datestr);
        $prev = $date->modify("-1 day");
        if ($i === 0 || $prev->format("n") !== $date->format("n") || $prev->format("Ymd") !== $dates_array[$i-1]){
            $groups[] = [];
        }
        $groups[array_key_last($groups)][] = $date;
    }
    
    $result = array_map(function($group) {
        return count($group) === 1 ? $group[0]->format("j F") : ($group[0]->format("j") . "-" . $group[array_key_last($group)]->format("j F"));
    }, $groups);
    
    echo implode(", ", $result); // 14 February, 28-29 February, 1 March
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search