briannesbitt/Carbon · error · InvalidPeriodParameterException

Invalid ISO 8601 specification: {$iso}.

Error message

Invalid ISO 8601 specification: {$iso}.

What it means

Strings passed to CarbonPeriod::create()/createFromISO() are treated as ISO 8601 repetition specs split on '/': an optional leading R<n>/RINF part, then at most interval, start and end segments, each of which must parse as a CarbonInterval or a date (the end may borrow missing parts from the start via addMissingParts()). When a segment cannot fill the next expected slot — too many segments, or one that is neither a valid interval nor a parseable date — the whole spec is rejected (src/Carbon/CarbonPeriod.php:547).

Source

Thrown at src/Carbon/CarbonPeriod.php:547

    {
        $result = [];

        $interval = null;
        $start = null;
        $end = null;
        $dateClass = static::DEFAULT_DATE_CLASS;

        foreach (explode('/', $iso) as $key => $part) {
            if ($key === 0 && preg_match('/^R(\d*|INF)$/', $part, $match)) {
                $parsed = \strlen($match[1]) ? (($match[1] !== 'INF') ? (int) $match[1] : INF) : null;
            } elseif ($interval === null && $parsed = self::makeInterval($part)) {
                $interval = $part;
            } elseif ($start === null && $parsed = $dateClass::make($part)) {
                $start = $part;
            } elseif ($end === null && $parsed = $dateClass::make(static::addMissingParts($start ?? '', $part))) {
                $end = $part;
            } else {
                throw new InvalidPeriodParameterException("Invalid ISO 8601 specification: $iso.");
            }

            $result[] = $parsed;
        }

        return $result;
    }

    /**
     * Add missing parts of the target date from the source date.
     */
    protected static function addMissingParts(string $source, string $target): string
    {
        $pattern = '/'.preg_replace('/\d+/', '[0-9]+', preg_quote($target, '/')).'$/';

        $result = preg_replace($pattern, $target, $source, 1, $count);

        return $count ? $result : $target;

View on GitHub (pinned to b13f05955d)

Solutions

  1. Pass the pieces as separate arguments instead of one ISO string: CarbonPeriod::create($start, 'P1D')->setRecurrences(4)
  2. Fix the spec layout: R<n>/<start>/<interval>/<end>, at most 4 segments, dates before a trailing interval are fine
  3. Validate at the boundary: regex-check the shape, then try CarbonInterval::make()/Carbon::hasFormat() per segment and reject bad input early

Example fix

// before
$period = CarbonPeriod::create('R4/2021-01-01/P1D/nope');

// after
$period = CarbonPeriod::create('2021-01-01', 'P1D')->setRecurrences(4);
// or a corrected spec
$period = CarbonPeriod::create('R4/2021-01-01/P1D');
Defensive patterns

Strategy: validation

Validate before calling

function isValidIsoSpec(string $iso): bool
{
    $parts = explode('/', $iso);
    if (\count($parts) > 4) {
        return false;
    }
    $i = 0;
    if (preg_match('/^R(\d*|INF)$/', $parts[0])) {
        $i = 1;
    }
    for (; $i < \count($parts); $i++) {
        $part = $parts[$i];
        if (CarbonInterval::make($part) === null && Carbon::hasFormat($part, 'Y-m-d\TH:i:s') === false && Carbon::make($part) === null) {
            return false;
        }
    }
    return true;
}

if (!isValidIsoSpec($iso)) {
    throw new InvalidArgumentException("Bad ISO 8601 period spec: $iso");
}

Try / catch

try {
    $period = CarbonPeriod::createFromISO8601String($iso);
} catch (\Carbon\Exceptions\InvalidPeriodParameterException $e) {
    // fall back to explicit construction
    $period = CarbonPeriod::create($defaultStart, 'P1D', $defaultEnd);
}

Prevention

When it happens

Trigger: CarbonPeriod::create('R4/2021-01-01/P1D/extra') (a 5th segment has no slot); CarbonPeriod::create('hello/world') ('world' is neither interval nor date); CarbonPeriod::create('P1D/not-a-date'); US m/d/Y dates like '01/31/2021' injected as a segment producing empty or unparseable parts.

Common situations: User-supplied recurrence strings (API query params, scheduled-task config); concatenating variables where one is empty and yields '//' segments; mis-remembering the spec order and putting the interval last after both dates.

Related errors


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