PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Calculation\Exception

#NUM!

#NUM!

Error message

#NUM!

What it means

RowIterator::seek() validates the row against the iterator's own [startRow, endRow] window, where endRow defaults to the worksheet's highest row captured at construction. Unlike resetStart() it never consults the live sheet, so even a row that now exists can be rejected when it lies beyond the frozen end bound, and any row before startRow is always rejected.

Source

Thrown at src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php:106

    }

    /**
     * Convert year from multiple formats to int.
     */
    private static function getYear(mixed $year, int $baseYear): int
    {
        if ($year === null) {
            $year = 0;
        } elseif (is_scalar($year)) {
            $year = StringHelper::testStringAsNumeric((string) $year);
        }
        if (!is_numeric($year)) {
            throw new Exception(ExcelError::VALUE());
        }
        $year = (int) $year;

        if ($year < ($baseYear - 1900)) {
            throw new Exception(ExcelError::NAN());
        }
        if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
            throw new Exception(ExcelError::NAN());
        }

        if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
            $year += 1900;
        }

        return (int) $year;
    }

    /**
     * Convert month from multiple formats to int.
     */
    private static function getMonth(mixed $month): int
    {
        if (is_string($month)) {

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Iterate with foreach instead of seek counters - foreach cannot leave the window
  2. Refresh the upper bound before seeking: construct a fresh $sheet->getRowIterator($start, $sheet->getHighestRow())
  3. Bounds-check your row number against the same start/end values you built the iterator with

Example fix

// before
$it = $sheet->getRowIterator(1, 10);
$it->seek(11); // 11 > 10 -> exception

// after
$it = $sheet->getRowIterator(1, $sheet->getHighestRow());
foreach ($it as $row) {
    // safe navigation
}
Defensive patterns

Strategy: validation

Validate before calling

$endRow = $sheet->getHighestRow(); // or the bound you constructed the iterator with
if ($row < $startRow || $row > $endRow) {
    // out of the iterator window; extend the range or skip
}

Try / catch

try {
    $rowIterator->seek($row);
} catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
    // row outside [startRow, endRow]; rebuild iterator with a fresh bound
}

Prevention

When it happens

Trigger: Manual navigation $rowIterator->seek($n) with $n outside [startRow, endRow]; write-back loops calling seek($current + 1) that walk past the end; using an iterator constructed before rows were appended (stale endRow).

Common situations: for loops bounded by count($data) while the iterator was built against fewer rows; mixing 0-based counters with 1-based row numbers; reusing iterators across data-refresh cycles.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/362d56b4b27cb970. Report an issue: GitHub.