phpdatephp-carbon

Using Carbon to generate multiple dates with an interval in a range, but only ones in the future


I need to generate multiple dates between two dates, based on an interval (eg. weekly, every 2 weeks, monthly, ...), but I only need the ones in the future.

For that I'm using the Carbon 2.72.3 library and I came up with the following code:

$interval = CarbonInterval::week();
$startDate = Carbon::create(2024, 3, 17);
$endDate = Carbon::create(2024, 4, 1);
$amount = 4;

$period = CarbonPeriod::interval($interval)
    ->setStartDate($startDate)
    ->addFilter(fn(Carbon $carbon) => $carbon->isFuture(), 'isFuture')
    ->addFilter(fn(Carbon $carbon) => !$endDate || $carbon->isBefore($endDate))
    ->setRecurrences($amount);

dd($period->toArray());

Unfortunately, this just works sometimes (meaning: Not with all dates / intervals) and I can't tell exactly under what conditions it works and when it doesn't work.

With the dates above, Carbon throws an Carbon\Exceptions\UnreachableException with the message Could not find next valid date.. If I reduce the amount to 1, it works, but only returns 2024-03-24 as a date (which is then expected, but doesn't solve my issue).

With other data, such as this, it works as expected: It returns 4 dates according to the interval in relation to the start date, but only the ones in the future.

$interval = CarbonInterval::month();
$startDate = Carbon::create(2022, 6, 2);
$endDate = null;
$amount = 4;

If I set the $endDate = Carbon::create(2024, 4, 1);, it also stops working. I suspect that it sometimes happens when it cannot generate the $amount of dates. But this doesn't seem to be the case always, as the following setup should be able to generate at least 4 dates, though the same exception is thrown:

$interval = CarbonInterval::year();
$startDate = Carbon::create(2022, 6, 2);
$endDate = Carbon::create(2028, 4, 1);
$amount = 4;

Expected dates:

Though in this case it only works if I set the end date to 2029-04-01, which doesn't make sense to me as the last date is already in 2027.


Solution

  • To set the end date, Carbon provides the setEndDate() method, so your code should be like this:

    $period = CarbonPeriod::interval($interval)
        ->setStartDate($startDate)
        ->addFilter(fn(Carbon $carbon) => $carbon->isFuture(), 'isFuture')
        ->setEndDate($endDate)
        ->setRecurrences($amount);
    

    Note that setEndDate() accepts a null value.