Search code examples
phpsymfonydatetimephp-carbondateinterval

Find dates between 2 dates with custom interval and without overflow


I have a customisable DateInterval object, along with a customisable start and end date. I want to find the dates between the start and end using the interval. I am using Carbon to try and help with this.

Here's the problem: I the interval is 𝑥 months but the start date is > 28 I cannot control the overflow using CarbonPeriod.

Here is the code I am testing with:

$di = CarbonInterval::create('P1M');
$start = Carbon::parse('31 january 2020')->startOfDay();
$end = Carbon::parse('01 april 2020')->startOfDay();

$period = CarbonPeriod::create($start, $di, $end);

$items = [];
foreach ($period as $item) {
    $items[] = $item;
}

I want the above to result in

2020-01-31
2020-02-29
2020-03-30

But I get

2020-01-31
2020-03-02
2020-04-02

Remember, the DateInterval is customisable (or I would just use Carbon::addMonthNoOverflow()).

Can anyone please help with how I achieve what I need to, above?


Solution

  • After much digging, and realising that @Jakumi is correct, I have just come up with a solution using Carbon after asking the same question here.

    The reason why DatePeriod() does not work is that it is always reliant on the previous date in the loop. That is why you get stuck at 29/28 for february and then repeating throughout the rest of the loop.

    Here is my solution:

    $endDate = CarbonImmutable::parse('10 april 2020')->startOfDay();
    $startDate = CarbonImmutable::parse('31 january 2020')->startOfDay();
    $interval = CarbonInterval::create('P1M'); // only works for number of months, not composite intervals
    
    
    $workingDate = $startDate->copy();
    
    for ($i = 1; $workingDate <= $endDate; $i = $i + $interval->m) {
        echo = $workingDate->format('Y-m-d') . "\n";
        $workingDate = $startDate->addMonthsNoOverflow($i);
    }
    

    I am involved in a bit of a code-off with a contributor of the Carbon codebase. If he finds a better solution then I will update my answer. For now, this works.