I'm dealing with events that can either take place on a single day (ex. 5/20) or multiple days (ex. 8/25, 8/26, 8/27, 9/3).
Given, for example, a 4-day event taking place on 8/25, 8/26, 8/27, and 9/3, I'd like to echo this:
Aug 25-27, Sep 3
I'd like the code to:
This is easy to do with only a single-day event using date()
formatting, but is it possible to intelligently produce formatting like this using multiple dates when necessary?
I made a function that should output the required string based on an array of DateTime
objects. I placed some inline comments to indicate what is happening at a given time in the function.
function produceDateString(array $dates): string
{
// sort the dates
sort($dates);
// create an array of arrays that contain ranges of consecutive days
$ranges = [];
$currentRange = [];
foreach ($dates as $date) {
if(empty($currentRange) || consecutive(end($currentRange), $date)) {
$currentRange[] = $date;
} else {
$ranges[] = $currentRange;
$currentRange = [$date];
}
}
$ranges[] = $currentRange;
// create the output string
$output = '';
$previous = null;
foreach ($ranges as $range) {
// add a comma between each range
if (!empty($output)) {
$output .= ', ';
}
// the long format should be used on the first occurrence of the loop
// or when the month of first date in the range doesn't match
// the month of the last date in the previous range
$format = $previous === null || end($previous)->format('m') !== reset($range)->format('m')
? 'M. j'
: 'j';
// the output differes when there are 1 or multiple dates in a range
if (count($range) > 1) {
// the output differs when the end and start are in the sane month
$output .= sameMonth(reset($range), end($range))
? reset($range)->format($format).'-'.end($range)->format('j')
: reset($range)->format('M. j').'-'.end($range)->format('M. j');
} else {
$output .= reset($range)->format($format);
}
$previous = $range;
}
return $output;
}
function consecutive(DateTime $t1, DateTime $t2): bool
{
$t1->setTime(0, 0, 0, 0);
$t2->setTime(0, 0, 0, 0);
return(abs($t2->getTimestamp() - $t1->getTimestamp()) < 87000);
}
function sameMonth(DateTime $t1, DateTime $t2): bool
{
return $t1->format('Y-m') === $t2->format('Y-m');
}
I made a small 3v4l to show you how it works exactly. Don't hesitate to ask if you might have any questions about how this works.