briannesbitt/Carbon · error · UnreachableException

Could not calculate period end after iterating 10000 times.

Error message

Could not calculate period end after iterating 10000 times.

What it means

When calculateEnd() cannot derive the end arithmetically (e.g. custom filters exist, so the recurrences alone don't give the last date), it falls back to iterateUntilEnd(), stepping through the whole period date by date for up to END_MAX_ATTEMPTS (10000) iterations. If the true end lies farther than 10000 steps away, the search aborts with UnreachableException instead of running forever (src/Carbon/CarbonPeriod.php:2007).

Source

Thrown at src/Carbon/CarbonPeriod.php:2007

        if ($this->filters === [[static::RECURRENCES_FILTER, null]]) {
            return $this->getStartDate()->avoidMutation()->add(
                $this->getDateInterval()->times(
                    $this->carbonRecurrences - ($this->isStartExcluded() ? 0 : 1),
                ),
            );
        }

        return null;
    }

    private function iterateUntilEnd(): ?CarbonInterface
    {
        $attempts = 0;
        $date = null;

        foreach ($this as $date) {
            if (++$attempts > static::END_MAX_ATTEMPTS) {
                throw new UnreachableException(
                    'Could not calculate period end after iterating '.static::END_MAX_ATTEMPTS.' times.',
                );
            }
        }

        return $date;
    }

    /**
     * Returns true if the current period overlaps the given one (if 1 parameter passed)
     * or the period between 2 dates (if 2 parameters passed).
     *
     * @param CarbonPeriod|\DateTimeInterface|Carbon|CarbonImmutable|string $rangeOrRangeStart
     * @param \DateTimeInterface|Carbon|CarbonImmutable|string|null         $rangeEnd
     *
     * @return bool
     */
    public function overlaps(mixed $rangeOrRangeStart, mixed $rangeEnd = null): bool

View on GitHub (pinned to b13f05955d)

Solutions

  1. Compute the end arithmetically yourself when there are no filters: $start->copy()->add($interval->times($n - 1))
  2. Calculate from an unfiltered copy with the same start/interval/recurrences (that path is O(1)), then keep the filters on the real period
  3. Coarsen the interval or bound recurrences so the step count stays under CarbonPeriod::END_MAX_ATTEMPTS (10000)

Example fix

// before
$end = CarbonPeriod::create('2000-01-01', 'P1D')
    ->setRecurrences(20000)
    ->calculateEnd(); // > 10000 iterations -> UnreachableException

// after — arithmetic, no iteration
$end = Carbon::parse('2000-01-01')->add(days: 20000 - 1);
Defensive patterns

Strategy: fallback

Validate before calling

$steps = $period->getRecurrences() ?? INF;
if (is_finite($steps) && $steps > CarbonPeriod::END_MAX_ATTEMPTS && \count($period->getFilters()) > 1) {
    // calculateEnd() would iterate each step; compute arithmetically instead
    $end = $period->getStartDate()->copy()->add($period->getDateInterval()->times((int) $steps - 1));
} else {
    $end = $period->calculateEnd();
}

Try / catch

try {
    $end = $period->calculateEnd();
} catch (\Carbon\Exceptions\UnreachableException $e) {
    // too many steps: derive the end arithmetically from an unfiltered twin
    $end = CarbonPeriod::create($period->getStartDate(), $period->getDateInterval())
        ->setRecurrences($period->getRecurrences())
        ->calculateEnd();
}

Prevention

When it happens

Trigger: CarbonPeriod::create('2000-01-01', 'P1D')->setRecurrences(20000)->calculateEnd(); a period with custom filters spanning more than 10000 steps; a PT1S interval over several months; daily occurrences across 30+ years with a filter attached.

Common situations: Fine-grained intervals over long ranges; high recurrence counts in schedules; reports computing the 'last occurrence' of a filtered schedule.

Related errors


AI-assisted analysis of briannesbitt/Carbon@b13f05955d (2026-08-17). Data as JSON: /api/errors/5ffae9e583709772. Report an issue: GitHub.