briannesbitt/Carbon · error · UnreachableException

Could not calculate period end without either explicit end o

Error message

Could not calculate period end without either explicit end or recurrences.
If you're looking for a forever-period, use ->setRecurrences(INF).

What it means

calculateEnd() returns the explicit end date or computes one. Computation needs either an end date or a recurrences bound; with neither there is no definable last date, so UnreachableException is thrown with a hint to use ->setRecurrences(INF) for deliberate forever-periods (src/Carbon/CarbonPeriod.php:1975).

Source

Thrown at src/Carbon/CarbonPeriod.php:1975

        }

        if ($this->dateInterval->isEmpty()) {
            return $this->getStartDate($rounding);
        }

        $date = $this->getEndFromRecurrences() ?? $this->iterateUntilEnd();

        if ($date && $rounding) {
            $date = $date->avoidMutation()->round($this->getDateInterval(), $rounding);
        }

        return $date;
    }

    private function getEndFromRecurrences(): ?CarbonInterface
    {
        if ($this->carbonRecurrences === null) {
            throw new UnreachableException(
                "Could not calculate period end without either explicit end or recurrences.\n".
                "If you're looking for a forever-period, use ->setRecurrences(INF).",
            );
        }

        if ($this->carbonRecurrences === INF) {
            $start = $this->getStartDate();

            return $start < $start->avoidMutation()->add($this->getDateInterval())
                ? CarbonImmutable::endOfTime()
                : CarbonImmutable::startOfTime();
        }

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

View on GitHub (pinned to b13f05955d)

Solutions

  1. Bound the period first: ->setRecurrences($n) or ->setEndDate($date)
  2. For an intentional infinite period: ->setRecurrences(INF) (the end then resolves to start/end of time)
  3. Branch explicitly: if ($period->getEndDate() === null && $period->getRecurrences() === null) handle the unbounded case before calling

Example fix

// before
$end = CarbonPeriod::create('2021-01-01')->calculateEnd();

// after
$end = CarbonPeriod::create('2021-01-01')
    ->setRecurrences(30)
    ->calculateEnd();
Defensive patterns

Strategy: validation

Validate before calling

if ($period->getEndDate() === null && $period->getRecurrences() === null) {
    throw new InvalidArgumentException('Period needs an end or recurrences before calculating its end');
}
$end = $period->calculateEnd();

Prevention

When it happens

Trigger: CarbonPeriod::create('2021-01-01')->calculateEnd(); any unbounded period (start only) fed to an API that needs the last date, e.g. ->getIncludedEndDate() flows or range comparison helpers that call calculateEnd().

Common situations: Generic reporting code calling calculateEnd() on periods built from optional user filters where the 'until' filter may be absent; forgetting create($start) alone is unbounded; migrating code that assumed an implicit end.

Related errors


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