PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Calculation\Exception
#NUM!
#NUM!
Error message
#NUM!
What it means
Thrown by SecurityValidations::validateSecurityPeriod() (Securities/SecurityValidations.php:16-21) when settlement >= maturity on the Excel date serial numbers produced by validateSettlementDate()/validateMaturityDate(). Nearly every securities function calls it: PRICE, PRICEDISC, PRICEMAT, RECEIVED, YIELD, YIELDDISC, DISC, INTRATE, ACCRINT, ACCRINTM. The comparison happens after date coercion, so string dates, DateTime objects and serials are all fine - only the ordering matters; equality is also rejected.
Source
Thrown at src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php:19
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class SecurityValidations extends FinancialValidations
{
public static function validateIssueDate(mixed $issue): float
{
return self::validateDate($issue);
}
public static function validateSecurityPeriod(mixed $settlement, mixed $maturity): void
{
if ($settlement >= $maturity) {
throw new Exception(ExcelError::NAN());
}
}
public static function validateRedemption(mixed $redemption): float
{
$redemption = self::validateFloat($redemption);
if ($redemption <= 0.0) {
throw new Exception(ExcelError::NAN());
}
return $redemption;
}
}
View on GitHub (pinned to 65b080eef4)
Solutions
- Check settlement < maturity before evaluation, comparing them as dates, not strings
- Fix swapped arguments in the formula or the direct function call
- Use unambiguous date inputs: DateTime objects, Excel serials, or 'YYYY-MM-DD' strings
- If reached via $cell->getCalculatedValue(), catch Calculation\Exception or set suppressFormulaErrors to receive '#NUM!' as a value
Example fix
// before
$price = Price::price('2030-01-01', '2024-01-01', 0.06, 0.05, 100, 2, 0); // settlement AFTER maturity -> '#NUM!'
// after: enforce the period invariant before pricing
if (strtotime($settlement) >= strtotime($maturity)) {
throw new InvalidArgumentException('settlement date must precede maturity date');
}
$price = Price::price($settlement, $maturity, 0.06, 0.05, 100, 2, 0); Defensive patterns
Strategy: try-catch
Validate before calling
if (strtotime($settlement) >= strtotime($maturity)) {
throw new InvalidArgumentException('settlement date must be strictly before maturity date');
} Type guard
function isValidSecurityPeriod(mixed $settlement, mixed $maturity): bool
{
$s = is_numeric($settlement) ? (float) $settlement : (float) strtotime((string) $settlement);
$m = is_numeric($maturity) ? (float) $maturity : (float) strtotime((string) $maturity);
return $s < $m;
} Try / catch
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcException;
try {
$value = $cell->getCalculatedValue(); // cell formula -> '#NUM!' surfaces as an exception
} catch (CalcException $e) {
// re-inspect the formula's settlement/maturity arguments
} Prevention
- Canonicalize all dates to DateTime or ISO strings before building formulas
- Reject ambiguous 'd/m/y' vs 'm/d/y' input at the boundary
- Remember equality is invalid too - settlement must be strictly before maturity
When it happens
Trigger: Swapped settlement/maturity arguments (the classic case); same-day settlement and maturity; a maturity parsed from 'dd/mm/yy' vs 'mm/dd/yy' so it lands before settlement for days <= 12.
Common situations: Locale-ambiguous dates imported from CSV; template columns users fill in the wrong order; boundary tests with settlement == maturity; midnight-trimmed DateTime values making the dates equal.
Related errors
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/6598e0f963c65fc0.
Report an issue: GitHub.