Updated June 2026. Tested on Laravel 13 and PHP 8.4.
A reader asked me how to take a flat list of dates and group them into ranges, where each range is an unbroken run of consecutive days. Wherever a day is missing, the run breaks and a new range starts. This comes up with things like leave records or booking periods.
Say you have these dates, with two gaps in them.
$days = [
'2026-10-15', '2026-10-16', '2026-10-17', '2026-10-18',
// 2026-10-19 missing
'2026-10-20', '2026-10-21', '2026-10-22',
// 2026-10-23 missing
'2026-10-24', '2026-10-25',
];
The result we want is three ranges, broken at each gap.
[ ['2026-10-15', '2026-10-18'],
['2026-10-20', '2026-10-22'],
['2026-10-24', '2026-10-25'] ]
The function
Sort the dates, then walk through them. Each time the gap between a date and the one before it is more than a day, close the current range and start a new one. Using Carbon to compare days keeps it readable.
use Carbon\Carbon;
function splitIntoRanges(array $dates): array
{
sort($dates);
$ranges = [];
$start = $dates[0];
$prev = $dates[0];
foreach (array_slice($dates, 1) as $date) {
// more than one day since the previous date means a gap
if (Carbon::parse($prev)->diffInDays($date) > 1) {
$ranges[] = [$start, $prev];
$start = $date;
}
$prev = $date;
}
$ranges[] = [$start, $prev]; // close the final range
return $ranges;
}
$ranges = splitIntoRanges($days);
How it works
$start holds the beginning of the current run, and $prev is the last date we looked at. For each date, if it is more than one day after $prev, the run has broken, so we record the range from $start to $prev and begin a new run at the current date. After the loop, we record whatever range was still open. Sorting first means the dates do not have to arrive in order.
That is the whole thing: one pass, breaking at each gap. Handy whenever you need to collapse scattered days into tidy ranges. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.