Extract ACCRINT() and ACCRINTM() Financial functions into their own class (#1956)

* Extract ACCRINT() and ACCRINTM() Financial functions into their own class
Implement additional validations, with additional unit tests
Add support for the new calculation method argument for ACCRINT()
* Additional tests for Amortization functions
This commit is contained in:
Mark Baker 2021-03-26 22:49:16 +01:00 committed by GitHub
parent d36f9d5a23
commit c699d144e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 625 additions and 370 deletions

View File

@ -233,12 +233,12 @@ class Calculation
], ],
'ACCRINT' => [ 'ACCRINT' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'ACCRINT'], 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'],
'argumentCount' => '4-7', 'argumentCount' => '4-8',
], ],
'ACCRINTM' => [ 'ACCRINTM' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'ACCRINTM'], 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'],
'argumentCount' => '3-5', 'argumentCount' => '3-5',
], ],
'ACOS' => [ 'ACOS' => [

View File

@ -0,0 +1,27 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
trait BaseValidations
{
protected static function validateFloat($value): float
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
}
return (float) $value;
}
protected static function validateInt($value): int
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
}
return (int) floor($value);
}
}

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering; namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class BesselI class BesselI
{ {
use BaseValidations;
/** /**
* BESSELI. * BESSELI.
* *
@ -32,20 +35,22 @@ class BesselI
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord); $ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
if ((is_numeric($x)) && (is_numeric($ord))) { try {
$ord = (int) floor($ord); $x = self::validateFloat($x);
$ord = self::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) { if ($ord < 0) {
return Functions::NAN(); return Functions::NAN();
} }
$fResult = self::calculate((float) $x, $ord); $fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? Functions::NAN() : $fResult; return (is_nan($fResult)) ? Functions::NAN() : $fResult;
} }
return Functions::VALUE();
}
private static function calculate(float $x, int $ord): float private static function calculate(float $x, int $ord): float
{ {
// special cases // special cases

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering; namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class BesselJ class BesselJ
{ {
use BaseValidations;
/** /**
* BESSELJ. * BESSELJ.
* *
@ -30,20 +33,22 @@ class BesselJ
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
$ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord);
if ((is_numeric($x)) && (is_numeric($ord))) { try {
$ord = (int) floor($ord); $x = self::validateFloat($x);
$ord = self::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if ($ord < 0) { if ($ord < 0) {
return Functions::NAN(); return Functions::NAN();
} }
$fResult = self::calculate((float) $x, $ord); $fResult = self::calculate($x, $ord);
return (is_nan($fResult)) ? Functions::NAN() : $fResult; return (is_nan($fResult)) ? Functions::NAN() : $fResult;
} }
return Functions::VALUE();
}
private static function calculate(float $x, int $ord): float private static function calculate(float $x, int $ord): float
{ {
// special cases // special cases

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering; namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class BesselK class BesselK
{ {
use BaseValidations;
/** /**
* BESSELK. * BESSELK.
* *
@ -28,9 +31,13 @@ class BesselK
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord); $ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
if ((is_numeric($x)) && (is_numeric($ord))) { try {
$ord = (int) floor($ord); $x = self::validateFloat($x);
$x = (float) $x; $ord = self::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) { if (($ord < 0) || ($x <= 0.0)) {
return Functions::NAN(); return Functions::NAN();
} }
@ -40,13 +47,10 @@ class BesselK
return (is_nan($fBk)) ? Functions::NAN() : $fBk; return (is_nan($fBk)) ? Functions::NAN() : $fBk;
} }
return Functions::VALUE(); private static function calculate(float $x, int $ord): float
}
private static function calculate($x, $ord): float
{ {
// special cases // special cases
switch (floor($ord)) { switch ($ord) {
case 0: case 0:
return self::besselK0($x); return self::besselK0($x);
case 1: case 1:

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering; namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class BesselY class BesselY
{ {
use BaseValidations;
/** /**
* BESSELY. * BESSELY.
* *
@ -27,9 +30,13 @@ class BesselY
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord); $ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
if ((is_numeric($x)) && (is_numeric($ord))) { try {
$ord = (int) floor($ord); $x = self::validateFloat($x);
$x = (float) $x; $ord = self::validateInt($ord);
} catch (Exception $e) {
return $e->getMessage();
}
if (($ord < 0) || ($x <= 0.0)) { if (($ord < 0) || ($x <= 0.0)) {
return Functions::NAN(); return Functions::NAN();
} }
@ -39,13 +46,10 @@ class BesselY
return (is_nan($fBy)) ? Functions::NAN() : $fBy; return (is_nan($fBy)) ? Functions::NAN() : $fBy;
} }
return Functions::VALUE(); private static function calculate(float $x, int $ord): float
}
private static function calculate($x, $ord): float
{ {
// special cases // special cases
switch (floor($ord)) { switch ($ord) {
case 0: case 0:
return self::besselY0($x); return self::besselY0($x);
case 1: case 1:

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering; namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Compare class Compare
{ {
use BaseValidations;
/** /**
* DELTA. * DELTA.
* *
@ -27,8 +30,11 @@ class Compare
$a = Functions::flattenSingleValue($a); $a = Functions::flattenSingleValue($a);
$b = Functions::flattenSingleValue($b); $b = Functions::flattenSingleValue($b);
if (!is_numeric($a) || !is_numeric($b)) { try {
return Functions::VALUE(); $a = self::validateFloat($a);
$b = self::validateFloat($b);
} catch (Exception $e) {
return $e->getMessage();
} }
return (int) ($a == $b); return (int) ($a == $b);
@ -54,8 +60,11 @@ class Compare
$number = Functions::flattenSingleValue($number); $number = Functions::flattenSingleValue($number);
$step = Functions::flattenSingleValue($step); $step = Functions::flattenSingleValue($step);
if (!is_numeric($number) || !is_numeric($step)) { try {
return Functions::VALUE(); $number = self::validateFloat($number);
$step = self::validateFloat($step);
} catch (Exception $e) {
return $e->getMessage();
} }
return (int) ($number >= $step); return (int) ($number >= $step);

View File

@ -4,10 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
use Complex\Complex as ComplexObject; use Complex\Complex as ComplexObject;
use Complex\Exception as ComplexException; use Complex\Exception as ComplexException;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Complex class Complex
{ {
use BaseValidations;
/** /**
* COMPLEX. * COMPLEX.
* *
@ -29,10 +32,14 @@ class Complex
$imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary); $imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary);
$suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix); $suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix);
if ( try {
((is_numeric($realNumber)) && (is_numeric($imaginary))) && $realNumber = self::validateFloat($realNumber);
(($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) $imaginary = self::validateFloat($imaginary);
) { } catch (Exception $e) {
return $e->getMessage();
}
if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
$complex = new ComplexObject($realNumber, $imaginary, $suffix); $complex = new ComplexObject($realNumber, $imaginary, $suffix);
return (string) $complex; return (string) $complex;

View File

@ -21,8 +21,8 @@ class Erf
* Excel Function: * Excel Function:
* ERF(lower[,upper]) * ERF(lower[,upper])
* *
* @param float $lower lower bound for integrating ERF * @param mixed (float) $lower lower bound for integrating ERF
* @param float $upper upper bound for integrating ERF. * @param mixed (float) $upper upper bound for integrating ERF.
* If omitted, ERF integrates between zero and lower_limit * If omitted, ERF integrates between zero and lower_limit
* *
* @return float|string * @return float|string
@ -52,7 +52,7 @@ class Erf
* Excel Function: * Excel Function:
* ERF.PRECISE(limit) * ERF.PRECISE(limit)
* *
* @param float $limit bound for integrating ERF * @param mixed (float) $limit bound for integrating ERF
* *
* @return float|string * @return float|string
*/ */

View File

@ -35,7 +35,12 @@ class Financial
* Returns the accrued interest for a security that pays periodic interest. * Returns the accrued interest for a security that pays periodic interest.
* *
* Excel Function: * Excel Function:
* ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis]) * ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method])
*
* @Deprecated 1.18.0
*
* @see Financial\Securities\AccruedInterest::periodic()
* Use the periodic() method in the Financial\Securities\AccruedInterest class instead
* *
* @param mixed $issue the security's issue date * @param mixed $issue the security's issue date
* @param mixed $firstinterest the security's first interest date * @param mixed $firstinterest the security's first interest date
@ -45,7 +50,7 @@ class Financial
* @param mixed (float) $rate the security's annual coupon rate * @param mixed (float) $rate the security's annual coupon rate
* @param mixed (float) $par The security's par value. * @param mixed (float) $par The security's par value.
* If you omit par, ACCRINT uses $1,000. * If you omit par, ACCRINT uses $1,000.
* @param mixed (int) $frequency the number of coupon payments per year. * @param mixed (int) $frequency The number of coupon payments per year.
* Valid frequency values are: * Valid frequency values are:
* 1 Annual * 1 Annual
* 2 Semi-Annual * 2 Semi-Annual
@ -56,36 +61,32 @@ class Financial
* 2 Actual/360 * 2 Actual/360
* 3 Actual/365 * 3 Actual/365
* 4 European 30/360 * 4 European 30/360
* @param mixed (bool) $calcMethod
* If true, use Issue to Settlement
* If false, use FirstInterest to Settlement
* *
* @return float|string Result, or a string containing an error * @return float|string Result, or a string containing an error
*/ */
public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0) public static function ACCRINT(
{ $issue,
$issue = Functions::flattenSingleValue($issue); $firstinterest,
$firstinterest = Functions::flattenSingleValue($firstinterest); $settlement,
$settlement = Functions::flattenSingleValue($settlement); $rate,
$rate = Functions::flattenSingleValue($rate); $par = 1000,
$par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); $frequency = 1,
$frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency); $basis = 0,
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); $calcMethod = true
) {
// Validate return Securities\AccruedInterest::periodic(
if ((is_numeric($rate)) && (is_numeric($par))) { $issue,
$rate = (float) $rate; $firstinterest,
$par = (float) $par; $settlement,
if (($rate <= 0) || ($par <= 0)) { $rate,
return Functions::NAN(); $par,
} $frequency,
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); $basis,
if (!is_numeric($daysBetweenIssueAndSettlement)) { $calcMethod
// return date error );
return $daysBetweenIssueAndSettlement;
}
return $par * $rate * $daysBetweenIssueAndSettlement;
}
return Functions::VALUE();
} }
/** /**
@ -96,6 +97,11 @@ class Financial
* Excel Function: * Excel Function:
* ACCRINTM(issue,settlement,rate[,par[,basis]]) * ACCRINTM(issue,settlement,rate[,par[,basis]])
* *
* @Deprecated 1.18.0
*
* @see Financial\Securities\AccruedInterest::atMaturity()
* Use the atMaturity() method in the Financial\Securities\AccruedInterest class instead
*
* @param mixed $issue The security's issue date * @param mixed $issue The security's issue date
* @param mixed $settlement The security's settlement (or maturity) date * @param mixed $settlement The security's settlement (or maturity) date
* @param mixed (float) $rate The security's annual coupon rate * @param mixed (float) $rate The security's annual coupon rate
@ -112,29 +118,7 @@ class Financial
*/ */
public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0) public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0)
{ {
$issue = Functions::flattenSingleValue($issue); return Securities\AccruedInterest::atMaturity($issue, $settlement, $rate, $par, $basis);
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$par = ($par === null) ? 1000 : Functions::flattenSingleValue($par);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
// Validate
if ((is_numeric($rate)) && (is_numeric($par))) {
$rate = (float) $rate;
$par = (float) $par;
if (($rate <= 0) || ($par <= 0)) {
return Functions::NAN();
}
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
return $par * $rate * $daysBetweenIssueAndSettlement;
}
return Functions::VALUE();
} }
/** /**

View File

@ -3,10 +3,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial; namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Amortization class Amortization
{ {
use BaseValidations;
/** /**
* AMORDEGRC. * AMORDEGRC.
* *
@ -47,6 +50,18 @@ class Amortization
$rate = Functions::flattenSingleValue($rate); $rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
try {
$cost = self::validateFloat($cost);
$purchased = self::validateDate($purchased);
$firstPeriod = self::validateDate($firstPeriod);
$salvage = self::validateFloat($salvage);
$period = self::validateFloat($period);
$rate = self::validateFloat($rate);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$yearFrac = DateTimeExcel\YearFrac::funcYearFrac($purchased, $firstPeriod, $basis); $yearFrac = DateTimeExcel\YearFrac::funcYearFrac($purchased, $firstPeriod, $basis);
if (is_string($yearFrac)) { if (is_string($yearFrac)) {
return $yearFrac; return $yearFrac;
@ -113,6 +128,18 @@ class Amortization
$rate = Functions::flattenSingleValue($rate); $rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
try {
$cost = self::validateFloat($cost);
$purchased = self::validateDate($purchased);
$firstPeriod = self::validateDate($firstPeriod);
$salvage = self::validateFloat($salvage);
$period = self::validateFloat($period);
$rate = self::validateFloat($rate);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$fOneRate = $cost * $rate; $fOneRate = $cost * $rate;
$fCostDelta = $cost - $salvage; $fCostDelta = $cost - $salvage;
// Note, quirky variation for leap years on the YEARFRAC for this function // Note, quirky variation for leap years on the YEARFRAC for this function

View File

@ -0,0 +1,72 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Constants as SecuritiesConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
trait BaseValidations
{
protected static function validateDate($date)
{
return DateTimeExcel\Helpers::getDateValue($date);
}
protected static function validateSettlementDate($settlement)
{
return self::validateDate($settlement);
}
protected static function validateMaturityDate($maturity)
{
return self::validateDate($maturity);
}
protected static function validateFloat($value): float
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
}
return (float) $value;
}
protected static function validateInt($value): int
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE());
}
return (int) floor($value);
}
protected static function validateFrequency($frequency): int
{
$frequency = self::validateInt($frequency);
if (
($frequency !== SecuritiesConstants::FREQUENCY_ANNUAL) &&
($frequency !== SecuritiesConstants::FREQUENCY_SEMI_ANNUAL) &&
($frequency !== SecuritiesConstants::FREQUENCY_QUARTERLY)
) {
throw new Exception(Functions::NAN());
}
return $frequency;
}
protected static function validateBasis($basis): int
{
if (!is_numeric($basis)) {
throw new Exception(Functions::VALUE());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(Functions::NAN());
}
return $basis;
}
}

View File

@ -2,7 +2,6 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial; namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception; use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
@ -10,6 +9,8 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
class Coupons class Coupons
{ {
use BaseValidations;
public const FREQUENCY_ANNUAL = 1; public const FREQUENCY_ANNUAL = 1;
public const FREQUENCY_SEMI_ANNUAL = 2; public const FREQUENCY_SEMI_ANNUAL = 2;
public const FREQUENCY_QUARTERLY = 4; public const FREQUENCY_QUARTERLY = 4;
@ -62,6 +63,9 @@ class Coupons
} }
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis); $daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis);
if (is_string($daysPerYear)) {
return Functions::VALUE();
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
if ($basis === Helpers::DAYS_PER_YEAR_ACTUAL) { if ($basis === Helpers::DAYS_PER_YEAR_ACTUAL) {
@ -185,7 +189,7 @@ class Coupons
if ($basis === Helpers::DAYS_PER_YEAR_NASD) { if ($basis === Helpers::DAYS_PER_YEAR_NASD) {
$settlementDate = Date::excelToDateTimeObject($settlement); $settlementDate = Date::excelToDateTimeObject($settlement);
$settlementEoM = self::isLastDayOfMonth($settlementDate); $settlementEoM = Helpers::isLastDayOfMonth($settlementDate);
if ($settlementEoM) { if ($settlementEoM) {
++$settlement; ++$settlement;
} }
@ -340,26 +344,12 @@ class Coupons
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
} }
/**
* isLastDayOfMonth.
*
* Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
*
* @param DateTime $testDate The date for testing
*
* @return bool
*/
private static function isLastDayOfMonth(DateTime $testDate)
{
return $testDate->format('d') === $testDate->format('t');
}
private static function couponFirstPeriodDate($settlement, $maturity, int $frequency, $next) private static function couponFirstPeriodDate($settlement, $maturity, int $frequency, $next)
{ {
$months = 12 / $frequency; $months = 12 / $frequency;
$result = Date::excelToDateTimeObject($maturity); $result = Date::excelToDateTimeObject($maturity);
$maturityEoM = self::isLastDayOfMonth($result); $maturityEoM = Helpers::isLastDayOfMonth($result);
while ($settlement < Date::PHPToExcel($result)) { while ($settlement < Date::PHPToExcel($result)) {
$result->modify('-' . $months . ' months'); $result->modify('-' . $months . ' months');
@ -375,62 +365,10 @@ class Coupons
return Date::PHPToExcel($result); return Date::PHPToExcel($result);
} }
private static function validateInputDate($date)
{
$date = DateTimeExcel\Helpers::getDateValue($date);
if (is_string($date)) {
throw new Exception(Functions::VALUE());
}
return $date;
}
private static function validateSettlementDate($settlement)
{
return self::validateInputDate($settlement);
}
private static function validateMaturityDate($maturity)
{
return self::validateInputDate($maturity);
}
private static function validateCouponPeriod($settlement, $maturity): void private static function validateCouponPeriod($settlement, $maturity): void
{ {
if ($settlement >= $maturity) { if ($settlement >= $maturity) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
} }
private static function validateFrequency($frequency): int
{
if (!is_numeric($frequency)) {
throw new Exception(Functions::NAN());
}
$frequency = (int) $frequency;
if (
($frequency !== self::FREQUENCY_ANNUAL) &&
($frequency !== self::FREQUENCY_SEMI_ANNUAL) &&
($frequency !== self::FREQUENCY_QUARTERLY)
) {
throw new Exception(Functions::NAN());
}
return $frequency;
}
private static function validateBasis($basis): int
{
if (!is_numeric($basis)) {
throw new Exception(Functions::NAN());
}
$basis = (int) $basis;
if (($basis < 0) || ($basis > 4)) {
throw new Exception(Functions::NAN());
}
return $basis;
}
} }

View File

@ -7,6 +7,8 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Depreciation class Depreciation
{ {
use BaseValidations;
/** /**
* DB. * DB.
* *
@ -203,11 +205,7 @@ class Depreciation
private static function validateCost($cost, bool $negativeValueAllowed = false): float private static function validateCost($cost, bool $negativeValueAllowed = false): float
{ {
if (!is_numeric($cost)) { $cost = self::validateFloat($cost);
throw new Exception(Functions::VALUE());
}
$cost = (float) $cost;
if ($cost < 0.0 && $negativeValueAllowed === false) { if ($cost < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -217,11 +215,7 @@ class Depreciation
private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float
{ {
if (!is_numeric($salvage)) { $salvage = self::validateFloat($salvage);
throw new Exception(Functions::VALUE());
}
$salvage = (float) $salvage;
if ($salvage < 0.0 && $negativeValueAllowed === false) { if ($salvage < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -231,11 +225,7 @@ class Depreciation
private static function validateLife($life, bool $negativeValueAllowed = false): float private static function validateLife($life, bool $negativeValueAllowed = false): float
{ {
if (!is_numeric($life)) { $life = self::validateFloat($life);
throw new Exception(Functions::VALUE());
}
$life = (float) $life;
if ($life < 0.0 && $negativeValueAllowed === false) { if ($life < 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -245,11 +235,7 @@ class Depreciation
private static function validatePeriod($period, bool $negativeValueAllowed = false): float private static function validatePeriod($period, bool $negativeValueAllowed = false): float
{ {
if (!is_numeric($period)) { $period = self::validateFloat($period);
throw new Exception(Functions::VALUE());
}
$period = (float) $period;
if ($period <= 0.0 && $negativeValueAllowed === false) { if ($period <= 0.0 && $negativeValueAllowed === false) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -259,11 +245,7 @@ class Depreciation
private static function validateMonth($month): int private static function validateMonth($month): int
{ {
if (!is_numeric($month)) { $month = self::validateInt($month);
throw new Exception(Functions::VALUE());
}
$month = (int) $month;
if ($month < 1) { if ($month < 1) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -273,11 +255,7 @@ class Depreciation
private static function validateFactor($factor): float private static function validateFactor($factor): float
{ {
if (!is_numeric($factor)) { $factor = self::validateFloat($factor);
throw new Exception(Functions::VALUE());
}
$factor = (float) $factor;
if ($factor <= 0.0) { if ($factor <= 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial; namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
@ -47,4 +48,18 @@ class Helpers
return Functions::NAN(); return Functions::NAN();
} }
/**
* isLastDayOfMonth.
*
* Returns a boolean TRUE/FALSE indicating if this date is the last date of the month
*
* @param DateTimeInterface $date The date for testing
*
* @return bool
*/
public static function isLastDayOfMonth(DateTimeInterface $date)
{
return $date->format('d') === $date->format('t');
}
} }

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial; namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class InterestRate class InterestRate
{ {
use BaseValidations;
/** /**
* EFFECT. * EFFECT.
* *
@ -25,16 +28,17 @@ class InterestRate
$nominalRate = Functions::flattenSingleValue($nominalRate); $nominalRate = Functions::flattenSingleValue($nominalRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear); $periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
// Validate parameters try {
if (!is_numeric($nominalRate) || !is_numeric($periodsPerYear)) { $nominalRate = self::validateFloat($nominalRate);
return Functions::VALUE(); $periodsPerYear = self::validateInt($periodsPerYear);
} catch (Exception $e) {
return $e->getMessage();
} }
if ($nominalRate <= 0 || $periodsPerYear < 1) { if ($nominalRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN(); return Functions::NAN();
} }
$periodsPerYear = (int) $periodsPerYear;
return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1; return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
} }
@ -53,16 +57,17 @@ class InterestRate
$effectiveRate = Functions::flattenSingleValue($effectiveRate); $effectiveRate = Functions::flattenSingleValue($effectiveRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear); $periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
// Validate parameters try {
if (!is_numeric($effectiveRate) || !is_numeric($periodsPerYear)) { $effectiveRate = self::validateFloat($effectiveRate);
return Functions::VALUE(); $periodsPerYear = self::validateInt($periodsPerYear);
} catch (Exception $e) {
return $e->getMessage();
} }
if ($effectiveRate <= 0 || $periodsPerYear < 1) { if ($effectiveRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN(); return Functions::NAN();
} }
$periodsPerYear = (int) $periodsPerYear;
// Calculate // Calculate
return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1); return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
} }

View File

@ -0,0 +1,143 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class AccruedInterest
{
use BaseValidations;
public const ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT = true;
public const ACCRINT_CALCMODE_FIRST_INTEREST_TO_SETTLEMENT = false;
/**
* ACCRINT.
*
* Returns the accrued interest for a security that pays periodic interest.
*
* Excel Function:
* ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method])
*
* @param mixed $issue the security's issue date
* @param mixed $firstinterest the security's first interest date
* @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue date
* when the security is traded to the buyer.
* @param mixed (float) $rate The security's annual coupon rate
* @param mixed (float) $par The security's par value.
* If you omit par, ACCRINT uses $1,000.
* @param mixed (int) $frequency The number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param mixed (int) $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* @param mixed $parValue
* @param mixed $calcMethod
*
* @return float|string Result, or a string containing an error
*/
public static function periodic(
$issue,
$firstinterest,
$settlement,
$rate,
$parValue = 1000,
$frequency = 1,
$basis = 0,
$calcMethod = self::ACCRINT_CALCMODE_ISSUE_TO_SETTLEMENT
) {
$issue = Functions::flattenSingleValue($issue);
$firstinterest = Functions::flattenSingleValue($firstinterest);
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$issue = self::validateIssueDate($issue);
$settlement = self::validateSettlementDate($settlement);
self::validateSecurityPeriod($issue, $settlement);
$rate = self::validateRate($rate);
$parValue = self::validateParValue($parValue);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
$daysBetweenFirstInterestAndSettlement = DateTime::YEARFRAC($firstinterest, $settlement, $basis);
if (!is_numeric($daysBetweenFirstInterestAndSettlement)) {
// return date error
return $daysBetweenFirstInterestAndSettlement;
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;
}
/**
* ACCRINTM.
*
* Returns the accrued interest for a security that pays interest at maturity.
*
* Excel Function:
* ACCRINTM(issue,settlement,rate[,par[,basis]])
*
* @param mixed $issue The security's issue date
* @param mixed $settlement The security's settlement (or maturity) date
* @param mixed (float) $rate The security's annual coupon rate
* @param mixed (float) $par The security's par value.
* If you omit par, ACCRINT uses $1,000.
* @param mixed (int) $basis The type of day count to use.
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* @param mixed $parValue
*
* @return float|string Result, or a string containing an error
*/
public static function atMaturity($issue, $settlement, $rate, $parValue = 1000, $basis = 0)
{
$issue = Functions::flattenSingleValue($issue);
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$issue = self::validateIssueDate($issue);
$settlement = self::validateSettlementDate($settlement);
self::validateSecurityPeriod($issue, $settlement);
$rate = self::validateRate($rate);
$parValue = self::validateParValue($parValue);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis);
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;
}
}

View File

@ -7,31 +7,35 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Constants as SecuritiesConstants; use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities\Constants as SecuritiesConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
abstract class BaseValidations trait BaseValidations
{ {
protected static function validateInputDate($date) protected static function validateDate($date)
{ {
$date = DateTimeExcel\Helpers::getDateValue($date); return DateTimeExcel\Helpers::getDateValue($date);
if (is_string($date)) { }
protected static function validateFloat($value): float
{
if (!is_numeric($value)) {
throw new Exception(Functions::VALUE()); throw new Exception(Functions::VALUE());
} }
return $date; return (float) $value;
} }
protected static function validateSettlementDate($settlement) protected static function validateSettlementDate($settlement)
{ {
return self::validateInputDate($settlement); return self::validateDate($settlement);
} }
protected static function validateMaturityDate($maturity) protected static function validateMaturityDate($maturity)
{ {
return self::validateInputDate($maturity); return self::validateDate($maturity);
} }
protected static function validateIssueDate($issue) protected static function validateIssueDate($issue)
{ {
return self::validateInputDate($issue); return self::validateDate($issue);
} }
protected static function validateSecurityPeriod($settlement, $maturity): void protected static function validateSecurityPeriod($settlement, $maturity): void
@ -43,11 +47,7 @@ abstract class BaseValidations
protected static function validateRate($rate): float protected static function validateRate($rate): float
{ {
if (!is_numeric($rate)) { $rate = self::validateFloat($rate);
throw new Exception(Functions::VALUE());
}
$rate = (float) $rate;
if ($rate < 0.0) { if ($rate < 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -55,13 +55,19 @@ abstract class BaseValidations
return $rate; return $rate;
} }
protected static function validatePrice($price): float protected static function validateParValue($parValue): float
{ {
if (!is_numeric($price)) { $parValue = self::validateFloat($parValue);
throw new Exception(Functions::VALUE()); if ($parValue < 0.0) {
throw new Exception(Functions::NAN());
} }
$price = (float) $price; return $parValue;
}
protected static function validatePrice($price): float
{
$price = self::validateFloat($price);
if ($price < 0.0) { if ($price < 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -71,11 +77,7 @@ abstract class BaseValidations
protected static function validateYield($yield): float protected static function validateYield($yield): float
{ {
if (!is_numeric($yield)) { $yield = self::validateFloat($yield);
throw new Exception(Functions::VALUE());
}
$yield = (float) $yield;
if ($yield < 0.0) { if ($yield < 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -85,11 +87,7 @@ abstract class BaseValidations
protected static function validateRedemption($redemption): float protected static function validateRedemption($redemption): float
{ {
if (!is_numeric($redemption)) { $redemption = self::validateFloat($redemption);
throw new Exception(Functions::VALUE());
}
$redemption = (float) $redemption;
if ($redemption <= 0.0) { if ($redemption <= 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }
@ -99,11 +97,7 @@ abstract class BaseValidations
protected static function validateDiscount($discount): float protected static function validateDiscount($discount): float
{ {
if (!is_numeric($discount)) { $discount = self::validateFloat($discount);
throw new Exception(Functions::VALUE());
}
$discount = (float) $discount;
if ($discount <= 0.0) { if ($discount <= 0.0) {
throw new Exception(Functions::NAN()); throw new Exception(Functions::NAN());
} }

View File

@ -8,8 +8,10 @@ use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers; use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Price extends BaseValidations class Price
{ {
use BaseValidations;
/** /**
* PRICE. * PRICE.
* *

View File

@ -7,8 +7,10 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers; use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Yields extends BaseValidations class Yields
{ {
use BaseValidations;
/** /**
* YIELDDISC. * YIELDDISC.
* *

View File

@ -8,6 +8,8 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class TreasuryBill class TreasuryBill
{ {
use BaseValidations;
/** /**
* TBILLEQ. * TBILLEQ.
* *
@ -29,8 +31,8 @@ class TreasuryBill
$discount = Functions::flattenSingleValue($discount); $discount = Functions::flattenSingleValue($discount);
try { try {
$maturity = DateTimeExcel\Helpers::getDateValue($maturity); $settlement = self::validateSettlementDate($settlement);
$settlement = DateTimeExcel\Helpers::getDateValue($settlement); $maturity = self::validateMaturityDate($maturity);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
@ -75,8 +77,8 @@ class TreasuryBill
$discount = Functions::flattenSingleValue($discount); $discount = Functions::flattenSingleValue($discount);
try { try {
$maturity = DateTimeExcel\Helpers::getDateValue($maturity); $settlement = self::validateSettlementDate($settlement);
$settlement = DateTimeExcel\Helpers::getDateValue($settlement); $maturity = self::validateMaturityDate($maturity);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }
@ -126,8 +128,8 @@ class TreasuryBill
$price = Functions::flattenSingleValue($price); $price = Functions::flattenSingleValue($price);
try { try {
$maturity = DateTimeExcel\Helpers::getDateValue($maturity); $settlement = self::validateSettlementDate($settlement);
$settlement = DateTimeExcel\Helpers::getDateValue($settlement); $maturity = self::validateMaturityDate($maturity);
} catch (Exception $e) { } catch (Exception $e) {
return $e->getMessage(); return $e->getMessage();
} }

View File

@ -21,7 +21,7 @@ class AccrintMTest extends TestCase
public function testACCRINTM($expectedResult, ...$args): void public function testACCRINTM($expectedResult, ...$args): void
{ {
$result = Financial::ACCRINTM(...$args); $result = Financial::ACCRINTM(...$args);
self::assertEqualsWithDelta($expectedResult, $result, 1E-8); self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
} }
public function providerACCRINTM() public function providerACCRINTM()

View File

@ -21,7 +21,7 @@ class AccrintTest extends TestCase
public function testACCRINT($expectedResult, ...$args): void public function testACCRINT($expectedResult, ...$args): void
{ {
$result = Financial::ACCRINT(...$args); $result = Financial::ACCRINT(...$args);
self::assertEqualsWithDelta($expectedResult, $result, 1E-8); self::assertEqualsWithDelta($expectedResult, $result, 1E-12);
} }
public function providerACCRINT() public function providerACCRINT()

View File

@ -4,72 +4,83 @@
return [ return [
[ [
16.666666666666998, 16.6666666666666,
'2008-03-01', '2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 0,
'2008-08-31',
'2008-05-01',
0.10000000000000001,
1000,
2,
0,
], ],
[ [
15.555555555555999, 15.5555555555559,
'2008-03-05', '2008-03-05', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 0,
'2008-08-31', ],
'2008-05-01', [
0.10000000000000001, 15.5555555555559,
1000, '2008-03-05', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 0, true,
2, ],
0, [
7.22222222222222,
'2008-04-05', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 0, true,
], ],
[ [
200, 200,
'2010-01-01', '2010-01-01', '2010-06-30', '2010-04-01', 0.08, 10000, 4,
'2010-06-30', ],
'2010-04-01', [
0.080000000000000002, 1600,
10000, '2012-01-01', '2012-04-01', '2013-12-31', 0.08, 10000, 4,
4, ],
[
32.363013698630134,
'2012-01-01', '2012-03-31', '2012-02-15', 0.0525, 5000, 4, 3, 1,
],
[
6.472602739726027,
'2012-01-01', '2012-03-31', '2012-02-15', 0.0525, 1000, 4, 3, 1,
],
[
18.05555555555555,
'2017-08-05', '2017-11-10', '2017-10-10', 0.05, 2000, 4, 0, 1,
], ],
[ [
'#NUM!', '#NUM!',
'2008-03-05', '2008-03-05', '2008-08-31', '2008-05-01', -0.10, 1000, 2, 0,
'2008-08-31',
'2008-05-01',
-0.10000000000000001,
1000,
2,
0,
], ],
[ [
'#VALUE!', '#VALUE!',
'Invalid Date', 'Invalid Date', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 0,
'2008-08-31',
'2008-05-01',
0.10000000000000001,
1000,
2,
0,
], ],
[ [
'#VALUE!', '#VALUE!',
'2008-03-01', '2008-03-01', '2008-08-31', '2008-05-01', 'ABC', 1000, 2, 0,
'2008-08-31',
'2008-05-01',
'ABC',
1000,
2,
0,
], ],
[ 'Non-numeric Rate' => [
'#VALUE!', '#VALUE!',
'2008-03-01', '2008-03-01', '2008-08-31', '2008-05-01', 'NaN', 1000, 2, 0,
'2008-08-31', ],
'2008-05-01', 'Invalid Rate' => [
0.10000000000000001, '#NUM!',
1000, '2008-03-01', '2008-08-31', '2008-05-01', -0.10, 1000, 2, 0,
2, ],
'ABC', 'Non-numeric Par Value' => [
'#VALUE!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, 'NaN', 2, 0,
],
'Invalid Par Value' => [
'#NUM!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, -1000, 2, 0,
],
'Non-numeric Frequency' => [
'#VALUE!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 'NaN', 0,
],
'Invalid Frequency' => [
'#NUM!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, -1000, 3, 0,
],
'Non-numeric Basis' => [
'#VALUE!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 2, 'ABC',
],
'Invalid Basis' => [
'#NUM!',
'2008-03-01', '2008-08-31', '2008-05-01', 0.10, 1000, 2, -2,
], ],
]; ];

View File

@ -5,41 +5,50 @@
return [ return [
[ [
20.547945205478999, 20.547945205478999,
'2008-04-01', '2008-04-01', '2008-06-15', 0.10, 1000, 3,
'2008-06-15',
0.10000000000000001,
1000,
3,
], ],
[ [
800, 800,
'2010-01-01', '2010-01-01', '2010-12-31', 0.08, 10000,
'2010-12-31', ],
0.080000000000000002, [
10000, 365.958904109589,
'2012-01-01', '2013-02-15', 0.065, 5000, 3,
],
[
73.1917808219178,
'2012-01-01', '2013-02-15', 0.065, 1000, 3,
], ],
[ [
'#NUM!', '#NUM!',
'2008-03-05', '2008-03-05', '2008-08-31', -0.10, 1000, 2,
'2008-08-31',
-0.10000000000000001,
1000,
2,
], ],
[ [
'#VALUE!', '#VALUE!',
'Invalid Date', 'Invalid Date', '2008-08-31', 0.10, 1000, 2,
'2008-08-31',
0.10000000000000001,
1000,
2,
], ],
[ 'Non-numeric Rate' => [
'#VALUE!', '#VALUE!',
'2008-03-01', '2008-03-01', '2008-08-31', 'NaN', 1000, 2,
'2008-08-31', ],
'ABC', 'Invalid Rate' => [
1000, '#NUM!',
2, '2008-03-01', '2008-08-31', -0.10, 1000, 2,
],
'Non-numeric Par Value' => [
'#VALUE!',
'2008-03-01', '2008-08-31', 0.10, 'NaN', 2,
],
'Invalid Par Value' => [
'#NUM!',
'2008-03-01', '2008-08-31', 0.10, -1000, 2,
],
'Non-numeric Basis' => [
'#VALUE!',
'2008-03-01', '2008-08-31', 0.10, 1000, 'NaN',
],
'Invalid Basis' => [
'#NUM!',
'2008-03-01', '2008-08-31', 0.10, 1000, 99,
], ],
]; ];

View File

@ -47,6 +47,14 @@ return [
42, 42,
150, '2011-01-01', '2011-09-30', 20, 1, 0.4, 4, 150, '2011-01-01', '2011-09-30', 20, 1, 0.4, 4,
], ],
[
2813,
10000, '2012-03-01', '2012-12-31', 1500, 1, 0.3, 1,
],
[
'#VALUE!',
'NaN', '2012-03-01', '2020-12-25', 20, 1, 0.2, 4,
],
[ [
'#VALUE!', '#VALUE!',
550, 'notADate', '2020-12-25', 20, 1, 0.2, 4, 550, 'notADate', '2020-12-25', 20, 1, 0.2, 4,
@ -55,4 +63,8 @@ return [
'#VALUE!', '#VALUE!',
550, '2011-01-01', 'notADate', 20, 1, 0.2, 4, 550, '2011-01-01', 'notADate', 20, 1, 0.2, 4,
], ],
[
'#VALUE!',
550, '2012-03-01', '2020-12-25', 'NaN', 1, 0.2, 4,
],
]; ];

View File

@ -45,7 +45,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -59,7 +59,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,

View File

@ -59,7 +59,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -73,7 +73,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,

View File

@ -38,7 +38,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -52,7 +52,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,

View File

@ -38,7 +38,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -52,7 +52,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,

View File

@ -39,7 +39,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -53,7 +53,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,

View File

@ -38,7 +38,7 @@ return [
1, 1,
], ],
'Non-Numeric Frequency' => [ 'Non-Numeric Frequency' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
'NaN', 'NaN',
@ -52,7 +52,7 @@ return [
-1, -1,
], ],
'Non-Numeric Basis' => [ 'Non-Numeric Basis' => [
'#NUM!', '#VALUE!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
4, 4,