Start work on breaking down some of the Financial Excel functions (#1941)

* Start work on breaking down some of the Financial Excel functions
* Unhappy path unit tests for Treasury Bill functions
* Codebase for Treasury Bills includes logic for a different days between settlement and maturity calculation for OpenOffice; but Open/Libre Office now uses the Excel days calculation, so this discrepancy between packages is no longer required
* We've already converted the Settlement and Maturity dates to Excel timestamps, so there's no need to try doing it again when calculating the days between Settlement and Maturity
* Add Unit Tests for the Days per Year helper function
* Extract Interest Rate functions - EFFECT() and NOMINAL() - with additional validation, and unhappy path unit tests
* First pass at extracting the Coupon Excel functions
* Simplify the validation methods
* Extended unit tests to cover all combinations of frequency and basis, including leap years
Fix for COUPDAYSNC() when basis is US 360 and settlement date is the last day of the month
* Ensure that all Financial function code uses the new Helpers class for Days Per Year
This commit is contained in:
Mark Baker 2021-03-20 18:40:53 +01:00 committed by GitHub
parent 4e8a926cb4
commit d346318c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2009 additions and 418 deletions

View File

@ -650,32 +650,32 @@ class Calculation
], ],
'COUPDAYBS' => [ 'COUPDAYBS' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPDAYBS'], 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COUPDAYS' => [ 'COUPDAYS' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPDAYS'], 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COUPDAYSNC' => [ 'COUPDAYSNC' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPDAYSNC'], 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COUPNCD' => [ 'COUPNCD' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPNCD'], 'functionCall' => [Financial\Coupons::class, 'COUPNCD'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COUPNUM' => [ 'COUPNUM' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPNUM'], 'functionCall' => [Financial\Coupons::class, 'COUPNUM'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COUPPCD' => [ 'COUPPCD' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'COUPPCD'], 'functionCall' => [Financial\Coupons::class, 'COUPPCD'],
'argumentCount' => '3,4', 'argumentCount' => '3,4',
], ],
'COVAR' => [ 'COVAR' => [
@ -875,12 +875,12 @@ class Calculation
], ],
'DOLLARDE' => [ 'DOLLARDE' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'DOLLARDE'], 'functionCall' => [Financial\Dollar::class, 'decimal'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'DOLLARFR' => [ 'DOLLARFR' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'DOLLARFR'], 'functionCall' => [Financial\Dollar::class, 'fractional'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'DPRODUCT' => [ 'DPRODUCT' => [
@ -925,7 +925,7 @@ class Calculation
], ],
'EFFECT' => [ 'EFFECT' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'EFFECT'], 'functionCall' => [Financial\InterestRate::class, 'effective'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'ENCODEURL' => [ 'ENCODEURL' => [
@ -1771,7 +1771,7 @@ class Calculation
], ],
'NOMINAL' => [ 'NOMINAL' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'NOMINAL'], 'functionCall' => [Financial\InterestRate::class, 'nominal'],
'argumentCount' => '2', 'argumentCount' => '2',
], ],
'NORMDIST' => [ 'NORMDIST' => [
@ -2376,17 +2376,17 @@ class Calculation
], ],
'TBILLEQ' => [ 'TBILLEQ' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'TBILLEQ'], 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'TBILLPRICE' => [ 'TBILLPRICE' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'TBILLPRICE'], 'functionCall' => [Financial\TreasuryBill::class, 'price'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'TBILLYIELD' => [ 'TBILLYIELD' => [
'category' => Category::CATEGORY_FINANCIAL, 'category' => Category::CATEGORY_FINANCIAL,
'functionCall' => [Financial::class, 'TBILLYIELD'], 'functionCall' => [Financial\TreasuryBill::class, 'yield'],
'argumentCount' => '3', 'argumentCount' => '3',
], ],
'TDIST' => [ 'TDIST' => [

View File

@ -2,7 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Calculation; namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate;
class Financial class Financial
{ {
@ -10,41 +11,6 @@ class Financial
const FINANCIAL_PRECISION = 1.0e-08; const FINANCIAL_PRECISION = 1.0e-08;
/**
* 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, $frequency, $next)
{
$months = 12 / $frequency;
$result = Date::excelToDateTimeObject($maturity);
$eom = self::isLastDayOfMonth($result);
while ($settlement < Date::PHPToExcel($result)) {
$result->modify('-' . $months . ' months');
}
if ($next) {
$result->modify('+' . $months . ' months');
}
if ($eom) {
$result->modify('-1 day');
}
return Date::PHPToExcel($result);
}
private static function isValidFrequency($frequency) private static function isValidFrequency($frequency)
{ {
if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) { if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) {
@ -54,45 +20,6 @@ class Financial
return false; return false;
} }
/**
* daysPerYear.
*
* Returns the number of days in a specified year, as defined by the "basis" value
*
* @param int|string $year The year against which we're testing
* @param int|string $basis The type of day count:
* 0 or omitted US (NASD) 360
* 1 Actual (365 or 366 in a leap year)
* 2 360
* 3 365
* 4 European 360
*
* @return int|string Result, or a string containing an error
*/
private static function daysPerYear($year, $basis = 0)
{
switch ($basis) {
case 0:
case 2:
case 4:
$daysPerYear = 360;
break;
case 3:
$daysPerYear = 365;
break;
case 1:
$daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365;
break;
default:
return Functions::NAN();
}
return $daysPerYear;
}
private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0)
{ {
$pmt = self::PMT($rate, $nper, $pv, $fv, $type); $pmt = self::PMT($rate, $nper, $pv, $fv, $type);
@ -369,6 +296,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPDAYBS(settlement,maturity,frequency[,basis]) * COUPDAYBS(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPDAYBS() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -390,34 +321,7 @@ class Financial
*/ */
public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0) public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
if ($basis == 1) {
return abs(DateTime::DAYS($prev, $settlement));
}
return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
} }
/** /**
@ -428,6 +332,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPDAYS(settlement,maturity,frequency[,basis]) * COUPDAYS(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPDAYS() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -449,45 +357,7 @@ class Financial
*/ */
public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0) public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
switch ($basis) {
case 3:
// Actual/365
return 365 / $frequency;
case 1:
// Actual/actual
if ($frequency == 1) {
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
return $daysPerYear / $frequency;
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
return $next - $prev;
default:
// US (NASD) 30/360, Actual/360 or European 30/360
return 360 / $frequency;
}
} }
/** /**
@ -498,6 +368,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPDAYSNC(settlement,maturity,frequency[,basis]) * COUPDAYSNC(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPDAYSNC() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -519,30 +393,7 @@ class Financial
*/ */
public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0) public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
} }
/** /**
@ -553,6 +404,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPNCD(settlement,maturity,frequency[,basis]) * COUPNCD(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPNCD() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -575,27 +430,7 @@ class Financial
*/ */
public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0) public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPNCD($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
} }
/** /**
@ -607,6 +442,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPNUM(settlement,maturity,frequency[,basis]) * COUPNUM(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPNUM() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -628,29 +467,7 @@ class Financial
*/ */
public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0) public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
$yearsBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, 0);
return ceil($yearsBetweenSettlementAndMaturity * $frequency);
} }
/** /**
@ -661,6 +478,10 @@ class Financial
* Excel Function: * Excel Function:
* COUPPCD(settlement,maturity,frequency[,basis]) * COUPPCD(settlement,maturity,frequency[,basis])
* *
* @Deprecated 1.18.0
*
* @see Use the COUPPCD() method in the Financial\Coupons class instead
*
* @param mixed $settlement The security's settlement date. * @param mixed $settlement The security's settlement date.
* The security settlement date is the date after the issue * The security settlement date is the date after the issue
* date when the security is traded to the buyer. * date when the security is traded to the buyer.
@ -683,27 +504,7 @@ class Financial
*/ */
public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0) public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Coupons::COUPPCD($settlement, $maturity, $frequency, $basis);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = (int) Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis);
if (is_string($settlement = DateTime::getDateValue($settlement))) {
return Functions::VALUE();
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (
($settlement >= $maturity) ||
(!self::isValidFrequency($frequency)) ||
(($basis < 0) || ($basis > 4))
) {
return Functions::NAN();
}
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false);
} }
/** /**
@ -997,6 +798,10 @@ class Financial
* Excel Function: * Excel Function:
* DOLLARDE(fractional_dollar,fraction) * DOLLARDE(fractional_dollar,fraction)
* *
* @Deprecated 1.18.0
*
* @see Use the decimal() method in the Financial\Dollar class instead
*
* @param float $fractional_dollar Fractional Dollar * @param float $fractional_dollar Fractional Dollar
* @param int $fraction Fraction * @param int $fraction Fraction
* *
@ -1004,23 +809,7 @@ class Financial
*/ */
public static function DOLLARDE($fractional_dollar = null, $fraction = 0) public static function DOLLARDE($fractional_dollar = null, $fraction = 0)
{ {
$fractional_dollar = Functions::flattenSingleValue($fractional_dollar); return Financial\Dollar::decimal($fractional_dollar, $fraction);
$fraction = (int) Functions::flattenSingleValue($fraction);
// Validate parameters
if ($fractional_dollar === null || $fraction < 0) {
return Functions::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
}
$dollars = floor($fractional_dollar);
$cents = fmod($fractional_dollar, 1);
$cents /= $fraction;
$cents *= 10 ** ceil(log10($fraction));
return $dollars + $cents;
} }
/** /**
@ -1033,6 +822,10 @@ class Financial
* Excel Function: * Excel Function:
* DOLLARFR(decimal_dollar,fraction) * DOLLARFR(decimal_dollar,fraction)
* *
* @Deprecated 1.18.0
*
* @see Use the fractional() method in the Financial\Dollar class instead
*
* @param float $decimal_dollar Decimal Dollar * @param float $decimal_dollar Decimal Dollar
* @param int $fraction Fraction * @param int $fraction Fraction
* *
@ -1040,23 +833,7 @@ class Financial
*/ */
public static function DOLLARFR($decimal_dollar = null, $fraction = 0) public static function DOLLARFR($decimal_dollar = null, $fraction = 0)
{ {
$decimal_dollar = Functions::flattenSingleValue($decimal_dollar); return Financial\Dollar::fractional($decimal_dollar, $fraction);
$fraction = (int) Functions::flattenSingleValue($fraction);
// Validate parameters
if ($decimal_dollar === null || $fraction < 0) {
return Functions::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
}
$dollars = floor($decimal_dollar);
$cents = fmod($decimal_dollar, 1);
$cents *= $fraction;
$cents *= 10 ** (-ceil(log10($fraction)));
return $dollars + $cents;
} }
/** /**
@ -1068,22 +845,18 @@ class Financial
* Excel Function: * Excel Function:
* EFFECT(nominal_rate,npery) * EFFECT(nominal_rate,npery)
* *
* @param float $nominal_rate Nominal interest rate * @Deprecated 1.18.0
* @param int $npery Number of compounding payments per year *
* @see Use the effective() method in the Financial\InterestRate class instead
*
* @param float $nominalRate Nominal interest rate
* @param int $periodsPerYear Number of compounding payments per year
* *
* @return float|string * @return float|string
*/ */
public static function EFFECT($nominal_rate = 0, $npery = 0) public static function EFFECT($nominalRate = 0, $periodsPerYear = 0)
{ {
$nominal_rate = Functions::flattenSingleValue($nominal_rate); return Financial\InterestRate::effective($nominalRate, $periodsPerYear);
$npery = (int) Functions::flattenSingleValue($npery);
// Validate parameters
if ($nominal_rate <= 0 || $npery < 1) {
return Functions::NAN();
}
return (1 + $nominal_rate / $npery) ** $npery - 1;
} }
/** /**
@ -1412,23 +1185,21 @@ class Financial
* *
* Returns the nominal interest rate given the effective rate and the number of compounding payments per year. * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
* *
* @param float $effect_rate Effective interest rate * Excel Function:
* @param int $npery Number of compounding payments per year * NOMINAL(effect_rate, npery)
*
* @Deprecated 1.18.0
*
* @see Use the nominal() method in the Financial\InterestRate class instead
*
* @param float $effectiveRate Effective interest rate
* @param int $periodsPerYear Number of compounding payments per year
* *
* @return float|string Result, or a string containing an error * @return float|string Result, or a string containing an error
*/ */
public static function NOMINAL($effect_rate = 0, $npery = 0) public static function NOMINAL($effectiveRate = 0, $periodsPerYear = 0)
{ {
$effect_rate = Functions::flattenSingleValue($effect_rate); return InterestRate::nominal($effectiveRate, $periodsPerYear);
$npery = (int) Functions::flattenSingleValue($npery);
// Validate parameters
if ($effect_rate <= 0 || $npery < 1) {
return Functions::NAN();
}
// Calculate
return $npery * (($effect_rate + 1) ** (1 / $npery) - 1);
} }
/** /**
@ -1754,7 +1525,7 @@ class Financial
if (($rate <= 0) || ($yield <= 0)) { if (($rate <= 0) || ($yield <= 0)) {
return Functions::NAN(); return Functions::NAN();
} }
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); $daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) { if (!is_numeric($daysPerYear)) {
return $daysPerYear; return $daysPerYear;
} }
@ -2030,6 +1801,10 @@ class Financial
* *
* Returns the bond-equivalent yield for a Treasury bill. * Returns the bond-equivalent yield for a Treasury bill.
* *
* @Deprecated 1.18.0
*
* @see Use the bondEquivalentYield() method in the Financial\TreasuryBill class instead
*
* @param mixed $settlement The Treasury bill's settlement date. * @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date. * @param mixed $maturity The Treasury bill's maturity date.
@ -2040,37 +1815,21 @@ class Financial
*/ */
public static function TBILLEQ($settlement, $maturity, $discount) public static function TBILLEQ($settlement, $maturity, $discount)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Financial\TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
// Use TBILLPRICE for validation
$testValue = self::TBILLPRICE($settlement, $maturity, $discount);
if (is_string($testValue)) {
return $testValue;
}
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
++$maturity;
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
} else {
$daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
}
return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
} }
/** /**
* TBILLPRICE. * TBILLPRICE.
* *
* Returns the yield for a Treasury bill. * Returns the price per $100 face value for a Treasury bill.
*
* @Deprecated 1.18.0
*
* @see Use the price() method in the Financial\TreasuryBill class instead
* *
* @param mixed $settlement The Treasury bill's settlement date. * @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. * The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date. * @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires. * The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate * @param int $discount The Treasury bill's discount rate
@ -2079,44 +1838,7 @@ class Financial
*/ */
public static function TBILLPRICE($settlement, $maturity, $discount) public static function TBILLPRICE($settlement, $maturity, $discount)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Financial\TreasuryBill::price($settlement, $maturity, $discount);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
if (is_string($maturity = DateTime::getDateValue($maturity))) {
return Functions::VALUE();
}
// Validate
if (is_numeric($discount)) {
if ($discount <= 0) {
return Functions::NAN();
}
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
++$maturity;
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
} else {
$daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
}
if ($daysBetweenSettlementAndMaturity > self::daysPerYear(DateTime::YEAR($maturity), 1)) {
return Functions::NAN();
}
$price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
if ($price <= 0) {
return Functions::NAN();
}
return $price;
}
return Functions::VALUE();
} }
/** /**
@ -2124,8 +1846,13 @@ class Financial
* *
* Returns the yield for a Treasury bill. * Returns the yield for a Treasury bill.
* *
* @Deprecated 1.18.0
*
* @see Use the yield() method in the Financial\TreasuryBill class instead
*
* @param mixed $settlement The Treasury bill's settlement date. * @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. * The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date. * @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires. * The maturity date is the date when the Treasury bill expires.
* @param int $price The Treasury bill's price per $100 face value * @param int $price The Treasury bill's price per $100 face value
@ -2134,35 +1861,7 @@ class Financial
*/ */
public static function TBILLYIELD($settlement, $maturity, $price) public static function TBILLYIELD($settlement, $maturity, $price)
{ {
$settlement = Functions::flattenSingleValue($settlement); return Financial\TreasuryBill::yield($settlement, $maturity, $price);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
// Validate
if (is_numeric($price)) {
if ($price <= 0) {
return Functions::NAN();
}
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
++$maturity;
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360;
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
}
} else {
$daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement));
}
if ($daysBetweenSettlementAndMaturity > 360) {
return Functions::NAN();
}
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
}
return Functions::VALUE();
} }
private static function bothNegAndPos($neg, $pos) private static function bothNegAndPos($neg, $pos)
@ -2407,7 +2106,7 @@ class Financial
if (($price <= 0) || ($redemption <= 0)) { if (($price <= 0) || ($redemption <= 0)) {
return Functions::NAN(); return Functions::NAN();
} }
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); $daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) { if (!is_numeric($daysPerYear)) {
return $daysPerYear; return $daysPerYear;
} }
@ -2459,7 +2158,7 @@ class Financial
if (($rate <= 0) || ($price <= 0)) { if (($rate <= 0) || ($price <= 0)) {
return Functions::NAN(); return Functions::NAN();
} }
$daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); $daysPerYear = Financial\Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
if (!is_numeric($daysPerYear)) { if (!is_numeric($daysPerYear)) {
return $daysPerYear; return $daysPerYear;
} }

