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

62 Views Asked by At

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:

  • 2024-06-02
  • 2025-06-02
  • 2026-06-02
  • 2027-06-02

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.

2

There are 2 best solutions below

0
Olivier On BEST ANSWER

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.

1
Don't Panic On

The syntax you're using looks a bit complicated to me, and if you're having trouble with it, maybe something simpler will work. The docs show pretty simple syntax for CarbonPeriod:

$startDate = '2022-06-02';
$endDate   = '2028-04-01';
$interval  = '1 year';
$number    = 4;

$count     = 0;
$results   = [];
$period    = new CarbonPeriod($startDate, $interval, $endDate);
foreach ($period as $date) {
    if ($date->isPast()) {
        continue;
    }

    $results[] = $date;

    $count++;
    if ($count >= $number) {
        break;
    }
}

print_r($results);

And produces your expected output:

[date] => 2024-06-02 00:00:00.000000
[date] => 2025-06-02 00:00:00.000000
[date] => 2026-06-02 00:00:00.000000
[date] => 2027-06-02 00:00:00.000000