briannesbitt/Carbon · error · InvalidPeriodDateException

Invalid start date.

Error message

Invalid start date.

What it means

setStartDate() converts its input through the period's date class ::make(); values that cannot be parsed into a date make ::make() return null, which triggers InvalidPeriodDateException('Invalid start date.') (src/Carbon/CarbonPeriod.php:1369). Infinite markers accepted by isInfiniteDate() (INF / end-of-time style values) are exempt.

Source

Thrown at src/Carbon/CarbonPeriod.php:1369

        $self->carbonRecurrences = $recurrences === INF ? INF : (int) $recurrences;

        return self::addFilterOrHandleChangedParameters($self, static::RECURRENCES_FILTER);
    }

    /**
     * Change the period start date.
     *
     * @param DateTime|DateTimeInterface|string $date
     * @param bool|null                         $inclusive
     *
     * @throws InvalidPeriodDateException
     *
     * @return static
     */
    public function setStartDate(mixed $date, ?bool $inclusive = null): static
    {
        if (!$this->isInfiniteDate($date) && !($date = ([$this->dateClass, 'make'])($date, $this->timezone))) {
            throw new InvalidPeriodDateException('Invalid start date.');
        }

        $self = $this->copyIfImmutable();
        $self->startDate = $date;

        if ($inclusive !== null) {
            $self = $self->toggleOptions(static::EXCLUDE_START_DATE, !$inclusive);
        }

        $self->syncNativePeriod();

        return $self;
    }

    /**
     * Change the period end date.
     *
     * @param DateTime|DateTimeInterface|string|null $date

View on GitHub (pinned to b13f05955d)

Solutions

  1. Validate or parse before: Carbon::hasFormat($date, 'Y-m-d') then pass, or wrap with Carbon::parse() and handle its failure
  2. Pass a DateTimeInterface you already constructed
  3. Reject empty/invalid input at the system boundary instead of letting the period constructor see it

Example fix

// before
$period->setStartDate($request->input('from')); // 'whenever'

// after
$from = $request->input('from');
if (is_string($from) && Carbon::hasFormat($from, 'Y-m-d')) {
    $period->setStartDate(Carbon::parse($from));
} else {
    abort(422, 'from must be a Y-m-d date');
}
Defensive patterns

Strategy: validation

Validate before calling

if (\is_string($date) && Carbon::make($date) === null) {
    throw new InvalidArgumentException('Unparseable start date: '.$date);
}
$period->setStartDate($date);

Try / catch

try {
    $period->setStartDate($input);
} catch (\Carbon\Exceptions\InvalidPeriodDateException $e) {
    throw new ValidationException('start: must be a valid date', 0, $e);
}

Prevention

When it happens

Trigger: ->setStartDate('not a date'); ->setStartDate('31/02/2021'); ->setStartDate(''); ->setStartDate($garbage) where $garbage is an unparseable string or unrelated value.

Common situations: Free-text date fields forwarded without validation; localized or day-first formats Carbon does not guess (d/m/Y); empty request parameters coerced to empty strings.

Related errors


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