View File

@ -0,0 +1,435 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class Coupons
{
public const FREQUENCY_ANNUAL = 1;
public const FREQUENCY_SEMI_ANNUAL = 2;
public const FREQUENCY_QUARTERLY = 4;
private const PERIOD_DATE_PREVIOUS = false;
private const PERIOD_DATE_NEXT = true;
/**
* COUPDAYBS.
*
* Returns the number of days from the beginning of the coupon period to the settlement date.
*
* Excel Function:
* COUPDAYBS(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param int $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return float|string
*/
public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
if ($basis === Helpers::DAYS_PER_YEAR_ACTUAL) {
return abs(DateTime::DAYS($prev, $settlement));
}
return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear;
}
/**
* COUPDAYS.
*
* Returns the number of days in the coupon period that contains the settlement date.
*
* Excel Function:
* COUPDAYS(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return float|string
*/
public static function COUPDAYS($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
switch ($basis) {
case Helpers::DAYS_PER_YEAR_365:
// Actual/365
return 365 / $frequency;
case Helpers::DAYS_PER_YEAR_ACTUAL:
// Actual/actual
if ($frequency == self::FREQUENCY_ANNUAL) {
$daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
return $daysPerYear / $frequency;
}
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
return $next - $prev;
default:
// US (NASD) 30/360, Actual/360 or European 30/360
return 360 / $frequency;
}
}
/**
* COUPDAYSNC.
*
* Returns the number of days from the settlement date to the next coupon date.
*
* Excel Function:
* COUPDAYSNC(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return float|string
*/
public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$daysPerYear = Helpers::daysPerYear(DateTime::YEAR($settlement), $basis);
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
if ($basis === Helpers::DAYS_PER_YEAR_NASD) {
$settlementDate = Date::excelToDateTimeObject($settlement);
$settlementEoM = self::isLastDayOfMonth($settlementDate);
if ($settlementEoM) {
++$settlement;
}
}
return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear;
}
/**
* COUPNCD.
*
* Returns the next coupon date after the settlement date.
*
* Excel Function:
* COUPNCD(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
public static function COUPNCD($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
}
/**
* COUPNUM.
*
* Returns the number of coupons payable between the settlement date and maturity date,
* rounded up to the nearest whole coupon.
*
* Excel Function:
* COUPNUM(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return int|string
*/
public static function COUPNUM($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
$yearsBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, 0);
return ceil($yearsBetweenSettlementAndMaturity * $frequency);
}
/**
* COUPPCD.
*
* Returns the previous coupon date before the settlement date.
*
* Excel Function:
* COUPPCD(settlement,maturity,frequency[,basis])
*
* @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 $maturity The security's maturity date.
* The maturity date is the date when the security expires.
* @param mixed $frequency the number of coupon payments per year.
* Valid frequency values are:
* 1 Annual
* 2 Semi-Annual
* 4 Quarterly
* @param 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
*
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
public static function COUPPCD($settlement, $maturity, $frequency, $basis = Helpers::DAYS_PER_YEAR_NASD)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis);
try {
$settlement = self::validateSettlementDate($settlement);
$maturity = self::validateMaturityDate($maturity);
self::validateCouponPeriod($settlement, $maturity);
$frequency = self::validateFrequency($frequency);
$basis = self::validateBasis($basis);
} catch (Exception $e) {
return $e->getMessage();
}
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)
{
$months = 12 / $frequency;
$result = Date::excelToDateTimeObject($maturity);
$maturityEoM = self::isLastDayOfMonth($result);
while ($settlement < Date::PHPToExcel($result)) {
$result->modify('-' . $months . ' months');
}
if ($next === true) {
$result->modify('+' . $months . ' months');
}
if ($maturityEoM === true) {
$result->modify('-1 day');
}
return Date::PHPToExcel($result);
}
private static function validateInputDate($date)
{
$date = DateTime::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
{
if ($settlement >= $maturity) {
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)
{
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

@ -0,0 +1,80 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Dollar
{
/**
* DOLLARDE.
*
* Converts a dollar price expressed as an integer part and a fraction
* part into a dollar price expressed as a decimal number.
* Fractional dollar numbers are sometimes used for security prices.
*
* Excel Function:
* DOLLARDE(fractional_dollar,fraction)
*
* @param float $fractionalDollar Fractional Dollar
* @param int $fraction Fraction
*
* @return float|string
*/
public static function decimal($fractionalDollar = null, $fraction = 0)
{
$fractionalDollar = Functions::flattenSingleValue($fractionalDollar);
$fraction = (int) Functions::flattenSingleValue($fraction);
// Validate parameters
if ($fractionalDollar === null || $fraction < 0) {
return Functions::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
}
$dollars = floor($fractionalDollar);
$cents = fmod($fractionalDollar, 1);
$cents /= $fraction;
$cents *= 10 ** ceil(log10($fraction));
return $dollars + $cents;
}
/**
* DOLLARFR.
*
* Converts a dollar price expressed as a decimal number into a dollar price
* expressed as a fraction.
* Fractional dollar numbers are sometimes used for security prices.
*
* Excel Function:
* DOLLARFR(decimal_dollar,fraction)
*
* @param float $decimalDollar Decimal Dollar
* @param int $fraction Fraction
*
* @return float|string
*/
public static function fractional($decimalDollar = null, $fraction = 0)
{
$decimalDollar = Functions::flattenSingleValue($decimalDollar);
$fraction = (int) Functions::flattenSingleValue($fraction);
// Validate parameters
if ($decimalDollar === null || $fraction < 0) {
return Functions::NAN();
}
if ($fraction == 0) {
return Functions::DIV0();
}
$dollars = floor($decimalDollar);
$cents = fmod($decimalDollar, 1);
$cents *= $fraction;
$cents *= 10 ** (-ceil(log10($fraction)));
return $dollars + $cents;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class Helpers
{
public const DAYS_PER_YEAR_NASD = 0;
public const DAYS_PER_YEAR_ACTUAL = 1;
public const DAYS_PER_YEAR_360 = 2;
public const DAYS_PER_YEAR_365 = 3;
public const DAYS_PER_YEAR_360_EUROPEAN = 4;
/**
* daysPerYear.
*
* Returns the number of days in a specified year, as defined by the "basis" value
*
* @param int|string $year The year against which we're testing
* @param int|string $basis The type of day count:
* 0 or omitted US (NASD) 360
* 1 Actual (365 or 366 in a leap year)
* 2 360
* 3 365
* 4 European 360
*
* @return int|string Result, or a string containing an error
*/
public static function daysPerYear($year, $basis = 0)
{
if (!is_numeric($basis)) {
return Functions::NAN();
}
switch ($basis) {
case self::DAYS_PER_YEAR_NASD:
case self::DAYS_PER_YEAR_360:
case self::DAYS_PER_YEAR_360_EUROPEAN:
return 360;
case self::DAYS_PER_YEAR_365:
return 365;
case self::DAYS_PER_YEAR_ACTUAL:
return (DateTime::isLeapYear($year)) ? 366 : 365;
}
return Functions::NAN();
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class InterestRate
{
/**
* EFFECT.
*
* Returns the effective interest rate given the nominal rate and the number of
* compounding payments per year.
*
* Excel Function:
* EFFECT(nominal_rate,npery)
*
* @param float $nominalRate Nominal interest rate
* @param int $periodsPerYear Number of compounding payments per year
*
* @return float|string
*/
public static function effective($nominalRate = 0, $periodsPerYear = 0)
{
$nominalRate = Functions::flattenSingleValue($nominalRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
// Validate parameters
if (!is_numeric($nominalRate) || !is_numeric($periodsPerYear)) {
return Functions::VALUE();
}
if ($nominalRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN();
}
$periodsPerYear = (int) $periodsPerYear;
return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
}
/**
* NOMINAL.
*
* Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
*
* @param float $effectiveRate Effective interest rate
* @param int $periodsPerYear Number of compounding payments per year
*
* @return float|string Result, or a string containing an error
*/
public static function nominal($effectiveRate = 0, $periodsPerYear = 0)
{
$effectiveRate = Functions::flattenSingleValue($effectiveRate);
$periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
// Validate parameters
if (!is_numeric($effectiveRate) || !is_numeric($periodsPerYear)) {
return Functions::VALUE();
}
if ($effectiveRate <= 0 || $periodsPerYear < 1) {
return Functions::NAN();
}
$periodsPerYear = (int) $periodsPerYear;
// Calculate
return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class TreasuryBill
{
/**
* TBILLEQ.
*
* Returns the bond-equivalent yield for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
public static function bondEquivalentYield($settlement, $maturity, $discount)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
if (
is_string($maturity = DateTime::getDateValue($maturity)) ||
is_string($settlement = DateTime::getDateValue($settlement))
) {
return Functions::VALUE();
}
// Validate
if (is_numeric($discount)) {
if ($discount <= 0) {
return Functions::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
if (
$daysBetweenSettlementAndMaturity > Helpers::daysPerYear(DateTime::YEAR($maturity), Helpers::DAYS_PER_YEAR_ACTUAL) ||
$daysBetweenSettlementAndMaturity < 0
) {
return Functions::NAN();
}
return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity);
}
return Functions::VALUE();
}
/**
* TBILLPRICE.
*
* Returns the price per $100 face value for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date
* when the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $discount The Treasury bill's discount rate
*
* @return float|string Result, or a string containing an error
*/
public static function price($settlement, $maturity, $discount)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
if (
is_string($maturity = DateTime::getDateValue($maturity)) ||
is_string($settlement = DateTime::getDateValue($settlement))
) {
return Functions::VALUE();
}
// Validate
if (is_numeric($discount)) {
if ($discount <= 0) {
return Functions::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
if (
$daysBetweenSettlementAndMaturity > Helpers::daysPerYear(DateTime::YEAR($maturity), Helpers::DAYS_PER_YEAR_ACTUAL) ||
$daysBetweenSettlementAndMaturity < 0
) {
return Functions::NAN();
}
$price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360));
if ($price < 0.0) {
return Functions::NAN();
}
return $price;
}
return Functions::VALUE();
}
/**
* TBILLYIELD.
*
* Returns the yield for a Treasury bill.
*
* @param mixed $settlement The Treasury bill's settlement date.
* The Treasury bill's settlement date is the date after the issue date when
* the Treasury bill is traded to the buyer.
* @param mixed $maturity The Treasury bill's maturity date.
* The maturity date is the date when the Treasury bill expires.
* @param int $price The Treasury bill's price per $100 face value
*
* @return float|string
*/
public static function yield($settlement, $maturity, $price)
{
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
if (
is_string($maturity = DateTime::getDateValue($maturity)) ||
is_string($settlement = DateTime::getDateValue($settlement))
) {
return Functions::VALUE();
}
// Validate
if (is_numeric($price)) {
if ($price <= 0) {
return Functions::NAN();
}
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
if ($daysBetweenSettlementAndMaturity > 360 || $daysBetweenSettlementAndMaturity < 0) {
return Functions::NAN();
}
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
}
return Functions::VALUE();
}
}

View File

@ -17,10 +17,12 @@ class EffectTest extends TestCase
* @dataProvider providerEFFECT * @dataProvider providerEFFECT
* *
* @param mixed $expectedResult * @param mixed $expectedResult
* @param mixed $rate
* @param mixed $periods
*/ */
public function testEFFECT($expectedResult, ...$args): void public function testEFFECT($expectedResult, $rate, $periods): void
{ {
$result = Financial::EFFECT(...$args); $result = Financial::EFFECT($rate, $periods);
self::assertEqualsWithDelta($expectedResult, $result, 1E-8); self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
} }

View File

@ -0,0 +1,27 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Financial;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PHPUnit\Framework\TestCase;
class HelpersTest extends TestCase
{
/**
* @dataProvider providerDaysPerYear
*
* @param mixed $expectedResult
* @param mixed $year
* @param mixed $basis
*/
public function testDaysPerYear($expectedResult, $year, $basis): void
{
$result = Helpers::daysPerYear($year, $basis);
self::assertSame($expectedResult, $result, 1E-8);
}
public function providerDaysPerYear()
{
return require 'tests/data/Calculation/Financial/DaysPerYear.php';
}
}

View File

@ -17,10 +17,12 @@ class NominalTest extends TestCase
* @dataProvider providerNOMINAL * @dataProvider providerNOMINAL
* *
* @param mixed $expectedResult * @param mixed $expectedResult
* @param mixed $rate
* @param mixed $periods
*/ */
public function testNOMINAL($expectedResult, ...$args): void public function testNOMINAL($expectedResult, $rate, $periods): void
{ {
$result = Financial::NOMINAL(...$args); $result = Financial::NOMINAL($rate, $periods);
self::assertEqualsWithDelta($expectedResult, $result, 1E-8); self::assertEqualsWithDelta($expectedResult, $result, 1E-8);
} }

View File

@ -16,6 +16,13 @@ return [
'2012-10-25', '2012-10-25',
4, 4,
], ],
[
71,
'2011-01-25',
'2011-11-15',
2,
1,
],
[ [
'#VALUE!', '#VALUE!',
'Invalid Date', 'Invalid Date',
@ -30,11 +37,165 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!',
'24-Dec-2000',
'24-Dec-2000',
4,
0,
],
[
311,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
317,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
317,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
317,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
317,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
310,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
131,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
133,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
133,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
133,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
133,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
130,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
41,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
42,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
42,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
42,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
42,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
40,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -51,11 +51,165 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!',
'24-Dec-2000',
'24-Dec-2000',
4,
0,
],
[
360,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
365,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
366,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
360,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
365,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
360,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
180,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
181,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
182,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
180,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
182.5,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
180,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
90,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
90,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
91,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
90,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
91.25,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
90,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -30,11 +30,186 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!',
'24-Dec-2000',
'24-Dec-2000',
4,
0,
],
[
49,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
49,
'01-Feb-2021',
'20-Mar-2021',
1,
0,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
49,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
50,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
49,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
49,
'01-Feb-2021',
'20-Mar-2021',
2,
0,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
49,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
50,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
49,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
49,
'01-Feb-2021',
'20-Mar-2021',
4,
0,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
49,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
48,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
50,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -30,14 +30,35 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
[ 'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!', '#NUM!',
'24-Dec-2000', '24-Dec-2000',
'24-Dec-2000', '24-Dec-2000',
@ -65,4 +86,130 @@ return [
4, 4,
0, 0,
], ],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
43910,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
43910,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
43910,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
44275,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -31,13 +31,41 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!',
'24-Dec-2000',
'24-Dec-2000',
4,
0,
],
[ [
5, 5,
'01-Jan-2008', '01-Jan-2008',
@ -108,4 +136,130 @@ return [
2, 2,
4, 4,
], ],
[
1,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
2,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
3,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
5,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
1,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -30,14 +30,35 @@ return [
2, 2,
1, 1,
], ],
[ 'Invalid Frequency' => [
'#NUM!', '#NUM!',
'25-Jan-2007', '25-Jan-2007',
'15-Nov-2008', '15-Nov-2008',
3, 3,
1, 1,
], ],
[ 'Non-Numeric Frequency' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
'NaN',
1,
],
'Invalid Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
-1,
],
'Non-Numeric Basis' => [
'#NUM!',
'25-Jan-2007',
'15-Nov-2008',
4,
'NaN',
],
'Same Date' => [
'#NUM!', '#NUM!',
'24-Dec-2000', '24-Dec-2000',
'24-Dec-2000', '24-Dec-2000',
@ -65,4 +86,130 @@ return [
4, 4,
0, 0,
], ],
[
43910,
'31-Jan-2021',
'20-Mar-2021',
1,
0,
],
[
43910,
'31-Jan-2021',
'20-Mar-2021',
1,
1,
],
[
43544,
'31-Jan-2020',
'20-Mar-2021',
1,
1,
],
[
43910,
'31-Jan-2021',
'20-Mar-2021',
1,
2,
],
[
43910,
'31-Jan-2021',
'20-Mar-2021',
1,
3,
],
[
43910,
'31-Jan-2021',
'20-Mar-2021',
1,
4,
],
[
44094,
'31-Jan-2021',
'20-Mar-2021',
2,
0,
],
[
44094,
'31-Jan-2021',
'20-Mar-2021',
2,
1,
],
[
43728,
'31-Jan-2020',
'20-Mar-2021',
2,
1,
],
[
44094,
'31-Jan-2021',
'20-Mar-2021',
2,
2,
],
[
44094,
'31-Jan-2021',
'20-Mar-2021',
2,
3,
],
[
44094,
'31-Jan-2021',
'20-Mar-2021',
2,
4,
],
[
44185,
'31-Jan-2021',
'20-Mar-2021',
4,
0,
],
[
44185,
'31-Jan-2021',
'20-Mar-2021',
4,
1,
],
[
43819,
'31-Jan-2020',
'20-Mar-2021',
4,
1,
],
[
44185,
'31-Jan-2021',
'20-Mar-2021',
4,
2,
],
[
44185,
'31-Jan-2021',
'20-Mar-2021',
4,
3,
],
[
44185,
'31-Jan-2021',
'20-Mar-2021',
4,
4,
],
]; ];

View File

@ -0,0 +1,15 @@
<?php
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [
[360, 2020, Helpers::DAYS_PER_YEAR_NASD],
[366, 2020, Helpers::DAYS_PER_YEAR_ACTUAL],
[365, 2021, Helpers::DAYS_PER_YEAR_ACTUAL],
[360, 2020, Helpers::DAYS_PER_YEAR_360],
[365, 2020, Helpers::DAYS_PER_YEAR_365],
[360, 2020, Helpers::DAYS_PER_YEAR_360_EUROPEAN],
[Functions::NAN(), 2020, 'Invalid'],
[Functions::NAN(), 2020, 999],
];

View File

@ -2,6 +2,8 @@
// nominal_rate, npery, Result // nominal_rate, npery, Result
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [ return [
[ [
0.053542667370758003, 0.053542667370758003,
@ -24,8 +26,23 @@ return [
2, 2,
], ],
[ [
'#NUM!', Functions::NAN(),
0.025,
-1,
],
[
Functions::NAN(),
-0.025,
1,
],
[
Functions::VALUE(),
0.025,
'NaN',
],
[
Functions::VALUE(),
'NaN',
1, 1,
0,
], ],
]; ];

View File

@ -2,6 +2,8 @@
// effect_rate, npery, result // effect_rate, npery, result
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [ return [
[ [
0.052500319868356002, 0.052500319868356002,
@ -24,8 +26,23 @@ return [
12, 12,
], ],
[ [
'#NUM!', Functions::NAN(),
-0.025000000000000001, -0.025,
12, 12,
], ],
[
Functions::NAN(),
0.025,
-12,
],
[
Functions::VALUE(),
'NaN',
12,
],
[
Functions::VALUE(),
0.025,
'NaN',
],
]; ];

View File

@ -1,16 +1,42 @@
<?php <?php
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [ return [
[ [
0.09415149356594302, 0.094151494,
'31-Mar-2008', '1-Jun-2008', 0.0914, '31-Mar-2008', '1-Jun-2008', 0.0914,
], ],
[ [
0.025465926064094112, Functions::VALUE(),
'Not a Valid Date', '1-Jun-2008', 0.09,
],
[
Functions::VALUE(),
'31-Mar-2008', 'Not a Valid Date', 0.09,
],
[
Functions::VALUE(),
'31-Mar-2008', '1-Jun-2008', 'NaN',
],
[
Functions::NAN(),
'31-Mar-2008', '1-Jun-2008', -0.09,
],
[
Functions::NAN(),
'31-Mar-2000', '1-Jun-2021', 0.09,
],
[
Functions::NAN(),
'1-Jun-2008', '31-Mar-2008', 0.09,
],
[
0.025465926,
'5-Feb-2019', '1-Feb-2020', 0.0245, '5-Feb-2019', '1-Feb-2020', 0.0245,
], ],
[ [
0.036787997465875716, 0.036787997,
'1-Feb-2016', '30-Jan-2017', 0.035, '1-Feb-2016', '30-Jan-2017', 0.035,
], ],
[ [

View File

@ -1,16 +1,50 @@
<?php <?php
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [ return [
[ [
98.45, 98.45,
'31-Mar-2008', '1-Jun-2008', 0.09, '31-Mar-2008', '1-Jun-2008', 0.09,
], ],
[
Functions::VALUE(),
'Not a Valid Date', '1-Jun-2008', 0.09,
],
[
Functions::VALUE(),
'31-Mar-2008', 'Not a Valid Date', 0.09,
],
[
Functions::VALUE(),
'31-Mar-2008', '1-Jun-2008', 'NaN',
],
[
Functions::NAN(),
'31-Mar-2008', '1-Jun-2008', -0.09,
],
[
Functions::NAN(),
'31-Mar-2000', '1-Jun-2021', 0.09,
],
[
Functions::NAN(),
'1-Jun-2008', '31-Mar-2008', 0.09,
],
[
0.0,
'31-Mar-2008', '1-Apr-2008', 360,
],
[
Functions::NAN(),
'31-Mar-2008', '1-Apr-2008', 361,
],
[ [
97.75, 97.75,
'1-Apr-2017', '30-Jun-2017', 0.09, '1-Apr-2017', '30-Jun-2017', 0.09,
], ],
[ [
97.54319444444445, 97.543194444,
'5-Feb-2019', '1-Feb-2020', 0.0245, '5-Feb-2019', '1-Feb-2020', 0.0245,
], ],
[ [

View File

@ -1,10 +1,36 @@
<?php <?php
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
return [ return [
[ [
0.09141696292534264, 0.09141696292534264,
'31-Mar-2008', '1-Jun-2008', 98.45, '31-Mar-2008', '1-Jun-2008', 98.45,
], ],
[
Functions::VALUE(),
'Not a Valid Date', '1-Jun-2008', 98.45,
],
[
Functions::VALUE(),
'31-Mar-2008', 'Not a Valid Date', 98.45,
],
[
Functions::VALUE(),
'31-Mar-2008', '1-Jun-2008', 'NaN',
],
[
Functions::NAN(),
'31-Mar-2008', '1-Jun-2008', -1.25,
],
[
Functions::NAN(),
'31-Mar-2000', '1-Jun-2021', 97.25,
],
[
Functions::NAN(),
'1-Jun-2008', '31-Mar-2008', 97.25,
],
[ [
0.024405125076266018, 0.024405125076266018,
'1-Feb-2017', '30-Jun-2017', 99, '1-Feb-2017', '30-Jun-2017', 99,