7

I have arrays of dates in Y-m-d format that may be any combination of ten set dates that are zero, one, or more days apart.

e.g. Here is a possible full set:

[
 '2011-01-01',
 '2011-01-02',
 '2011-01-03',
 '2011-01-04',
 '2011-01-05',
 '2011-01-06',
 '2011-01-07',
 '2011-01-08',
 '2011-01-09',
 '2011-01-10'
]

The arrays that are created from that set can be any combination of consecutive or gapped dates.

I currently have them printing pretty much as they're returned. e.g. here's another possible data set:

[
 '2011-01-02',
 '2011-01-03',
 '2011-01-04',
 '2011-01-08',
]

(What's actually printed is more like "Friday, Jan. 2...", but we'll stick with the simple date string.)

I'd like to condense it so that if there are three or more consecutive days, those become a range, e.g the above example would become:

[
 '2011-01-02 to 2011年01月04日',
 '2011-01-08',
]

which would eventually become:

[
 'Sunday, Jan. 2 - Tuesday, Jan. 4',
 'Saturday Jan. 8',
]

Is there a way to loop through and consolidate consecutive dates as ranged date strings?

mickmackusa
49.3k13 gold badges98 silver badges165 bronze badges
asked Dec 11, 2011 at 2:32
2
  • all the dates provided in this question are in consecutive, what exactly are you looking for? Commented Dec 11, 2011 at 2:37
  • The list of possible dates are consecutive. The actual dates returned can be any combination from that list of dates (see my "possible result" example). Commented Dec 11, 2011 at 21:01

2 Answers 2

14

Bit of a quick answer so sorry about the lack of implementation but assuming you are using 5.3 and the dates are ordered chronologically, you could convert each date to a DateTime object (if they aren't already) and then iterate over the array using DateTime::diff() to generate a DateInterval object which you could use to compare the current date in the iteration with the last. You could group your consecutive dates into sub arrays and use shift() and pop() to get the first and last days in that sub array.

EDIT

I had a think about this. Pretty rough and ready implementation follows, but it should work:

// assuming a chronologically
// ordered array of DateTime objects 
$dates = array(
 new DateTime('2010-12-30'), 
 new DateTime('2011-01-01'), 
 new DateTime('2011-01-02'), 
 new DateTime('2011-01-03'), 
 new DateTime('2011-01-06'), 
 new DateTime('2011-01-07'), 
 new DateTime('2011-01-10'),
);
// process the array
$lastDate = null;
$ranges = array();
$currentRange = array();
foreach ($dates as $date) { 
 if (null === $lastDate) {
 $currentRange[] = $date;
 } else {
 // get the DateInterval object
 $interval = $date->diff($lastDate);
 // DateInterval has properties for 
 // days, weeks. months etc. You should 
 // implement some more robust conditions here to 
 // make sure all you're not getting false matches
 // for diffs like a month and a day, a year and 
 // a day and so on...
 if ($interval->days === 1) {
 // add this date to the current range
 $currentRange[] = $date; 
 } else {
 // store the old range and start anew
 $ranges[] = $currentRange;
 $currentRange = array($date);
 }
 }
 // end of iteration... 
 // this date is now the last date 
 $lastDate = $date;
}
// messy... 
$ranges[] = $currentRange;
// print dates
foreach ($ranges as $range) {
 // there'll always be one array element, so 
 // shift that off and create a string from the date object 
 $startDate = array_shift($range);
 $str = sprintf('%s', $startDate->format('D j M'));
 // if there are still elements in $range
 // then this is a range. pop off the last 
 // element, do the same as above and concatenate
 if (count($range)) {
 $endDate = array_pop($range);
 $str .= sprintf(' to %s', $endDate->format('D j M'));
 }
 echo "<p>$str</p>";
}

Outputs:

Thu 30 Dec
Sat 1 Jan to Mon 3 Jan
Thu 6 Jan to Fri 7 Jan
Mon 10 Jan
answered Dec 11, 2011 at 2:46
Sign up to request clarification or add additional context in comments.

2 Comments

@Darragh any idea on how to include double dates in one range? My setup is about shifts, sometimes there are two shifts in one day. So in my date array, one date could appear two times and should be included in one daterange, but with your code is marked as a separate day. Thanks in advance!
Hi @maartenmachiels - I guess the super-fast answer to this might be to base your interval comparisons on hours rather than (or even in addition to) days. Currently the code only compares on days - see the line if ($interval->days === 1) { // etc.
0

This task can /should be accomplished with only one loop. I will leverage a custom function to handle the formatting and conditionally-presented range expressions. There is never a reason to "temp"orarily hold more than two dates when determining range because if there are three or more dates in a single group, only the first and last will be used.

The script will honour the asker's commented requirement of handling duplicate dates, but ultimately it is assumed that the input data is guaranteed to be pre-sorted in an ascending direction.

Breakdown:

# loop each date 
 # declare the date as a Date Time class object
 # if a group has been started already...
 # if the new date is more than one day ahead of the last encountered date...
 # format the grouped data and save to the result array 
 # clear the grouping array and add the new date as the first date in the new group.
 # otherwise, add the new date to new group (because it belongs there) as the end of range value. 
 # otherwise, start a group and use the new date as the starting value.
# after iterating all of the input data, if the temp array is holding any data, format the grouped data and save to the result array.

Code: (Demo)

$dates = [
 '2010-12-30',
 '2011-01-01',
 '2011-01-02',
 '2011-01-03',
 '2011-01-06',
 '2011-01-06',
 '2011-01-07',
 '2011-01-10',
];
function formatRange(object $start, ?object $end = null): string
{
 $last = $end ?? $start;
 return sprintf(
 '%s%s',
 $start->format('l j M'),
 $last === $start
 ? ''
 : ' to ' . $last->format('l j M')
 );
}
$result = [];
foreach ($dates as $date) {
 $obj = new Datetime($date);
 if (isset($temp['start'])) {
 if ($obj->diff($temp['end'] ?? $temp['start'])->days > 1) {
 $result[] = formatRange(...$temp);
 $temp = ['start' => $obj];
 } else {
 $temp['end'] = $obj;
 }
 } else {
 $temp['start'] = $obj;
 }
}
if (isset($temp)) {
 $result[] = formatRange(...$temp);
}
var_export($result);

Output:

array (
 0 => 'Thursday 30 Dec',
 1 => 'Saturday 1 Jan to Monday 3 Jan',
 2 => 'Thursday 6 Jan to Friday 7 Jan',
 3 => 'Monday 10 Jan',
)

Other answers of mine which perform a related consolidation of grouped date/time data:

answered Sep 30, 2023 at 21:54

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.