Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COUPNUM should not return zero when settlement is in the last period #1020

Closed
myfonj opened this issue Jun 18, 2019 · 2 comments
Closed

COUPNUM should not return zero when settlement is in the last period #1020

myfonj opened this issue Jun 18, 2019 · 2 comments

Comments

@myfonj
Copy link

myfonj commented Jun 18, 2019

Apparently what docs say ("rounded up to the nearest whole coupon") actually means that COUPNUM can never return 0.

=COUPNUM(DATE(2000;12;24);DATE(2000;12;24);4;0)
echo Financial::COUPNUM(date_create('2000-12-24'),date_create('2000-12-24'),4,0);
// settlement = maturity → Excel 2016 gives #NUMBER,  PHPOffice -1

=COUPNUM(DATE(2000;12;23);DATE(2000;12;24);4;0)
echo Financial::COUPNUM(date_create('2000-12-23'),date_create('2000-12-24'),4,0);
// settlement is one day before maturity → Excel 2016 gives 1, PHPOffice 0

=COUPNUM(DATE(2000;9;24);DATE(2000;12;24);4;0)
echo Financial::COUPNUM(date_create('2000-09-24'),date_create('2000-12-24'),4,0);
// settlement is on first coupon day before maturity → Excel 2016 gives 1, PHPOffice 0 

=COUPNUM(DATE(2000;9;23);DATE(2000;12;24);4;0)
echo Financial::COUPNUM(date_create('2000-09-23'),date_create('2000-12-24'),4,0);
// settlement is on day before first coupon day before maturity → Excel 2016 gives 2, PHPOffice 2 

Also, upon investigating it turned out current COUPNUM implementation somewhat lives in 365 year, or something.

/**
* 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])
*
* @category Financial Functions
*
* @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
* If working in Gnumeric Mode, the following frequency options are
* also available
* 6 Bimonthly
* 12 Monthly
* @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 = 0)
{
$settlement = Functions::flattenSingleValue($settlement);
$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();
}
$settlement = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true);
$daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * 365;
switch ($frequency) {
case 1: // annual payments
return ceil($daysBetweenSettlementAndMaturity / 360);
case 2: // half-yearly
return ceil($daysBetweenSettlementAndMaturity / 180);
case 4: // quarterly
return ceil($daysBetweenSettlementAndMaturity / 90);
case 6: // bimonthly
return ceil($daysBetweenSettlementAndMaturity / 60);
case 12: // monthly
return ceil($daysBetweenSettlementAndMaturity / 30);
}
return Functions::VALUE();
}

@MarkBaker
Copy link
Member

Fixed in master

@myfonj
Copy link
Author

myfonj commented Jul 11, 2019

Thanks for incorporating in #1068.

PowerKiKi added a commit that referenced this issue Aug 17, 2019
1.9.0

### Added

- When &lt;br&gt; appears in a table cell, set the cell to wrap [#1071](#1071) and [#1070](#1070)
- Add MAXIFS, MINIFS, COUNTIFS and Remove MINIF, MAXIF [#1056](#1056)
- HLookup needs an ordered list even if range_lookup is set to false [#1055](#1055) and [#1076](#1076)
- Improve performance of IF function calls via ranch pruning to avoid resolution of every branches [#844](#844)
- MATCH function supports `*?~` Excel functionality, when match_type=0 [#1116](#1116)
- Allow HTML Reader to accept HTML as a string [#1136](#1136)

### Fixed

- Fix to AVERAGEIF() function when called with a third argument
- Eliminate duplicate fill none style entries [#1066](#1066)
- Fix number format masks containing literal (non-decimal point) dots [#1079](#1079)
- Fix number format masks containing named colours that were being misinterpreted as date formats; and add support for masks that fully replace the value with a full text string [#1009](#1009)
- Stricter-typed comparison testing in COUNTIF() and COUNTIFS() evaluation [#1046](#1046)
- COUPNUM should not return zero when settlement is in the last period [#1020](#1020) and [#1021](#1021)
- Fix handling of named ranges referencing sheets with spaces or "!" in their title
- Cover `getSheetByName()` with tests for name with quote and spaces [#739](#739)
- Best effort to support invalid colspan values in HTML reader - [#878](#878)
- Fixes incorrect rows deletion [#868](#868)
- MATCH function fix (value search by type, stop search when match_type=-1 and unordered element encountered) [#1116](#1116)
- Fix `getCalculatedValue()` error with more than two INDIRECT [#1115](#1115)
- Writer\Html did not hide columns [#985](#985)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants