Code Quickie: Prettify a List of Dates

Most of the time, writing code can be very repetitive. There are only so many ways to handle something. However, a few times per project, I’m tasked to come up with a new solution to a challenge. After accepting and conquering the challenge, I sometimes come up with code that I’m very proud to share because of its elegance, simplicity, and/or novelty.

This particular challenge involved a report that produced a list of dates as parts of its results. When I ran a few sample reports, I noticed that I could potentially get a nigh unreadable list of numerous, consecutive dates. I decided to try and turn that horrible list into something beautiful by condensing three or more consecutive dates into a range.

Looking at the image below, you can see data from a sample report that lists a lot of consecutive dates. However, not all of the dates are consecutive. We need a function that can compare sorted dates, looking for adjacent values. Our function will then condense any three consecutive dates into a range, showing only the first and last value separated by a hyphen.

Listing Individual Dates Without Ranges

Pure ugliness!

Creating A Date Range From Consecutive Dates

The key to solving this problem is using PHP’s DateTime class to format and compare two dates. Using the diff method, I could check for adjacent dates.

To start, we need an array of dates, and we need to know the format of each date. With that data, we can use the following function to create our “pretty” listing of dates with ranges.

<?php
function datesArrayToDateRangeString(array $dates) {
      $new_dates = array();
      $last_date = NULL;
      $dates_string = '';

      foreach ( $dates as $index => $date ) {
        if ( array_values($dates)[0] == $date ) {
          $new_dates[] = $date;
        } else if ( end($dates) == $date ) {
          $new_dates[] = $date;
        } else {
          $datetime1 = DateTime::createFromFormat('n/j/Y', $dates[$index-1]);
          $datetime2 = DateTime::createFromFormat('n/j/Y', $date);
          $datetime3 = DateTime::createFromFormat('n/j/Y', $dates[$index+1]);

          $backward_interval = $datetime1->diff($datetime2);
          $forward_interval = $datetime2->diff($datetime3);

          if ( $backward_interval->days === 1  && $forward_interval->days === 1) {
            $new_dates[] = '-';
          } else {
            $new_dates[] = $date;
          }
        }
      }

      foreach ( $new_dates as $index => $date ) {
        if ( $date == '-' ) {
          if ( $last_date == '-' ) unset($new_dates[$index]);
        } 

        $last_date = $date;
      }

      foreach ( $new_dates as $index => $date ) {
        if ( $date == '-' ) {
          $dates_string = substr($dates_string, 0, -2).$date;
        } else {
          $dates_string .= $date.', ';
        }
      }
      
      return substr($dates_string, 0, -2);
    }
?>

Looking at the snippet above, you can see that the DateTime->diff method really does the computing here.1Using the DateTime->diff method is much better than parsing the string manually. The former can determine if days are adjacent even if they are in different months. Without a lot more code, the latter cannot. It decides if a date is adjacent to another. If so, we replace that value in the array with a hyphen. After washing the resultant array through a few more loops, we end up with something like the screenshot below.

Condensing a list of individual dates to a list of ranges.

Much better, huh?

Happy Coding!

About

Hubby, Daddy, Web Developer.

References   [ + ]

1. Using the DateTime->diff method is much better than parsing the string manually. The former can determine if days are adjacent even if they are in different months. Without a lot more code, the latter cannot.

Add Your Thoughts

Your email address will not be published. Required fields are marked *