briannesbitt/Carbon · error · InvalidArgumentException

You must specify $end or $recurrences but not both

Error message

You must specify $end or $recurrences but not both

What it means

The cycle constructors CarbonPeriod::monthly()/quarterly()/yearly() bound the period either by an explicit $end date or by a count of $recurrences. Supplying both is ambiguous (which bound wins?), so getStartAndEndForCyclePeriod() rejects the combination immediately (src/Carbon/CarbonPeriod.php:582).

Source

Thrown at src/Carbon/CarbonPeriod.php:582

        return $count ? $result : $target;
    }

    private static function getStartAndEndForCyclePeriod(
        DateTimeInterface|string|int|null $start = null,
        DateTimeInterface|string|int|null $end = null,
        ?int $recurrences = null,
        ?int $anchorDay = null,
        OverflowMode $mode = OverflowMode::AnchorDay,
    ): array {
        if ($anchorDay !== null && $mode !== OverflowMode::AnchorDay) {
            throw new InvalidArgumentException(
                '$anchorDay parameter must not be set for $mode OverflowMode::'.$mode->name,
            );
        }

        if ($end !== null && $recurrences !== null) {
            throw new InvalidArgumentException(
                'You must specify $end or $recurrences but not both',
            );
        }

        if (\is_int($start)) {
            $start = CarbonImmutable::createFromTimestamp($start);
        } elseif (\is_string($start)) {
            $start = CarbonImmutable::parse($start);
        }

        $start ??= CarbonImmutable::now();

        if (\is_int($end)) {
            $end = CarbonImmutable::createFromTimestamp($end);
        }

        return [$start, $end];
    }

View on GitHub (pinned to b13f05955d)

Solutions

  1. Pass only one bound: either the end date or the recurrence count
  2. Make the config unambiguous: when both are present, pick one (e.g. prefer the end date) before calling
  3. Build incrementally with individually safe setters: CarbonPeriod::monthly($start)->setRecurrences(12) or ->setEndDate($end)

Example fix

// before
$period = CarbonPeriod::monthly('2021-01-31', '2021-12-31', 12);

// after — choose one bound
$period = CarbonPeriod::monthly('2021-01-31', '2021-12-31');
// or
$period = CarbonPeriod::monthly('2021-01-31', null, 12);
Defensive patterns

Strategy: validation

Validate before calling

if ($end !== null && $recurrences !== null) {
    throw new InvalidArgumentException('Pass either $end or $recurrences, not both');
}
$period = CarbonPeriod::monthly($start, $end, $recurrences);

Prevention

When it happens

Trigger: CarbonPeriod::monthly('2021-01-31', '2021-12-31', 12); CarbonPeriod::yearly($start, $end, 5) — any call where both $end and $recurrences are non-null.

Common situations: Config-driven schedules where both 'end_date' and 'occurrences' keys are set and both get forwarded; copy-pasting between create() and monthly() whose argument positions differ.

Related errors


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