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
- Iterate with foreach instead of seek counters - foreach cannot leave the window
- Refresh the upper bound before seeking: construct a fresh $sheet->getRowIterator($start, $sheet->getHighestRow())
- 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
- Use foreach instead of manual seek counters - it cannot leave the window
- Rebuild iterators after appending rows (endRow is a snapshot)
- Keep loop bounds and iterator bounds from the same source variable
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
- Column $column is out of range ({$this->startColumnIndex} -
- Invalid R1C1-format Cell Reference, Value out of range
- In "IterateOnlyExistingCells" mode and Cell does not exist
- Row $row is out of range ({$this->startRow} - {$this->endRow
- Start column ({$startColumn}) is beyond highest column ({$th
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/362d56b4b27cb970.
Report an issue: GitHub.