-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Closed
Description
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.
PhpSpreadsheet/src/PhpSpreadsheet/Calculation/Financial.php
Lines 622 to 693 in ca56fd2
| /** | |
| * 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(); | |
| } |
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels