Merge remote-tracking branch 'master' into fix_excel_overwrite
This commit is contained in:
commit
5e96f4292b
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
|
||||
- name: Delete composer lock file
|
||||
id: composer-lock
|
||||
if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }}
|
||||
if: ${{ matrix.php-version == '8.1' }}
|
||||
run: |
|
||||
rm composer.lock
|
||||
echo "::set-output name=flags::--ignore-platform-reqs"
|
||||
|
|
@ -124,6 +124,37 @@ jobs:
|
|||
- name: Code style with PHP_CodeSniffer
|
||||
run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr
|
||||
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP, with composer and extensions
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
|
||||
coverage: none
|
||||
tools: cs2pr
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- name: Cache composer dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Static analysis with PHPStan
|
||||
run: ./vendor/bin/phpstan analyse
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ return PhpCsFixer\Config::create()
|
|||
'php_unit_test_annotation' => true,
|
||||
'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
|
||||
'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage
|
||||
'phpdoc_add_missing_param_annotation' => true,
|
||||
'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value
|
||||
'phpdoc_align' => false, // Waste of time
|
||||
'phpdoc_annotation_without_dot' => true,
|
||||
'phpdoc_indent' => true,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### Added
|
||||
|
||||
- Implemented the CHITEST(), CHISQ.DIST() and CHISQ.INV() and equivalent Statistical functions, for both left- and right-tailed distributions.
|
||||
- Support for ActiveSheet and SelectedCells in the ODS Reader and Writer. [PR #1908](https://github.com/PHPOffice/PhpSpreadsheet/pull/1908)
|
||||
|
||||
### Changed
|
||||
|
|
@ -25,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with quoted strings in number format mask rendered with toFormattedString() [Issue 1972#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1972) [PR #1978](https://github.com/PHPOffice/PhpSpreadsheet/pull/1978)
|
||||
- Fixed issue with percentage formats in number format mask rendered with toFormattedString() [Issue 1929#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1929) [PR #1928](https://github.com/PHPOffice/PhpSpreadsheet/pull/1928)
|
||||
- Fixed issue with _ spacing character in number format mask corrupting output from toFormattedString() [Issue 1924#](https://github.com/PHPOffice/PhpSpreadsheet/issues/1924) [PR #1927](https://github.com/PHPOffice/PhpSpreadsheet/pull/1927)
|
||||
- Fix for [Issue #1887](https://github.com/PHPOffice/PhpSpreadsheet/issues/1887) - Lose Track of Selected Cells After Save
|
||||
- Fixed issue with Xlsx@listWorksheetInfo not returning any data
|
||||
- Fixed invalid arguments triggering mb_substr() error in LEFT(), MID() and RIGHT() text functions. [Issue #640](https://github.com/PHPOffice/PhpSpreadsheet/issues/640)
|
||||
|
|
|
|||
|
|
@ -9,3 +9,12 @@ If you would like to contribute, here are some notes and guidelines:
|
|||
- All code changes must be validated by `composer check`
|
||||
- [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository")
|
||||
- [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests")
|
||||
|
||||
## How to release
|
||||
|
||||
1. Complete CHANGELOG.md and commit
|
||||
2. Create an annotated tag
|
||||
1. `git tag -a 1.2.3`
|
||||
2. Tag subject must be the version number, eg: `1.2.3`
|
||||
3. Tag body must be a copy-paste of the changelog entries
|
||||
3. Push tag with `git push --tags`, GitHub Actions will create a GitHub release automatically
|
||||
|
|
|
|||
|
|
@ -1,7 +1,19 @@
|
|||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||
"keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls", "ods", "gnumeric", "spreadsheet"],
|
||||
"keywords": [
|
||||
"PHP",
|
||||
"OpenXML",
|
||||
"Excel",
|
||||
"xlsx",
|
||||
"xls",
|
||||
"ods",
|
||||
"gnumeric",
|
||||
"spreadsheet"
|
||||
],
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
|
|
@ -29,7 +41,8 @@
|
|||
"check": [
|
||||
"php-cs-fixer fix --ansi --dry-run --diff",
|
||||
"phpcs",
|
||||
"phpunit --color=always"
|
||||
"phpunit --color=always",
|
||||
"phpstan analyse --ansi"
|
||||
],
|
||||
"fix": [
|
||||
"php-cs-fixer fix --ansi"
|
||||
|
|
@ -39,35 +52,36 @@
|
|||
]
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2||^8.0",
|
||||
"php": "^7.2 || ^8.0",
|
||||
"ext-simplexml": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-SimpleXML": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlreader": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.13",
|
||||
"maennchen/zipstream-php": "^2.1",
|
||||
"markbaker/complex": "^1.5||^2.0",
|
||||
"markbaker/matrix": "^1.2||^2.0",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"markbaker/complex": "^2.0",
|
||||
"markbaker/matrix": "^2.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"ezyang/htmlpurifier": "^4.13"
|
||||
"psr/simple-cache": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dompdf/dompdf": "^0.8.5",
|
||||
"dompdf/dompdf": "^1.0",
|
||||
"friendsofphp/php-cs-fixer": "^2.18",
|
||||
"jpgraph/jpgraph": "^4.0",
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpunit/phpunit": "^8.5||^9.3",
|
||||
"phpstan/phpstan": "^0.12.82",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"tecnickcom/tcpdf": "^6.3"
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,16 @@
|
|||
parameters:
|
||||
level: 2
|
||||
paths:
|
||||
- src/
|
||||
- tests/
|
||||
ignoreErrors:
|
||||
- '~^Class GdImage not found\.$~'
|
||||
- '~^Return typehint of method .* has invalid type GdImage\.$~'
|
||||
- '~^Property .* has unknown class GdImage as its type\.$~'
|
||||
- '~^Parameter .* of method .* has invalid typehint type GdImage\.$~'
|
||||
|
||||
# Ignore all JpGraph issues
|
||||
- '~^Constant (MARK_CIRCLE|MARK_CROSS|MARK_DIAMOND|MARK_DTRIANGLE|MARK_FILLEDCIRCLE|MARK_SQUARE|MARK_STAR|MARK_UTRIANGLE|MARK_X|SIDE_RIGHT) not found\.$~'
|
||||
- '~^Instantiated class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot) not found\.$~'
|
||||
- '~^Call to method .*\(\) on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~'
|
||||
- '~^Access to property .* on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~'
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\NamedRange;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
require __DIR__ . '/../../Header.php';
|
||||
|
||||
$helper->log('Returns the cell specified by a text string.');
|
||||
|
||||
// Create new PhpSpreadsheet object
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$data = [
|
||||
[8, 9, 0],
|
||||
[3, 4, 5],
|
||||
[9, 1, 3],
|
||||
[4, 6, 2],
|
||||
];
|
||||
$worksheet->fromArray($data, null, 'C1');
|
||||
|
||||
$spreadsheet->addNamedRange(new NamedRange('NAMED_RANGE_FOR_CELL_D4', $worksheet, '="$D$4"'));
|
||||
|
||||
$worksheet->getCell('A1')->setValue('=INDIRECT("C1")');
|
||||
$worksheet->getCell('A2')->setValue('=INDIRECT("D"&4)');
|
||||
$worksheet->getCell('A3')->setValue('=INDIRECT("E"&ROW())');
|
||||
$worksheet->getCell('A4')->setValue('=SUM(INDIRECT("$C$4:$E$4"))');
|
||||
$worksheet->getCell('A5')->setValue('=INDIRECT(NAMED_RANGE_FOR_CELL_D4)');
|
||||
|
||||
for ($row = 1; $row <= 5; ++$row) {
|
||||
$cell = $worksheet->getCell("A{$row}");
|
||||
$helper->log("A{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
|
||||
require __DIR__ . '/../../Header.php';
|
||||
|
||||
$helper->log('Returns a cell range that is a specified number of rows and columns from a cell or range of cells.');
|
||||
|
||||
// Create new PhpSpreadsheet object
|
||||
$spreadsheet = new Spreadsheet();
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
|
||||
$data = [
|
||||
[null, 'Week 1', 'Week 2', 'Week 3', 'Week 4'],
|
||||
['Sunday', 4500, 2200, 3800, 1500],
|
||||
['Monday', 5500, 6100, 5200, 4800],
|
||||
['Tuesday', 7000, 6200, 5000, 7100],
|
||||
['Wednesday', 8000, 4000, 3900, 7600],
|
||||
['Thursday', 5900, 5500, 6900, 7100],
|
||||
['Friday', 4900, 6300, 6900, 5200],
|
||||
['Saturday', 3500, 3900, 5100, 4100],
|
||||
];
|
||||
$worksheet->fromArray($data, null, 'A3');
|
||||
|
||||
$worksheet->getCell('H1')->setValue('=OFFSET(A3, 3, 1)');
|
||||
$worksheet->getCell('H2')->setValue('=SUM(OFFSET(A3, 3, 1, 1, 4))');
|
||||
$worksheet->getCell('H3')->setValue('=SUM(OFFSET(B3:E3, 3, 0))');
|
||||
$worksheet->getCell('H4')->setValue('=SUM(OFFSET(E3, 1, -3, 7))');
|
||||
|
||||
for ($row = 1; $row <= 4; ++$row) {
|
||||
$cell = $worksheet->getCell("H{$row}");
|
||||
$helper->log("H{$row}: {$cell->getValue()} => {$cell->getCalculatedValue()}");
|
||||
}
|
||||
|
|
@ -5,6 +5,11 @@ use PhpOffice\PhpSpreadsheet\Settings;
|
|||
|
||||
require __DIR__ . '/../Header.php';
|
||||
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
$helper->log('Jpgraph no longer runs against PHP8');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Change these values to select the Rendering library that you wish to use
|
||||
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class);
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -245,7 +245,7 @@ class Database
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DPRODUCT($database, $field, $criteria)
|
||||
{
|
||||
|
|
@ -349,7 +349,7 @@ class Database
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DSUM($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class DAverage extends DatabaseAbstract
|
|||
return null;
|
||||
}
|
||||
|
||||
return Averages::AVERAGE(
|
||||
return Averages::average(
|
||||
self::getFilteredColumn($database, $field, $criteria)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DMax extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DMin extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DProduct extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
@ -38,7 +38,7 @@ class DProduct extends DatabaseAbstract
|
|||
return null;
|
||||
}
|
||||
|
||||
return MathTrig::PRODUCT(
|
||||
return MathTrig\Product::funcProduct(
|
||||
self::getFilteredColumn($database, $field, $criteria)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DStDev extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float|string
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DStDevP extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float|string
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DSum extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float
|
||||
* @return null|float|string
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
@ -38,7 +38,7 @@ class DSum extends DatabaseAbstract
|
|||
return null;
|
||||
}
|
||||
|
||||
return MathTrig::SUM(
|
||||
return MathTrig\Sum::funcSum(
|
||||
self::getFilteredColumn($database, $field, $criteria)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DVar extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
* @return null|float|string (string if result is an error)
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class DVarP extends DatabaseAbstract
|
|||
* the column label in which you specify a condition for the
|
||||
* column.
|
||||
*
|
||||
* @return float|string (string if result is an error)
|
||||
* @return null|float|string (string if result is an error)
|
||||
*/
|
||||
public static function evaluate($database, $field, $criteria)
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
class Constants
|
||||
{
|
||||
// Constants currently used by WeekNum; will eventually be used by WEEKDAY
|
||||
const STARTWEEK_SUNDAY = 1;
|
||||
const STARTWEEK_MONDAY = 2;
|
||||
const STARTWEEK_MONDAY_ALT = 11;
|
||||
const STARTWEEK_TUESDAY = 12;
|
||||
const STARTWEEK_WEDNESDAY = 13;
|
||||
const STARTWEEK_THURSDAY = 14;
|
||||
const STARTWEEK_FRIDAY = 15;
|
||||
const STARTWEEK_SATURDAY = 16;
|
||||
const STARTWEEK_SUNDAY_ALT = 17;
|
||||
const DOW_SUNDAY = 1;
|
||||
const DOW_MONDAY = 2;
|
||||
const DOW_TUESDAY = 3;
|
||||
const DOW_WEDNESDAY = 4;
|
||||
const DOW_THURSDAY = 5;
|
||||
const DOW_FRIDAY = 6;
|
||||
const DOW_SATURDAY = 7;
|
||||
const STARTWEEK_MONDAY_ISO = 21;
|
||||
const METHODARR = [
|
||||
self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
|
||||
self::DOW_MONDAY,
|
||||
self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
|
||||
self::DOW_TUESDAY,
|
||||
self::DOW_WEDNESDAY,
|
||||
self::DOW_THURSDAY,
|
||||
self::DOW_FRIDAY,
|
||||
self::DOW_SATURDAY,
|
||||
self::DOW_SUNDAY,
|
||||
self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class DateDif
|
||||
{
|
||||
/**
|
||||
* DATEDIF.
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object
|
||||
* or a standard date string
|
||||
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
|
||||
* or a standard date string
|
||||
* @param string $unit
|
||||
*
|
||||
* @return int|string Interval between the dates
|
||||
*/
|
||||
public static function funcDateDif($startDate, $endDate, $unit = 'D')
|
||||
{
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDate = Helpers::getDateValue($endDate);
|
||||
$difference = self::initialDiff($startDate, $endDate);
|
||||
$unit = strtoupper(Functions::flattenSingleValue($unit));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = Date::excelToDateTimeObject($startDate);
|
||||
$startDays = (int) $PHPStartDateObject->format('j');
|
||||
$startMonths = (int) $PHPStartDateObject->format('n');
|
||||
$startYears = (int) $PHPStartDateObject->format('Y');
|
||||
|
||||
$PHPEndDateObject = Date::excelToDateTimeObject($endDate);
|
||||
$endDays = (int) $PHPEndDateObject->format('j');
|
||||
$endMonths = (int) $PHPEndDateObject->format('n');
|
||||
$endYears = (int) $PHPEndDateObject->format('Y');
|
||||
|
||||
$PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject);
|
||||
|
||||
$retVal = false;
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject);
|
||||
|
||||
return is_bool($retVal) ? Functions::VALUE() : $retVal;
|
||||
}
|
||||
|
||||
private static function initialDiff(float $startDate, float $endDate): float
|
||||
{
|
||||
// Validate parameters
|
||||
if ($startDate > $endDate) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $endDate - $startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether it's time to set retVal.
|
||||
*
|
||||
* @param bool|int $retVal
|
||||
*
|
||||
* @return null|bool|int
|
||||
*/
|
||||
private static function replaceRetValue($retVal, string $unit, string $compare)
|
||||
{
|
||||
if ($retVal !== false || $unit !== $compare) {
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function datedifD(float $difference): int
|
||||
{
|
||||
return (int) $difference;
|
||||
}
|
||||
|
||||
private static function datedifM(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m');
|
||||
}
|
||||
|
||||
private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
if ($endDays < $startDays) {
|
||||
$retVal = $endDays;
|
||||
$PHPEndDateObject->modify('-' . $endDays . ' days');
|
||||
$adjustDays = (int) $PHPEndDateObject->format('j');
|
||||
$retVal += ($adjustDays - $startDays);
|
||||
} else {
|
||||
$retVal = (int) $PHPDiffDateObject->format('%d');
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
private static function datedifY(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return (int) $PHPDiffDateObject->format('%y');
|
||||
}
|
||||
|
||||
private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int
|
||||
{
|
||||
$retVal = (int) $difference;
|
||||
if ($endYears > $startYears) {
|
||||
$isLeapStartYear = $PHPStartDateObject->format('L');
|
||||
$wasLeapEndYear = $PHPEndDateObject->format('L');
|
||||
|
||||
// Adjust end year to be as close as possible as start year
|
||||
while ($PHPEndDateObject >= $PHPStartDateObject) {
|
||||
$PHPEndDateObject->modify('-1 year');
|
||||
$endYears = $PHPEndDateObject->format('Y');
|
||||
}
|
||||
$PHPEndDateObject->modify('+1 year');
|
||||
|
||||
// Get the result
|
||||
$retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days;
|
||||
|
||||
// Adjust for leap years cases
|
||||
$isLeapEndYear = $PHPEndDateObject->format('L');
|
||||
$limit = new DateTime($PHPEndDateObject->format('Y-02-29'));
|
||||
if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) {
|
||||
--$retVal;
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $retVal;
|
||||
}
|
||||
|
||||
private static function datedifYM(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return (int) $PHPDiffDateObject->format('%m');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class DateValue
|
||||
{
|
||||
/**
|
||||
* DATEVALUE.
|
||||
*
|
||||
* Returns a value that represents a particular date.
|
||||
* Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp
|
||||
* value.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* DATEVALUE(dateValue)
|
||||
*
|
||||
* @param string $dateValue Text that represents a date in a Microsoft Excel date format.
|
||||
* For example, "1/30/2008" or "30-Jan-2008" are text strings within
|
||||
* quotation marks that represent dates. Using the default date
|
||||
* system in Excel for Windows, date_text must represent a date from
|
||||
* January 1, 1900, to December 31, 9999. Using the default date
|
||||
* system in Excel for the Macintosh, date_text must represent a date
|
||||
* from January 1, 1904, to December 31, 9999. DATEVALUE returns the
|
||||
* #VALUE! error value if date_text is out of this range.
|
||||
*
|
||||
* @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 funcDateValue($dateValue)
|
||||
{
|
||||
$dti = new DateTimeImmutable();
|
||||
$baseYear = Date::getExcelCalendar();
|
||||
$dateValue = trim(Functions::flattenSingleValue($dateValue), '"');
|
||||
// Strip any ordinals because they're allowed in Excel (English only)
|
||||
$dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
|
||||
// Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
|
||||
$dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue);
|
||||
|
||||
$yearFound = false;
|
||||
$t1 = explode(' ', $dateValue);
|
||||
$t = '';
|
||||
foreach ($t1 as &$t) {
|
||||
if ((is_numeric($t)) && ($t > 31)) {
|
||||
if ($yearFound) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
if ($t < 100) {
|
||||
$t += 1900;
|
||||
}
|
||||
$yearFound = true;
|
||||
}
|
||||
}
|
||||
if (count($t1) === 1) {
|
||||
// We've been fed a time value without any date
|
||||
return ((strpos($t, ':') === false)) ? Functions::Value() : 0.0;
|
||||
}
|
||||
unset($t);
|
||||
|
||||
$dateValue = self::t1ToString($t1, $dti, $yearFound);
|
||||
|
||||
$PHPDateArray = self::setUpArray($dateValue, $dti);
|
||||
|
||||
return self::finalResults($PHPDateArray, $dti, $baseYear);
|
||||
}
|
||||
|
||||
private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string
|
||||
{
|
||||
if (count($t1) == 2) {
|
||||
// We only have two parts of the date: either day/month or month/year
|
||||
if ($yearFound) {
|
||||
array_unshift($t1, 1);
|
||||
} else {
|
||||
if (is_numeric($t1[1]) && $t1[1] > 29) {
|
||||
$t1[1] += 1900;
|
||||
array_unshift($t1, 1);
|
||||
} else {
|
||||
$t1[] = $dti->format('Y');
|
||||
}
|
||||
}
|
||||
}
|
||||
$dateValue = implode(' ', $t1);
|
||||
|
||||
return $dateValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse date.
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
private static function setUpArray(string $dateValue, DateTimeImmutable $dti)
|
||||
{
|
||||
$PHPDateArray = date_parse($dateValue);
|
||||
if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) {
|
||||
// If original count was 1, we've already returned.
|
||||
// If it was 2, we added another.
|
||||
// Therefore, neither of the first 2 stroks below can fail.
|
||||
$testVal1 = strtok($dateValue, '- ');
|
||||
$testVal2 = strtok('- ');
|
||||
$testVal3 = strtok('- ') ?: $dti->format('Y');
|
||||
Helpers::adjustYear($testVal1, $testVal2, $testVal3);
|
||||
$PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3);
|
||||
if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) {
|
||||
$PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3);
|
||||
}
|
||||
}
|
||||
|
||||
return $PHPDateArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final results.
|
||||
*
|
||||
* @param array|false $PHPDateArray
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
private static function finalResults($PHPDateArray, DateTimeImmutable $dti, int $baseYear)
|
||||
{
|
||||
$retValue = Functions::Value();
|
||||
if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) {
|
||||
// Execute function
|
||||
Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
|
||||
if ($PHPDateArray['year'] < $baseYear) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
|
||||
Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
|
||||
$PHPDateArray['hour'] = 0;
|
||||
$PHPDateArray['minute'] = 0;
|
||||
$PHPDateArray['second'] = 0;
|
||||
$month = (int) $PHPDateArray['month'];
|
||||
$day = (int) $PHPDateArray['day'];
|
||||
$year = (int) $PHPDateArray['year'];
|
||||
if (!checkdate($month, $day, $year)) {
|
||||
return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : Functions::VALUE();
|
||||
}
|
||||
$retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true);
|
||||
}
|
||||
|
||||
return $retValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Datefunc
|
||||
{
|
||||
/**
|
||||
* DATE.
|
||||
*
|
||||
* The DATE function returns a value that represents a particular date.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* DATE(year,month,day)
|
||||
*
|
||||
* PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function.
|
||||
* A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
|
||||
* as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
|
||||
*
|
||||
* @param int $year The value of the year argument can include one to four digits.
|
||||
* Excel interprets the year argument according to the configured
|
||||
* date system: 1900 or 1904.
|
||||
* If year is between 0 (zero) and 1899 (inclusive), Excel adds that
|
||||
* value to 1900 to calculate the year. For example, DATE(108,1,2)
|
||||
* returns January 2, 2008 (1900+108).
|
||||
* If year is between 1900 and 9999 (inclusive), Excel uses that
|
||||
* value as the year. For example, DATE(2008,1,2) returns January 2,
|
||||
* 2008.
|
||||
* If year is less than 0 or is 10000 or greater, Excel returns the
|
||||
* #NUM! error value.
|
||||
* @param int $month A positive or negative integer representing the month of the year
|
||||
* from 1 to 12 (January to December).
|
||||
* If month is greater than 12, month adds that number of months to
|
||||
* the first month in the year specified. For example, DATE(2008,14,2)
|
||||
* returns the serial number representing February 2, 2009.
|
||||
* If month is less than 1, month subtracts the magnitude of that
|
||||
* number of months, plus 1, from the first month in the year
|
||||
* specified. For example, DATE(2008,-3,2) returns the serial number
|
||||
* representing September 2, 2007.
|
||||
* @param int $day A positive or negative integer representing the day of the month
|
||||
* from 1 to 31.
|
||||
* If day is greater than the number of days in the month specified,
|
||||
* day adds that number of days to the first day in the month. For
|
||||
* example, DATE(2008,1,35) returns the serial number representing
|
||||
* February 4, 2008.
|
||||
* If day is less than 1, day subtracts the magnitude that number of
|
||||
* days, plus one, from the first day of the month specified. For
|
||||
* example, DATE(2008,1,-15) returns the serial number representing
|
||||
* December 16, 2007.
|
||||
*
|
||||
* @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 funcDate($year, $month, $day)
|
||||
{
|
||||
$baseYear = Date::getExcelCalendar();
|
||||
|
||||
try {
|
||||
$year = self::getYear($year, $baseYear);
|
||||
$month = self::getMonth($month);
|
||||
$day = self::getDay($day);
|
||||
self::adjustYearMonth($year, $month, $baseYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$excelDateValue = Date::formattedPHPToExcel($year, $month, $day);
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert year from multiple formats to int.
|
||||
*
|
||||
* @param mixed $year
|
||||
*/
|
||||
private static function getYear($year, int $baseYear): int
|
||||
{
|
||||
$year = Functions::flattenSingleValue($year);
|
||||
$year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0;
|
||||
if (!is_numeric($year)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
$year = (int) $year;
|
||||
|
||||
if ($year < ($baseYear - 1900)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
|
||||
$year += 1900;
|
||||
}
|
||||
|
||||
return $year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert month from multiple formats to int.
|
||||
*
|
||||
* @param mixed $month
|
||||
*/
|
||||
private static function getMonth($month): int
|
||||
{
|
||||
$month = Functions::flattenSingleValue($month);
|
||||
|
||||
if (($month !== null) && (!is_numeric($month))) {
|
||||
$month = Date::monthStringToNumber($month);
|
||||
}
|
||||
|
||||
$month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0;
|
||||
if (!is_numeric($month)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return (int) $month;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert day from multiple formats to int.
|
||||
*
|
||||
* @param mixed $day
|
||||
*/
|
||||
private static function getDay($day): int
|
||||
{
|
||||
$day = Functions::flattenSingleValue($day);
|
||||
|
||||
if (($day !== null) && (!is_numeric($day))) {
|
||||
$day = Date::dayStringToNumber($day);
|
||||
}
|
||||
|
||||
$day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0;
|
||||
if (!is_numeric($day)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return (int) $day;
|
||||
}
|
||||
|
||||
private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void
|
||||
{
|
||||
if ($month < 1) {
|
||||
// Handle year/month adjustment if month < 1
|
||||
--$month;
|
||||
$year += ceil($month / 12) - 1;
|
||||
$month = 13 - abs($month % 12);
|
||||
} elseif ($month > 12) {
|
||||
// Handle year/month adjustment if month > 12
|
||||
$year += floor($month / 12);
|
||||
$month = ($month % 12);
|
||||
}
|
||||
|
||||
// Re-validate the year parameter after adjustments
|
||||
if (($year < $baseYear) || ($year >= 10000)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Day
|
||||
{
|
||||
/**
|
||||
* DAYOFMONTH.
|
||||
*
|
||||
* Returns the day of the month, for a specified date. The day is given as an integer
|
||||
* ranging from 1 to 31.
|
||||
*
|
||||
* Excel Function:
|
||||
* DAY(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Day of the month
|
||||
*/
|
||||
public static function funcDay($dateValue)
|
||||
{
|
||||
$weirdResult = self::weirdCondition($dateValue);
|
||||
if ($weirdResult >= 0) {
|
||||
return $weirdResult;
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
|
||||
return (int) $PHPDateObject->format('j');
|
||||
}
|
||||
|
||||
private static function weirdCondition($dateValue): int
|
||||
{
|
||||
// Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR)
|
||||
if (Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
|
||||
if (is_bool($dateValue)) {
|
||||
return (int) $dateValue;
|
||||
}
|
||||
if ($dateValue === null) {
|
||||
return 0;
|
||||
}
|
||||
if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Days
|
||||
{
|
||||
/**
|
||||
* DAYS.
|
||||
*
|
||||
* Returns the number of days between two dates
|
||||
*
|
||||
* Excel Function:
|
||||
* DAYS(endDate, startDate)
|
||||
*
|
||||
* @param DateTimeInterface|float|int|string $endDate Excel date serial value (float),
|
||||
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
|
||||
* @param DateTimeInterface|float|int|string $startDate Excel date serial value (float),
|
||||
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Number of days between start date and end date or an error
|
||||
*/
|
||||
public static function funcDays($endDate, $startDate)
|
||||
{
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDate = Helpers::getDateValue($endDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = Date::excelToDateTimeObject($startDate);
|
||||
$PHPEndDateObject = Date::excelToDateTimeObject($endDate);
|
||||
|
||||
$days = Functions::VALUE();
|
||||
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
|
||||
if ($diff !== false && !is_bool($diff->days)) {
|
||||
$days = $diff->days;
|
||||
if ($diff->invert) {
|
||||
$days = -$days;
|
||||
}
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Days360
|
||||
{
|
||||
/**
|
||||
* DAYS360.
|
||||
*
|
||||
* Returns the number of days between two dates based on a 360-day year (twelve 30-day months),
|
||||
* which is used in some accounting calculations. Use this function to help compute payments if
|
||||
* your accounting system is based on twelve 30-day months.
|
||||
*
|
||||
* Excel Function:
|
||||
* DAYS360(startDate,endDate[,method])
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param mixed $method US or European Method as a bool
|
||||
* FALSE or omitted: U.S. (NASD) method. If the starting date is
|
||||
* the last day of a month, it becomes equal to the 30th of the
|
||||
* same month. If the ending date is the last day of a month and
|
||||
* the starting date is earlier than the 30th of a month, the
|
||||
* ending date becomes equal to the 1st of the next month;
|
||||
* otherwise the ending date becomes equal to the 30th of the
|
||||
* same month.
|
||||
* TRUE: European method. Starting dates and ending dates that
|
||||
* occur on the 31st of a month become equal to the 30th of the
|
||||
* same month.
|
||||
*
|
||||
* @return int|string Number of days between start date and end date
|
||||
*/
|
||||
public static function funcDays360($startDate = 0, $endDate = 0, $method = false)
|
||||
{
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDate = Helpers::getDateValue($endDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (!is_bool($method)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = Date::excelToDateTimeObject($startDate);
|
||||
$startDay = $PHPStartDateObject->format('j');
|
||||
$startMonth = $PHPStartDateObject->format('n');
|
||||
$startYear = $PHPStartDateObject->format('Y');
|
||||
|
||||
$PHPEndDateObject = Date::excelToDateTimeObject($endDate);
|
||||
$endDay = $PHPEndDateObject->format('j');
|
||||
$endMonth = $PHPEndDateObject->format('n');
|
||||
$endYear = $PHPEndDateObject->format('Y');
|
||||
|
||||
return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of days between two dates based on a 360 day calendar.
|
||||
*/
|
||||
private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int
|
||||
{
|
||||
$startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS);
|
||||
$endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS);
|
||||
|
||||
return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
|
||||
}
|
||||
|
||||
private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int
|
||||
{
|
||||
if ($startDay == 31) {
|
||||
--$startDay;
|
||||
} elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) {
|
||||
$startDay = 30;
|
||||
}
|
||||
|
||||
return $startDay;
|
||||
}
|
||||
|
||||
private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int
|
||||
{
|
||||
if ($endDay == 31) {
|
||||
if ($methodUS && $startDay != 30) {
|
||||
$endDay = 1;
|
||||
if ($endMonth == 12) {
|
||||
++$endYear;
|
||||
$endMonth = 1;
|
||||
} else {
|
||||
++$endMonth;
|
||||
}
|
||||
} else {
|
||||
$endDay = 30;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDay;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class EDate
|
||||
{
|
||||
/**
|
||||
* EDATE.
|
||||
*
|
||||
* Returns the serial number that represents the date that is the indicated number of months
|
||||
* before or after a specified date (the start_date).
|
||||
* Use EDATE to calculate maturity dates or due dates that fall on the same day of the month
|
||||
* as the date of issue.
|
||||
*
|
||||
* Excel Function:
|
||||
* EDATE(dateValue,adjustmentMonths)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $adjustmentMonths The number of months before or after start_date.
|
||||
* A positive value for months yields a future date;
|
||||
* a negative value yields a past date.
|
||||
*
|
||||
* @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 funcEDate($dateValue, $adjustmentMonths)
|
||||
{
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue, false);
|
||||
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths);
|
||||
|
||||
return Helpers::returnIn3FormatsObject($PHPDateObject);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class EoMonth
|
||||
{
|
||||
/**
|
||||
* EOMONTH.
|
||||
*
|
||||
* Returns the date value for the last day of the month that is the indicated number of months
|
||||
* before or after start_date.
|
||||
* Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
|
||||
*
|
||||
* Excel Function:
|
||||
* EOMONTH(dateValue,adjustmentMonths)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $adjustmentMonths The number of months before or after start_date.
|
||||
* A positive value for months yields a future date;
|
||||
* a negative value yields a past date.
|
||||
*
|
||||
* @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 funcEoMonth($dateValue, $adjustmentMonths)
|
||||
{
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue, false);
|
||||
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1);
|
||||
$adjustDays = (int) $PHPDateObject->format('d');
|
||||
$adjustDaysString = '-' . $adjustDays . ' days';
|
||||
$PHPDateObject->modify($adjustDaysString);
|
||||
|
||||
return Helpers::returnIn3FormatsObject($PHPDateObject);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Identify if a year is a leap year or not.
|
||||
*
|
||||
* @param int|string $year The year to test
|
||||
*
|
||||
* @return bool TRUE if the year is a leap year, otherwise FALSE
|
||||
*/
|
||||
public static function isLeapYear($year)
|
||||
{
|
||||
return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* getDateValue.
|
||||
*
|
||||
* @param mixed $dateValue
|
||||
*
|
||||
* @return float Excel date/time serial value
|
||||
*/
|
||||
public static function getDateValue($dateValue, bool $allowBool = true)
|
||||
{
|
||||
if (is_object($dateValue)) {
|
||||
$retval = Date::PHPToExcel($dateValue);
|
||||
if (is_bool($retval)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
self::nullFalseTrueToNumber($dateValue, $allowBool);
|
||||
if (!is_numeric($dateValue)) {
|
||||
$saveReturnDateType = Functions::getReturnDateType();
|
||||
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||
$dateValue = DateValue::funcDateValue($dateValue);
|
||||
Functions::setReturnDateType($saveReturnDateType);
|
||||
if (!is_numeric($dateValue)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
}
|
||||
if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return (float) $dateValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDateValueNoThrow.
|
||||
*
|
||||
* @param mixed $dateValue
|
||||
*
|
||||
* @return mixed Excel date/time serial value, or string if error
|
||||
*/
|
||||
public static function getDateValueNoThrow($dateValue)
|
||||
{
|
||||
try {
|
||||
return self::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getTimeValue.
|
||||
*
|
||||
* @param string $timeValue
|
||||
*
|
||||
* @return mixed Excel date/time serial value, or string if error
|
||||
*/
|
||||
public static function getTimeValue($timeValue)
|
||||
{
|
||||
$saveReturnDateType = Functions::getReturnDateType();
|
||||
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||
$timeValue = TimeValue::funcTimeValue($timeValue);
|
||||
Functions::setReturnDateType($saveReturnDateType);
|
||||
|
||||
return $timeValue;
|
||||
}
|
||||
|
||||
public static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0)
|
||||
{
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
$oMonth = (int) $PHPDateObject->format('m');
|
||||
$oYear = (int) $PHPDateObject->format('Y');
|
||||
|
||||
$adjustmentMonthsString = (string) $adjustmentMonths;
|
||||
if ($adjustmentMonths > 0) {
|
||||
$adjustmentMonthsString = '+' . $adjustmentMonths;
|
||||
}
|
||||
if ($adjustmentMonths != 0) {
|
||||
$PHPDateObject->modify($adjustmentMonthsString . ' months');
|
||||
}
|
||||
$nMonth = (int) $PHPDateObject->format('m');
|
||||
$nYear = (int) $PHPDateObject->format('Y');
|
||||
|
||||
$monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
|
||||
if ($monthDiff != $adjustmentMonths) {
|
||||
$adjustDays = (int) $PHPDateObject->format('d');
|
||||
$adjustDaysString = '-' . $adjustDays . ' days';
|
||||
$PHPDateObject->modify($adjustDaysString);
|
||||
}
|
||||
|
||||
return $PHPDateObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Help reduce perceived complexity of some tests.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $altValue
|
||||
*/
|
||||
public static function replaceIfEmpty(&$value, $altValue): void
|
||||
{
|
||||
$value = $value ?: $altValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust year in ambiguous situations.
|
||||
*/
|
||||
public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
|
||||
{
|
||||
if (!is_numeric($testVal1) || $testVal1 < 31) {
|
||||
if (!is_numeric($testVal2) || $testVal2 < 12) {
|
||||
if (is_numeric($testVal3) && $testVal3 < 12) {
|
||||
$testVal3 += 2000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false)
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
|
||||
return new DateTime(
|
||||
$dateArray['year']
|
||||
. '-' . $dateArray['month']
|
||||
. '-' . $dateArray['day']
|
||||
. ' ' . $dateArray['hour']
|
||||
. ':' . $dateArray['minute']
|
||||
. ':' . $dateArray['second']
|
||||
);
|
||||
}
|
||||
$excelDateValue =
|
||||
Date::formattedPHPToExcel(
|
||||
$dateArray['year'],
|
||||
$dateArray['month'],
|
||||
$dateArray['day'],
|
||||
$dateArray['hour'],
|
||||
$dateArray['minute'],
|
||||
$dateArray['second']
|
||||
);
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return $noFrac ? floor($excelDateValue) : (float) $excelDateValue;
|
||||
}
|
||||
// RETURNDATE_UNIX_TIMESTAMP)
|
||||
|
||||
return (int) Date::excelToTimestamp($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function returnIn3FormatsFloat(float $excelDateValue)
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return $excelDateValue;
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
return (int) Date::excelToTimestamp($excelDateValue);
|
||||
}
|
||||
// RETURNDATE_PHP_DATETIME_OBJECT
|
||||
|
||||
return Date::excelToDateTimeObject($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function returnIn3FormatsObject(DateTime $PHPDateObject)
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
|
||||
return $PHPDateObject;
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return (float) Date::PHPToExcel($PHPDateObject);
|
||||
}
|
||||
// RETURNDATE_UNIX_TIMESTAMP
|
||||
|
||||
return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject));
|
||||
}
|
||||
|
||||
private static function baseDate(): int
|
||||
{
|
||||
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return 0;
|
||||
}
|
||||
if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null/false/true argument treated as 0/0/1.
|
||||
*
|
||||
* @param mixed $number
|
||||
*/
|
||||
public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
$nullVal = self::baseDate();
|
||||
if ($number === null) {
|
||||
$number = $nullVal;
|
||||
} elseif ($allowBool && is_bool($number)) {
|
||||
$number = $nullVal + (int) $number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null argument treated as 0.
|
||||
*
|
||||
* @param mixed $number
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
public static function validateNumericNull($number)
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
if ($number === null) {
|
||||
return 0;
|
||||
}
|
||||
if (is_numeric($number)) {
|
||||
return $number;
|
||||
}
|
||||
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null/false/true argument treated as 0/0/1.
|
||||
*
|
||||
* @param mixed $number
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function validateNotNegative($number)
|
||||
{
|
||||
if (!is_numeric($number)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
if ($number >= 0) {
|
||||
return (float) $number;
|
||||
}
|
||||
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
|
||||
{
|
||||
$isoDate = $PHPDateObject->format('c');
|
||||
if ($isoDate < '1900-03-01') {
|
||||
$PHPDateObject->modify($mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Hour
|
||||
{
|
||||
/**
|
||||
* HOUROFDAY.
|
||||
*
|
||||
* Returns the hour of a time value.
|
||||
* The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.).
|
||||
*
|
||||
* Excel Function:
|
||||
* HOUR(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int|string Hour
|
||||
*/
|
||||
public static function funcHour($timeValue)
|
||||
{
|
||||
try {
|
||||
$timeValue = Functions::flattenSingleValue($timeValue);
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = Date::excelToDateTimeObject($timeValue);
|
||||
|
||||
return (int) $timeValue->format('H');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class IsoWeekNum
|
||||
{
|
||||
/**
|
||||
* ISOWEEKNUM.
|
||||
*
|
||||
* Returns the ISO 8601 week number of the year for a specified date.
|
||||
*
|
||||
* Excel Function:
|
||||
* ISOWEEKNUM(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Week Number
|
||||
*/
|
||||
public static function funcIsoWeekNum($dateValue)
|
||||
{
|
||||
if (self::apparentBug($dateValue)) {
|
||||
return 52;
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('W');
|
||||
}
|
||||
|
||||
private static function apparentBug($dateValue): bool
|
||||
{
|
||||
if (Date::getExcelCalendar() !== DATE::CALENDAR_MAC_1904) {
|
||||
if (is_bool($dateValue)) {
|
||||
return true;
|
||||
}
|
||||
if (is_numeric($dateValue) && !((int) $dateValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Minute
|
||||
{
|
||||
/**
|
||||
* MINUTE.
|
||||
*
|
||||
* Returns the minutes of a time value.
|
||||
* The minute is given as an integer, ranging from 0 to 59.
|
||||
*
|
||||
* Excel Function:
|
||||
* MINUTE(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int|string Minute
|
||||
*/
|
||||
public static function funcMinute($timeValue)
|
||||
{
|
||||
try {
|
||||
$timeValue = Functions::flattenSingleValue($timeValue);
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = Date::excelToDateTimeObject($timeValue);
|
||||
|
||||
return (int) $timeValue->format('i');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Month
|
||||
{
|
||||
/**
|
||||
* MONTHOFYEAR.
|
||||
*
|
||||
* Returns the month of a date represented by a serial number.
|
||||
* The month is given as an integer, ranging from 1 (January) to 12 (December).
|
||||
*
|
||||
* Excel Function:
|
||||
* MONTH(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Month of the year
|
||||
*/
|
||||
public static function funcMonth($dateValue)
|
||||
{
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
|
||||
return (int) $PHPDateObject->format('n');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class NetworkDays
|
||||
{
|
||||
/**
|
||||
* NETWORKDAYS.
|
||||
*
|
||||
* Returns the number of whole working days between start_date and end_date. Working days
|
||||
* exclude weekends and any dates identified in holidays.
|
||||
* Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days
|
||||
* worked during a specific term.
|
||||
*
|
||||
* Excel Function:
|
||||
* NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]])
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Interval between the dates
|
||||
*/
|
||||
public static function funcNetworkDays($startDate, $endDate, ...$dateArgs)
|
||||
{
|
||||
try {
|
||||
// Retrieve the mandatory start and end date that are referenced in the function definition
|
||||
$sDate = Helpers::getDateValue($startDate);
|
||||
$eDate = Helpers::getDateValue($endDate);
|
||||
$startDate = min($sDate, $eDate);
|
||||
$endDate = max($sDate, $eDate);
|
||||
// Get the optional days
|
||||
$dateArgs = Functions::flattenArray($dateArgs);
|
||||
// Test any extra holiday parameters
|
||||
$holidayArray = [];
|
||||
foreach ($dateArgs as $holidayDate) {
|
||||
$holidayArray[] = Helpers::getDateValue($holidayDate);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$startDow = self::calcStartDow($startDate);
|
||||
$endDow = self::calcEndDow($endDate);
|
||||
$wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5;
|
||||
$partWeekDays = self::calcPartWeekDays($startDow, $endDow);
|
||||
|
||||
// Test any extra holiday parameters
|
||||
$holidayCountedArray = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
|
||||
if ((WeekDay::funcWeekDay($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) {
|
||||
--$partWeekDays;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate);
|
||||
}
|
||||
|
||||
private static function calcStartDow(float $startDate): int
|
||||
{
|
||||
$startDow = 6 - (int) WeekDay::funcWeekDay($startDate, 2);
|
||||
if ($startDow < 0) {
|
||||
$startDow = 5;
|
||||
}
|
||||
|
||||
return $startDow;
|
||||
}
|
||||
|
||||
private static function calcEndDow(float $endDate): int
|
||||
{
|
||||
$endDow = (int) WeekDay::funcWeekDay($endDate, 2);
|
||||
if ($endDow >= 6) {
|
||||
$endDow = 0;
|
||||
}
|
||||
|
||||
return $endDow;
|
||||
}
|
||||
|
||||
private static function calcPartWeekDays(int $startDow, int $endDow): int
|
||||
{
|
||||
$partWeekDays = $endDow + $startDow;
|
||||
if ($partWeekDays > 5) {
|
||||
$partWeekDays -= 5;
|
||||
}
|
||||
|
||||
return $partWeekDays;
|
||||
}
|
||||
|
||||
private static function applySign(int $result, float $sDate, float $eDate)
|
||||
{
|
||||
return ($sDate > $eDate) ? -$result : $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Now
|
||||
{
|
||||
/**
|
||||
* DATETIMENOW.
|
||||
*
|
||||
* Returns the current date and time.
|
||||
* The NOW function is useful when you need to display the current date and time on a worksheet or
|
||||
* calculate a value based on the current date and time, and have that value updated each time you
|
||||
* open the worksheet.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* NOW()
|
||||
*
|
||||
* @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 funcNow()
|
||||
{
|
||||
$dti = new DateTimeImmutable();
|
||||
$dateArray = date_parse($dti->format('c'));
|
||||
|
||||
return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Second
|
||||
{
|
||||
/**
|
||||
* MINUTE.
|
||||
*
|
||||
* Returns the minutes of a time value.
|
||||
* The minute is given as an integer, ranging from 0 to 59.
|
||||
*
|
||||
* Excel Function:
|
||||
* MINUTE(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
*
|
||||
* @return int|string Minute
|
||||
*/
|
||||
public static function funcSecond($timeValue)
|
||||
{
|
||||
try {
|
||||
$timeValue = Functions::flattenSingleValue($timeValue);
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = Date::excelToDateTimeObject($timeValue);
|
||||
|
||||
return (int) $timeValue->format('s');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Time
|
||||
{
|
||||
/**
|
||||
* TIME.
|
||||
*
|
||||
* The TIME function returns a value that represents a particular time.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TIME(hour,minute,second)
|
||||
*
|
||||
* @param int $hour A number from 0 (zero) to 32767 representing the hour.
|
||||
* Any value greater than 23 will be divided by 24 and the remainder
|
||||
* will be treated as the hour value. For example, TIME(27,0,0) =
|
||||
* TIME(3,0,0) = .125 or 3:00 AM.
|
||||
* @param int $minute A number from 0 to 32767 representing the minute.
|
||||
* Any value greater than 59 will be converted to hours and minutes.
|
||||
* For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM.
|
||||
* @param int $second A number from 0 to 32767 representing the second.
|
||||
* Any value greater than 59 will be converted to hours, minutes,
|
||||
* and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148
|
||||
* or 12:33:20 AM
|
||||
*
|
||||
* @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 funcTime($hour, $minute, $second)
|
||||
{
|
||||
try {
|
||||
$hour = self::toIntWithNullBool($hour);
|
||||
$minute = self::toIntWithNullBool($minute);
|
||||
$second = self::toIntWithNullBool($second);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
self::adjustSecond($second, $minute);
|
||||
self::adjustMinute($minute, $hour);
|
||||
|
||||
if ($hour > 23) {
|
||||
$hour = $hour % 24;
|
||||
} elseif ($hour < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
$calendar = Date::getExcelCalendar();
|
||||
$date = (int) ($calendar !== Date::CALENDAR_WINDOWS_1900);
|
||||
|
||||
return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600
|
||||
}
|
||||
// RETURNDATE_PHP_DATETIME_OBJECT
|
||||
// Hour has already been normalized (0-23) above
|
||||
$phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second);
|
||||
|
||||
return $phpDateObject;
|
||||
}
|
||||
|
||||
private static function adjustSecond(int &$second, int &$minute): void
|
||||
{
|
||||
if ($second < 0) {
|
||||
$minute += floor($second / 60);
|
||||
$second = 60 - abs($second % 60);
|
||||
if ($second == 60) {
|
||||
$second = 0;
|
||||
}
|
||||
} elseif ($second >= 60) {
|
||||
$minute += floor($second / 60);
|
||||
$second = $second % 60;
|
||||
}
|
||||
}
|
||||
|
||||
private static function adjustMinute(int &$minute, int &$hour): void
|
||||
{
|
||||
if ($minute < 0) {
|
||||
$hour += floor($minute / 60);
|
||||
$minute = 60 - abs($minute % 60);
|
||||
if ($minute == 60) {
|
||||
$minute = 0;
|
||||
}
|
||||
} elseif ($minute >= 60) {
|
||||
$hour += floor($minute / 60);
|
||||
$minute = $minute % 60;
|
||||
}
|
||||
}
|
||||
|
||||
private static function toIntWithNullBool($value): int
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
$value = $value ?? 0;
|
||||
if (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Datetime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class TimeValue
|
||||
{
|
||||
/**
|
||||
* TIMEVALUE.
|
||||
*
|
||||
* Returns a value that represents a particular time.
|
||||
* Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp
|
||||
* value.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TIMEVALUE(timeValue)
|
||||
*
|
||||
* @param string $timeValue A text string that represents a time in any one of the Microsoft
|
||||
* Excel time formats; for example, "6:45 PM" and "18:45" text strings
|
||||
* within quotation marks that represent time.
|
||||
* Date information in time_text is ignored.
|
||||
*
|
||||
* @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 funcTimeValue($timeValue)
|
||||
{
|
||||
$timeValue = trim(Functions::flattenSingleValue($timeValue), '"');
|
||||
$timeValue = str_replace(['/', '.'], '-', $timeValue);
|
||||
|
||||
$arraySplit = preg_split('/[\/:\-\s]/', $timeValue);
|
||||
if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
|
||||
$arraySplit[0] = ($arraySplit[0] % 24);
|
||||
$timeValue = implode(':', $arraySplit);
|
||||
}
|
||||
|
||||
$PHPDateArray = date_parse($timeValue);
|
||||
$retValue = Functions::VALUE();
|
||||
if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) {
|
||||
// OpenOffice-specific code removed - it works just like Excel
|
||||
$excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1;
|
||||
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
$retValue = (float) $excelDateValue;
|
||||
} elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
$retValue = (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600;
|
||||
} else {
|
||||
$retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']);
|
||||
}
|
||||
}
|
||||
|
||||
return $retValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Today
|
||||
{
|
||||
/**
|
||||
* DATENOW.
|
||||
*
|
||||
* Returns the current date.
|
||||
* The NOW function is useful when you need to display the current date and time on a worksheet or
|
||||
* calculate a value based on the current date and time, and have that value updated each time you
|
||||
* open the worksheet.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TODAY()
|
||||
*
|
||||
* @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 funcToday()
|
||||
{
|
||||
$dti = new DateTimeImmutable();
|
||||
$dateArray = date_parse($dti->format('c'));
|
||||
|
||||
return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class WeekDay
|
||||
{
|
||||
/**
|
||||
* WEEKDAY.
|
||||
*
|
||||
* Returns the day of the week for a specified date. The day is given as an integer
|
||||
* ranging from 0 to 7 (dependent on the requested style).
|
||||
*
|
||||
* Excel Function:
|
||||
* WEEKDAY(dateValue[,style])
|
||||
*
|
||||
* @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $style A number that determines the type of return value
|
||||
* 1 or omitted Numbers 1 (Sunday) through 7 (Saturday).
|
||||
* 2 Numbers 1 (Monday) through 7 (Sunday).
|
||||
* 3 Numbers 0 (Monday) through 6 (Sunday).
|
||||
*
|
||||
* @return int|string Day of the week value
|
||||
*/
|
||||
public static function funcWeekDay($dateValue, $style = 1)
|
||||
{
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
$style = self::validateStyle($style);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
$DoW = (int) $PHPDateObject->format('w');
|
||||
|
||||
switch ($style) {
|
||||
case 1:
|
||||
++$DoW;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
$DoW = self::dow0Becomes7($DoW);
|
||||
|
||||
break;
|
||||
case 3:
|
||||
$DoW = self::dow0Becomes7($DoW) - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $DoW;
|
||||
}
|
||||
|
||||
private static function validateStyle($style): int
|
||||
{
|
||||
$style = Functions::flattenSingleValue($style);
|
||||
|
||||
if (!is_numeric($style)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
$style = (int) $style;
|
||||
if (($style < 1) || ($style > 3)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
private static function dow0Becomes7(int $DoW): int
|
||||
{
|
||||
return ($DoW === 0) ? 7 : $DoW;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class WeekNum
|
||||
{
|
||||
/**
|
||||
* WEEKNUM.
|
||||
*
|
||||
* Returns the week of the year for a specified date.
|
||||
* The WEEKNUM function considers the week containing January 1 to be the first week of the year.
|
||||
* However, there is a European standard that defines the first week as the one with the majority
|
||||
* of days (four or more) falling in the new year. This means that for years in which there are
|
||||
* three days or less in the first week of January, the WEEKNUM function returns week numbers
|
||||
* that are incorrect according to the European standard.
|
||||
*
|
||||
* Excel Function:
|
||||
* WEEKNUM(dateValue[,style])
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $method Week begins on Sunday or Monday
|
||||
* 1 or omitted Week begins on Sunday.
|
||||
* 2 Week begins on Monday.
|
||||
* 11 Week begins on Monday.
|
||||
* 12 Week begins on Tuesday.
|
||||
* 13 Week begins on Wednesday.
|
||||
* 14 Week begins on Thursday.
|
||||
* 15 Week begins on Friday.
|
||||
* 16 Week begins on Saturday.
|
||||
* 17 Week begins on Sunday.
|
||||
* 21 ISO (Jan. 4 is week 1, begins on Monday).
|
||||
*
|
||||
* @return int|string Week Number
|
||||
*/
|
||||
public static function funcWeekNum($dateValue, $method = Constants::STARTWEEK_SUNDAY)
|
||||
{
|
||||
$origDateValueNull = empty($dateValue);
|
||||
|
||||
try {
|
||||
$method = self::validateMethod($method);
|
||||
if ($dateValue === null) { // boolean not allowed
|
||||
$dateValue = (Date::getExcelCalendar() === DATE::CALENDAR_MAC_1904 || $method === Constants::DOW_SUNDAY) ? 0 : 1;
|
||||
}
|
||||
$dateValue = self::validateDateValue($dateValue);
|
||||
if (!$dateValue && self::buggyWeekNum1900($method)) {
|
||||
// This seems to be an additional Excel bug.
|
||||
return 0;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
if ($method == Constants::STARTWEEK_MONDAY_ISO) {
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('W');
|
||||
}
|
||||
if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) {
|
||||
return 0;
|
||||
}
|
||||
Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches
|
||||
$dayOfYear = (int) $PHPDateObject->format('z');
|
||||
$PHPDateObject->modify('-' . $dayOfYear . ' days');
|
||||
$firstDayOfFirstWeek = (int) $PHPDateObject->format('w');
|
||||
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
|
||||
$daysInFirstWeek += 7 * !$daysInFirstWeek;
|
||||
$endFirstWeek = $daysInFirstWeek - 1;
|
||||
$weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
|
||||
|
||||
return (int) $weekOfYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate dateValue parameter.
|
||||
*
|
||||
* @param mixed $dateValue
|
||||
*/
|
||||
private static function validateDateValue($dateValue): float
|
||||
{
|
||||
if (is_bool($dateValue)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return Helpers::getDateValue($dateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate method parameter.
|
||||
*
|
||||
* @param mixed $method
|
||||
*/
|
||||
private static function validateMethod($method): int
|
||||
{
|
||||
if ($method === null) {
|
||||
$method = Constants::STARTWEEK_SUNDAY;
|
||||
}
|
||||
$method = Functions::flattenSingleValue($method);
|
||||
if (!is_numeric($method)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
$method = (int) $method;
|
||||
if (!array_key_exists($method, Constants::METHODARR)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
$method = Constants::METHODARR[$method];
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
private static function buggyWeekNum1900(int $method): bool
|
||||
{
|
||||
return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_WINDOWS_1900;
|
||||
}
|
||||
|
||||
private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool
|
||||
{
|
||||
// This appears to be another Excel bug.
|
||||
|
||||
return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_MAC_1904 && !$origNull && $dateObject->format('Y-m-d') === '1904-01-01';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class WorkDay
|
||||
{
|
||||
/**
|
||||
* WORKDAY.
|
||||
*
|
||||
* Returns the date that is the indicated number of working days before or after a date (the
|
||||
* starting date). Working days exclude weekends and any dates identified as holidays.
|
||||
* Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
|
||||
* delivery times, or the number of days of work performed.
|
||||
*
|
||||
* Excel Function:
|
||||
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $endDays The number of nonweekend and nonholiday days before or after
|
||||
* startDate. A positive value for days yields a future date; a
|
||||
* negative value yields a past date.
|
||||
*
|
||||
* @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 funcWorkDay($startDate, $endDays, ...$dateArgs)
|
||||
{
|
||||
// Retrieve the mandatory start date and days that are referenced in the function definition
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDays = Helpers::validateNumericNull($endDays);
|
||||
$dateArgs = Functions::flattenArray($dateArgs);
|
||||
$holidayArray = [];
|
||||
foreach ($dateArgs as $holidayDate) {
|
||||
$holidayArray[] = Helpers::getDateValue($holidayDate);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$startDate = (float) floor($startDate);
|
||||
$endDays = (int) floor($endDays);
|
||||
// If endDays is 0, we always return startDate
|
||||
if ($endDays == 0) {
|
||||
return $startDate;
|
||||
}
|
||||
if ($endDays < 0) {
|
||||
return self::decrementing($startDate, $endDays, $holidayArray);
|
||||
}
|
||||
|
||||
return self::incrementing($startDate, $endDays, $holidayArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use incrementing logic to determine Workday.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function incrementing(float $startDate, int $endDays, array $holidayArray)
|
||||
{
|
||||
// Adjust the start date if it falls over a weekend
|
||||
|
||||
$startDoW = WeekDay::funcWeekDay($startDate, 3);
|
||||
if (WeekDay::funcWeekDay($startDate, 3) >= 5) {
|
||||
$startDate += 7 - $startDoW;
|
||||
--$endDays;
|
||||
}
|
||||
|
||||
// Add endDays
|
||||
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
||||
$endDays = $endDays % 5;
|
||||
while ($endDays > 0) {
|
||||
++$endDate;
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDow = WeekDay::funcWeekDay($endDate, 3);
|
||||
if ($endDow >= 5) {
|
||||
$endDate += 7 - $endDow;
|
||||
}
|
||||
--$endDays;
|
||||
}
|
||||
|
||||
// Test any extra holiday parameters
|
||||
if (!empty($holidayArray)) {
|
||||
$endDate = self::incrementingArray($startDate, $endDate, $holidayArray);
|
||||
}
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($endDate);
|
||||
}
|
||||
|
||||
private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
||||
{
|
||||
$holidayCountedArray = $holidayDates = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (WeekDay::funcWeekDay($holidayDate, 3) < 5) {
|
||||
$holidayDates[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
sort($holidayDates, SORT_NUMERIC);
|
||||
foreach ($holidayDates as $holidayDate) {
|
||||
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
|
||||
if (!in_array($holidayDate, $holidayCountedArray)) {
|
||||
++$endDate;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDoW = WeekDay::funcWeekDay($endDate, 3);
|
||||
if ($endDoW >= 5) {
|
||||
$endDate += 7 - $endDoW;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use decrementing logic to determine Workday.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function decrementing(float $startDate, int $endDays, array $holidayArray)
|
||||
{
|
||||
// Adjust the start date if it falls over a weekend
|
||||
|
||||
$startDoW = WeekDay::funcWeekDay($startDate, 3);
|
||||
if (WeekDay::funcWeekDay($startDate, 3) >= 5) {
|
||||
// @phpstan-ignore-next-line
|
||||
$startDate += -$startDoW + 4;
|
||||
++$endDays;
|
||||
}
|
||||
|
||||
// Add endDays
|
||||
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
||||
$endDays = $endDays % 5;
|
||||
while ($endDays < 0) {
|
||||
--$endDate;
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDow = WeekDay::funcWeekDay($endDate, 3);
|
||||
if ($endDow >= 5) {
|
||||
$endDate += 4 - $endDow;
|
||||
}
|
||||
++$endDays;
|
||||
}
|
||||
|
||||
// Test any extra holiday parameters
|
||||
if (!empty($holidayArray)) {
|
||||
$endDate = self::decrementingArray($startDate, $endDate, $holidayArray);
|
||||
}
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($endDate);
|
||||
}
|
||||
|
||||
private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
||||
{
|
||||
$holidayCountedArray = $holidayDates = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (WeekDay::funcWeekDay($holidayDate, 3) < 5) {
|
||||
$holidayDates[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
rsort($holidayDates, SORT_NUMERIC);
|
||||
foreach ($holidayDates as $holidayDate) {
|
||||
if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
|
||||
if (!in_array($holidayDate, $holidayCountedArray)) {
|
||||
--$endDate;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDoW = WeekDay::funcWeekDay($endDate, 3);
|
||||
if ($endDoW >= 5) {
|
||||
// @phpstan-ignore-next-line
|
||||
$endDate += -$endDoW + 4;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Year
|
||||
{
|
||||
/**
|
||||
* YEAR.
|
||||
*
|
||||
* Returns the year corresponding to a date.
|
||||
* The year is returned as an integer in the range 1900-9999.
|
||||
*
|
||||
* Excel Function:
|
||||
* YEAR(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*
|
||||
* @return int|string Year
|
||||
*/
|
||||
public static function funcYear($dateValue)
|
||||
{
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) {
|
||||
return 1900;
|
||||
}
|
||||
// Execute function
|
||||
$PHPDateObject = Date::excelToDateTimeObject($dateValue);
|
||||
|
||||
return (int) $PHPDateObject->format('Y');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class YearFrac
|
||||
{
|
||||
/**
|
||||
* YEARFRAC.
|
||||
*
|
||||
* Calculates the fraction of the year represented by the number of whole days between two dates
|
||||
* (the start_date and the end_date).
|
||||
* Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or
|
||||
* obligations to assign to a specific term.
|
||||
*
|
||||
* Excel Function:
|
||||
* YEARFRAC(startDate,endDate[,method])
|
||||
* See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
|
||||
* for description of algorithm used in Excel
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* @param int $method Method used for the calculation
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string fraction of the year, or a string containing an error
|
||||
*/
|
||||
public static function funcYearFrac($startDate, $endDate, $method = 0)
|
||||
{
|
||||
try {
|
||||
$method = (int) Helpers::validateNumericNull($method);
|
||||
$sDate = Helpers::getDateValue($startDate);
|
||||
$eDate = Helpers::getDateValue($endDate);
|
||||
$sDate = self::excelBug($sDate, $startDate, $endDate, $method);
|
||||
$eDate = self::excelBug($eDate, $endDate, $startDate, $method);
|
||||
$startDate = min($sDate, $eDate);
|
||||
$endDate = max($sDate, $eDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case 0:
|
||||
return Days360::funcDays360($startDate, $endDate) / 360;
|
||||
case 1:
|
||||
return self::method1($startDate, $endDate);
|
||||
case 2:
|
||||
return DateDif::funcDateDif($startDate, $endDate) / 360;
|
||||
case 3:
|
||||
return DateDif::funcDateDif($startDate, $endDate) / 365;
|
||||
case 4:
|
||||
return Days360::funcDays360($startDate, $endDate, true) / 360;
|
||||
}
|
||||
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel 1900 calendar treats date argument of null as 1900-01-00. Really.
|
||||
*
|
||||
* @param mixed $startDate
|
||||
* @param mixed $endDate
|
||||
*/
|
||||
private static function excelBug(float $sDate, $startDate, $endDate, int $method): float
|
||||
{
|
||||
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && Date::getExcelCalendar() !== Date::CALENDAR_MAC_1904) {
|
||||
if ($endDate === null && $startDate !== null) {
|
||||
if (Month::funcMonth($sDate) == 12 && Day::funcDay($sDate) === 31 && $method === 0) {
|
||||
$sDate += 2;
|
||||
} else {
|
||||
++$sDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sDate;
|
||||
}
|
||||
|
||||
private static function method1(float $startDate, float $endDate): float
|
||||
{
|
||||
$days = DateDif::funcDateDif($startDate, $endDate);
|
||||
$startYear = Year::funcYear($startDate);
|
||||
$endYear = Year::funcYear($endDate);
|
||||
$years = $endYear - $startYear + 1;
|
||||
$startMonth = Month::funcMonth($startDate);
|
||||
$startDay = Day::funcDay($startDate);
|
||||
$endMonth = Month::funcMonth($endDate);
|
||||
$endDay = Day::funcDay($endDate);
|
||||
$startMonthDay = 100 * $startMonth + $startDay;
|
||||
$endMonthDay = 100 * $endMonth + $endDay;
|
||||
if ($years == 1) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear);
|
||||
} elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
|
||||
if (Helpers::isLeapYear($startYear)) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229);
|
||||
} elseif (Helpers::isLeapYear($endYear)) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229);
|
||||
} else {
|
||||
$tmpCalcAnnualBasis = 365;
|
||||
}
|
||||
} else {
|
||||
$tmpCalcAnnualBasis = 0;
|
||||
for ($year = $startYear; $year <= $endYear; ++$year) {
|
||||
$tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year);
|
||||
}
|
||||
$tmpCalcAnnualBasis /= $years;
|
||||
}
|
||||
|
||||
return $days / $tmpCalcAnnualBasis;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,14 +3,21 @@
|
|||
namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||
|
||||
use Complex\Complex;
|
||||
use Complex\Exception as ComplexException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexFunctions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexOperations;
|
||||
|
||||
/**
|
||||
* @deprecated 1.18.0
|
||||
*/
|
||||
class Engineering
|
||||
{
|
||||
/**
|
||||
* EULER.
|
||||
*
|
||||
* @deprecated 1.18.0
|
||||
* @see Use Engineering\Constants\EULER instead
|
||||
*/
|
||||
const EULER = 2.71828182845904523536;
|
||||
public const EULER = 2.71828182845904523536;
|
||||
|
||||
/**
|
||||
* parseComplex.
|
||||
|
|
@ -149,7 +156,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toDecimal() method in the Engineering\ConvertBinary class instead
|
||||
*
|
||||
* @param string $x The binary number (as a string) that you want to convert. The number
|
||||
* @param mixed $x The binary number (as a string) that you want to convert. The number
|
||||
* cannot contain more than 10 characters (10 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
|
||||
* Negative numbers are represented using two's-complement notation.
|
||||
|
|
@ -175,13 +182,13 @@ class Engineering
|
|||
*
|
||||
* @see Use the toHex() method in the Engineering\ConvertBinary class instead
|
||||
*
|
||||
* @param string $x The binary number (as a string) that you want to convert. The number
|
||||
* @param mixed $x The binary number (as a string) that you want to convert. The number
|
||||
* cannot contain more than 10 characters (10 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
|
||||
* Negative numbers are represented using two's-complement notation.
|
||||
* If number is not a valid binary number, or if number contains more than
|
||||
* 10 characters (10 bits), BIN2HEX returns the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the
|
||||
* @param mixed $places The number of characters to use. If places is omitted, BIN2HEX uses the
|
||||
* minimum number of characters necessary. Places is useful for padding the
|
||||
* return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -207,13 +214,13 @@ class Engineering
|
|||
*
|
||||
* @see Use the toOctal() method in the Engineering\ConvertBinary class instead
|
||||
*
|
||||
* @param string $x The binary number (as a string) that you want to convert. The number
|
||||
* @param mixed $x The binary number (as a string) that you want to convert. The number
|
||||
* cannot contain more than 10 characters (10 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 9 bits are magnitude bits.
|
||||
* Negative numbers are represented using two's-complement notation.
|
||||
* If number is not a valid binary number, or if number contains more than
|
||||
* 10 characters (10 bits), BIN2OCT returns the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the
|
||||
* @param mixed $places The number of characters to use. If places is omitted, BIN2OCT uses the
|
||||
* minimum number of characters necessary. Places is useful for padding the
|
||||
* return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -239,7 +246,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toBinary() method in the Engineering\ConvertDecimal class instead
|
||||
*
|
||||
* @param string $x The decimal integer you want to convert. If number is negative,
|
||||
* @param mixed $x The decimal integer you want to convert. If number is negative,
|
||||
* valid place values are ignored and DEC2BIN returns a 10-character
|
||||
* (10-bit) binary number in which the most significant bit is the sign
|
||||
* bit. The remaining 9 bits are magnitude bits. Negative numbers are
|
||||
|
|
@ -249,7 +256,7 @@ class Engineering
|
|||
* If number is nonnumeric, DEC2BIN returns the #VALUE! error value.
|
||||
* If DEC2BIN requires more than places characters, it returns the #NUM!
|
||||
* error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, DEC2BIN uses
|
||||
* @param mixed $places The number of characters to use. If places is omitted, DEC2BIN uses
|
||||
* the minimum number of characters necessary. Places is useful for
|
||||
* padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -275,7 +282,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toHex() method in the Engineering\ConvertDecimal class instead
|
||||
*
|
||||
* @param string $x The decimal integer you want to convert. If number is negative,
|
||||
* @param mixed $x The decimal integer you want to convert. If number is negative,
|
||||
* places is ignored and DEC2HEX returns a 10-character (40-bit)
|
||||
* hexadecimal number in which the most significant bit is the sign
|
||||
* bit. The remaining 39 bits are magnitude bits. Negative numbers
|
||||
|
|
@ -285,7 +292,7 @@ class Engineering
|
|||
* If number is nonnumeric, DEC2HEX returns the #VALUE! error value.
|
||||
* If DEC2HEX requires more than places characters, it returns the
|
||||
* #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, DEC2HEX uses
|
||||
* @param mixed $places The number of characters to use. If places is omitted, DEC2HEX uses
|
||||
* the minimum number of characters necessary. Places is useful for
|
||||
* padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -311,7 +318,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toOctal() method in the Engineering\ConvertDecimal class instead
|
||||
*
|
||||
* @param string $x The decimal integer you want to convert. If number is negative,
|
||||
* @param mixed $x The decimal integer you want to convert. If number is negative,
|
||||
* places is ignored and DEC2OCT returns a 10-character (30-bit)
|
||||
* octal number in which the most significant bit is the sign bit.
|
||||
* The remaining 29 bits are magnitude bits. Negative numbers are
|
||||
|
|
@ -321,7 +328,7 @@ class Engineering
|
|||
* If number is nonnumeric, DEC2OCT returns the #VALUE! error value.
|
||||
* If DEC2OCT requires more than places characters, it returns the
|
||||
* #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, DEC2OCT uses
|
||||
* @param mixed $places The number of characters to use. If places is omitted, DEC2OCT uses
|
||||
* the minimum number of characters necessary. Places is useful for
|
||||
* padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -347,7 +354,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toBinary() method in the Engineering\ConvertHex class instead
|
||||
*
|
||||
* @param string $x the hexadecimal number you want to convert.
|
||||
* @param mixed $x the hexadecimal number (as a string) that you want to convert.
|
||||
* Number cannot contain more than 10 characters.
|
||||
* The most significant bit of number is the sign bit (40th bit from the right).
|
||||
* The remaining 9 bits are magnitude bits.
|
||||
|
|
@ -357,7 +364,7 @@ class Engineering
|
|||
* and if number is positive, it cannot be greater than 1FF.
|
||||
* If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value.
|
||||
* If HEX2BIN requires more than places characters, it returns the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted,
|
||||
* @param mixed $places The number of characters to use. If places is omitted,
|
||||
* HEX2BIN uses the minimum number of characters necessary. Places
|
||||
* is useful for padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -383,7 +390,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toDecimal() method in the Engineering\ConvertHex class instead
|
||||
*
|
||||
* @param string $x The hexadecimal number you want to convert. This number cannot
|
||||
* @param mixed $x The hexadecimal number (as a string) that you want to convert. This number cannot
|
||||
* contain more than 10 characters (40 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 39 bits are magnitude
|
||||
* bits. Negative numbers are represented using two's-complement
|
||||
|
|
@ -410,7 +417,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toOctal() method in the Engineering\ConvertHex class instead
|
||||
*
|
||||
* @param string $x The hexadecimal number you want to convert. Number cannot
|
||||
* @param mixed $x The hexadecimal number (as a string) that you want to convert. Number cannot
|
||||
* contain more than 10 characters. The most significant bit of
|
||||
* number is the sign bit. The remaining 39 bits are magnitude
|
||||
* bits. Negative numbers are represented using two's-complement
|
||||
|
|
@ -423,7 +430,7 @@ class Engineering
|
|||
* the #NUM! error value.
|
||||
* If HEX2OCT requires more than places characters, it returns
|
||||
* the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, HEX2OCT
|
||||
* @param mixed $places The number of characters to use. If places is omitted, HEX2OCT
|
||||
* uses the minimum number of characters necessary. Places is
|
||||
* useful for padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -450,7 +457,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toBinary() method in the Engineering\ConvertOctal class instead
|
||||
*
|
||||
* @param string $x The octal number you want to convert. Number may not
|
||||
* @param mixed $x The octal number you want to convert. Number may not
|
||||
* contain more than 10 characters. The most significant
|
||||
* bit of number is the sign bit. The remaining 29 bits
|
||||
* are magnitude bits. Negative numbers are represented
|
||||
|
|
@ -463,7 +470,7 @@ class Engineering
|
|||
* the #NUM! error value.
|
||||
* If OCT2BIN requires more than places characters, it
|
||||
* returns the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted,
|
||||
* @param mixed $places The number of characters to use. If places is omitted,
|
||||
* OCT2BIN uses the minimum number of characters necessary.
|
||||
* Places is useful for padding the return value with
|
||||
* leading 0s (zeros).
|
||||
|
|
@ -492,7 +499,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toDecimal() method in the Engineering\ConvertOctal class instead
|
||||
*
|
||||
* @param string $x The octal number you want to convert. Number may not contain
|
||||
* @param mixed $x The octal number you want to convert. Number may not contain
|
||||
* more than 10 octal characters (30 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 29 bits are
|
||||
* magnitude bits. Negative numbers are represented using
|
||||
|
|
@ -519,7 +526,7 @@ class Engineering
|
|||
*
|
||||
* @see Use the toHex() method in the Engineering\ConvertOctal class instead
|
||||
*
|
||||
* @param string $x The octal number you want to convert. Number may not contain
|
||||
* @param mixed $x The octal number you want to convert. Number may not contain
|
||||
* more than 10 octal characters (30 bits). The most significant
|
||||
* bit of number is the sign bit. The remaining 29 bits are
|
||||
* magnitude bits. Negative numbers are represented using
|
||||
|
|
@ -530,7 +537,7 @@ class Engineering
|
|||
* #NUM! error value.
|
||||
* If OCT2HEX requires more than places characters, it returns
|
||||
* the #NUM! error value.
|
||||
* @param int $places The number of characters to use. If places is omitted, OCT2HEX
|
||||
* @param mixed $places The number of characters to use. If places is omitted, OCT2HEX
|
||||
* uses the minimum number of characters necessary. Places is useful
|
||||
* for padding the return value with leading 0s (zeros).
|
||||
* If places is not an integer, it is truncated.
|
||||
|
|
@ -552,6 +559,10 @@ class Engineering
|
|||
* Excel Function:
|
||||
* COMPLEX(realNumber,imaginary[,suffix])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the COMPLEX() method in the Engineering\Complex class instead
|
||||
*
|
||||
* @param float $realNumber the real coefficient of the complex number
|
||||
* @param float $imaginary the imaginary coefficient of the complex number
|
||||
* @param string $suffix The suffix for the imaginary component of the complex number.
|
||||
|
|
@ -561,20 +572,7 @@ class Engineering
|
|||
*/
|
||||
public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i')
|
||||
{
|
||||
$realNumber = ($realNumber === null) ? 0.0 : Functions::flattenSingleValue($realNumber);
|
||||
$imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary);
|
||||
$suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix);
|
||||
|
||||
if (
|
||||
((is_numeric($realNumber)) && (is_numeric($imaginary))) &&
|
||||
(($suffix == 'i') || ($suffix == 'j') || ($suffix == ''))
|
||||
) {
|
||||
$complex = new Complex($realNumber, $imaginary, $suffix);
|
||||
|
||||
return (string) $complex;
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -585,16 +583,18 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMAGINARY(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMAGINARY() method in the Engineering\Complex class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the imaginary
|
||||
* coefficient
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMAGINARY($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (new Complex($complexNumber))->getImaginary();
|
||||
return Engineering\Complex::IMAGINARY($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -605,15 +605,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMREAL(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMREAL() method in the Engineering\Complex class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the real coefficient
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMREAL($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (new Complex($complexNumber))->getReal();
|
||||
return Engineering\Complex::IMREAL($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -624,15 +626,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMABS(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMABS() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the absolute value
|
||||
*
|
||||
* @return float
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMABS($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (new Complex($complexNumber))->abs();
|
||||
return ComplexFunctions::IMABS($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -644,20 +648,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMARGUMENT(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the argument theta
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMARGUMENT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
return $complex->argument();
|
||||
return ComplexFunctions::IMARGUMENT($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -668,15 +669,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCONJUGATE(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the conjugate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMCONJUGATE($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->conjugate();
|
||||
return ComplexFunctions::IMCONJUGATE($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -687,15 +690,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCOS(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMCOS() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cosine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOS($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->cos();
|
||||
return ComplexFunctions::IMCOS($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -706,15 +711,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCOSH(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMCOSH() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOSH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->cosh();
|
||||
return ComplexFunctions::IMCOSH($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -725,15 +732,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCOT(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMCOT() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cotangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->cot();
|
||||
return ComplexFunctions::IMCOT($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -744,15 +753,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCSC(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMCSC() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->csc();
|
||||
return ComplexFunctions::IMCSC($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -763,15 +774,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMCSCH(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMCSCH() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSCH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->csch();
|
||||
return ComplexFunctions::IMCSCH($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -782,15 +795,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSIN(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSIN() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the sine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSIN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sin();
|
||||
return ComplexFunctions::IMSIN($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -801,15 +816,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSINH(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSINH() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic sine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSINH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sinh();
|
||||
return ComplexFunctions::IMSINH($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -820,15 +837,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSEC(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSEC() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSEC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sec();
|
||||
return ComplexFunctions::IMSEC($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -839,15 +858,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSECH(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSECH() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSECH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->sech();
|
||||
return ComplexFunctions::IMSECH($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -858,15 +879,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMTAN(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMTAN() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the tangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMTAN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->tan();
|
||||
return ComplexFunctions::IMTAN($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -877,20 +900,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSQRT(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSQRT() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the square root
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMSQRT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$theta = self::IMARGUMENT($complexNumber);
|
||||
if ($theta === Functions::DIV0()) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return (string) (new Complex($complexNumber))->sqrt();
|
||||
return ComplexFunctions::IMSQRT($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -901,20 +921,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMLN(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMLN() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the natural logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) (new Complex($complexNumber))->ln();
|
||||
return ComplexFunctions::IMLN($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -925,20 +942,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMLOG10(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMLOG10() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the common logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLOG10($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) (new Complex($complexNumber))->log10();
|
||||
return ComplexFunctions::IMLOG10($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -949,20 +963,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMLOG2(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMLOG2() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the base-2 logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLOG2($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
$complex = new Complex($complexNumber);
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) (new Complex($complexNumber))->log2();
|
||||
return ComplexFunctions::IMLOG2($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -973,15 +984,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMEXP(complexNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMEXP() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the exponential
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMEXP($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
return (string) (new Complex($complexNumber))->exp();
|
||||
return ComplexFunctions::IMEXP($complexNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -992,6 +1005,10 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMPOWER(complexNumber,realNumber)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMPOWER() method in the Engineering\ComplexFunctions class instead
|
||||
*
|
||||
* @param string $complexNumber the complex number you want to raise to a power
|
||||
* @param float $realNumber the power to which you want to raise the complex number
|
||||
*
|
||||
|
|
@ -999,14 +1016,7 @@ class Engineering
|
|||
*/
|
||||
public static function IMPOWER($complexNumber, $realNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
$realNumber = Functions::flattenSingleValue($realNumber);
|
||||
|
||||
if (!is_numeric($realNumber)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return (string) (new Complex($complexNumber))->pow($realNumber);
|
||||
return ComplexFunctions::IMPOWER($complexNumber, $realNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1017,6 +1027,10 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMDIV(complexDividend,complexDivisor)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMDIV() method in the Engineering\ComplexOperations class instead
|
||||
*
|
||||
* @param string $complexDividend the complex numerator or dividend
|
||||
* @param string $complexDivisor the complex denominator or divisor
|
||||
*
|
||||
|
|
@ -1024,14 +1038,7 @@ class Engineering
|
|||
*/
|
||||
public static function IMDIV($complexDividend, $complexDivisor)
|
||||
{
|
||||
$complexDividend = Functions::flattenSingleValue($complexDividend);
|
||||
$complexDivisor = Functions::flattenSingleValue($complexDivisor);
|
||||
|
||||
try {
|
||||
return (string) (new Complex($complexDividend))->divideby(new Complex($complexDivisor));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
return ComplexOperations::IMDIV($complexDividend, $complexDivisor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1042,6 +1049,10 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSUB(complexNumber1,complexNumber2)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSUB() method in the Engineering\ComplexOperations class instead
|
||||
*
|
||||
* @param string $complexNumber1 the complex number from which to subtract complexNumber2
|
||||
* @param string $complexNumber2 the complex number to subtract from complexNumber1
|
||||
*
|
||||
|
|
@ -1049,14 +1060,7 @@ class Engineering
|
|||
*/
|
||||
public static function IMSUB($complexNumber1, $complexNumber2)
|
||||
{
|
||||
$complexNumber1 = Functions::flattenSingleValue($complexNumber1);
|
||||
$complexNumber2 = Functions::flattenSingleValue($complexNumber2);
|
||||
|
||||
try {
|
||||
return (string) (new Complex($complexNumber1))->subtract(new Complex($complexNumber2));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
return ComplexOperations::IMSUB($complexNumber1, $complexNumber2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1067,26 +1071,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMSUM(complexNumber[,complexNumber[,...]])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMSUM() method in the Engineering\ComplexOperations class instead
|
||||
*
|
||||
* @param string ...$complexNumbers Series of complex numbers to add
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMSUM(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = new Complex(0.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->add(new Complex($complex));
|
||||
}
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $returnValue;
|
||||
return ComplexOperations::IMSUM(...$complexNumbers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1097,26 +1092,17 @@ class Engineering
|
|||
* Excel Function:
|
||||
* IMPRODUCT(complexNumber[,complexNumber[,...]])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead
|
||||
*
|
||||
* @param string ...$complexNumbers Series of complex numbers to multiply
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMPRODUCT(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = new Complex(1.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->multiply(new Complex($complex));
|
||||
}
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $returnValue;
|
||||
return ComplexOperations::IMPRODUCT(...$complexNumbers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
class BesselI
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* BESSELI.
|
||||
*
|
||||
|
|
@ -16,9 +18,12 @@ class BesselI
|
|||
* Excel Function:
|
||||
* BESSELI(x,ord)
|
||||
*
|
||||
* @param float $x The value at which to evaluate the function.
|
||||
* NOTE: The MS Excel implementation of the BESSELI function is still not accurate.
|
||||
* This code provides a more accurate calculation
|
||||
*
|
||||
* @param mixed $x A float value at which to evaluate the function.
|
||||
* If x is nonnumeric, BESSELI returns the #VALUE! error value.
|
||||
* @param int $ord The order of the Bessel function.
|
||||
* @param mixed $ord The integer order of the Bessel function.
|
||||
* If ord is not an integer, it is truncated.
|
||||
* If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
|
||||
* If $ord < 0, BESSELI returns the #NUM! error value.
|
||||
|
|
@ -28,45 +33,107 @@ class BesselI
|
|||
public static function BESSELI($x, $ord)
|
||||
{
|
||||
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
|
||||
$ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord);
|
||||
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
|
||||
|
||||
if ((is_numeric($x)) && (is_numeric($ord))) {
|
||||
$ord = (int) floor($ord);
|
||||
if ($ord < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fResult = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
|
||||
try {
|
||||
$x = self::validateFloat($x);
|
||||
$ord = self::validateInt($ord);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
if ($ord < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fResult = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
|
||||
}
|
||||
|
||||
private static function calculate(float $x, int $ord): float
|
||||
{
|
||||
if (abs($x) <= 30) {
|
||||
$fResult = $fTerm = ($x / 2) ** $ord / MathTrig::FACT($ord);
|
||||
$ordK = 1;
|
||||
$fSqrX = ($x * $x) / 4;
|
||||
do {
|
||||
$fTerm *= $fSqrX;
|
||||
$fTerm /= ($ordK * ($ordK + $ord));
|
||||
$fResult += $fTerm;
|
||||
} while ((abs($fTerm) > 1e-12) && (++$ordK < 100));
|
||||
|
||||
return $fResult;
|
||||
// special cases
|
||||
switch ($ord) {
|
||||
case 0:
|
||||
return self::besselI0($x);
|
||||
case 1:
|
||||
return self::besselI1($x);
|
||||
}
|
||||
|
||||
$f_2_PI = 2 * M_PI;
|
||||
return self::besselI2($x, $ord);
|
||||
}
|
||||
|
||||
$fXAbs = abs($x);
|
||||
$fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs);
|
||||
if (($ord & 1) && ($x < 0)) {
|
||||
$fResult = -$fResult;
|
||||
private static function besselI0(float $x): float
|
||||
{
|
||||
$ax = abs($x);
|
||||
|
||||
if ($ax < 3.75) {
|
||||
$y = $x / 3.75;
|
||||
$y = $y * $y;
|
||||
|
||||
return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492
|
||||
+ $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2)))));
|
||||
}
|
||||
|
||||
return $fResult;
|
||||
$y = 3.75 / $ax;
|
||||
|
||||
return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2
|
||||
+ $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 +
|
||||
$y * (-0.1647633e-1 + $y * 0.392377e-2))))))));
|
||||
}
|
||||
|
||||
private static function besselI1(float $x): float
|
||||
{
|
||||
$ax = abs($x);
|
||||
|
||||
if ($ax < 3.75) {
|
||||
$y = $x / 3.75;
|
||||
$y = $y * $y;
|
||||
$ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 +
|
||||
$y * (0.301532e-2 + $y * 0.32411e-3))))));
|
||||
|
||||
return ($x < 0.0) ? -$ans : $ans;
|
||||
}
|
||||
|
||||
$y = 3.75 / $ax;
|
||||
$ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2));
|
||||
$ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 +
|
||||
$y * (-0.1031555e-1 + $y * $ans))));
|
||||
$ans *= exp($ax) / sqrt($ax);
|
||||
|
||||
return ($x < 0.0) ? -$ans : $ans;
|
||||
}
|
||||
|
||||
private static function besselI2(float $x, int $ord): float
|
||||
{
|
||||
if ($x === 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$tox = 2.0 / abs($x);
|
||||
$bip = 0;
|
||||
$ans = 0.0;
|
||||
$bi = 1.0;
|
||||
|
||||
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
|
||||
$bim = $bip + $j * $tox * $bi;
|
||||
$bip = $bi;
|
||||
$bi = $bim;
|
||||
|
||||
if (abs($bi) > 1.0e+12) {
|
||||
$ans *= 1.0e-12;
|
||||
$bi *= 1.0e-12;
|
||||
$bip *= 1.0e-12;
|
||||
}
|
||||
|
||||
if ($j === $ord) {
|
||||
$ans = $bip;
|
||||
}
|
||||
}
|
||||
|
||||
$ans *= self::besselI0($x) / $bi;
|
||||
|
||||
return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
class BesselJ
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* BESSELJ.
|
||||
*
|
||||
|
|
@ -15,9 +17,13 @@ class BesselJ
|
|||
* Excel Function:
|
||||
* BESSELJ(x,ord)
|
||||
*
|
||||
* @param float $x The value at which to evaluate the function.
|
||||
* NOTE: The MS Excel implementation of the BESSELJ function is still not accurate, particularly for higher order
|
||||
* values with x < -8 and x > 8. This code provides a more accurate calculation
|
||||
*
|
||||
* @param mixed $x A float value at which to evaluate the function.
|
||||
* If x is nonnumeric, BESSELJ returns the #VALUE! error value.
|
||||
* @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
|
||||
* @param mixed $ord The integer order of the Bessel function.
|
||||
* If ord is not an integer, it is truncated.
|
||||
* If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
|
||||
* If $ord < 0, BESSELJ returns the #NUM! error value.
|
||||
*
|
||||
|
|
@ -28,44 +34,141 @@ class BesselJ
|
|||
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
|
||||
$ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord);
|
||||
|
||||
if ((is_numeric($x)) && (is_numeric($ord))) {
|
||||
$ord = (int) floor($ord);
|
||||
if ($ord < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fResult = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
|
||||
try {
|
||||
$x = self::validateFloat($x);
|
||||
$ord = self::validateInt($ord);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
if ($ord < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fResult = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fResult)) ? Functions::NAN() : $fResult;
|
||||
}
|
||||
|
||||
private static function calculate(float $x, int $ord): float
|
||||
{
|
||||
if (abs($x) <= 30) {
|
||||
$fResult = $fTerm = ($x / 2) ** $ord / MathTrig::FACT($ord);
|
||||
$ordK = 1;
|
||||
$fSqrX = ($x * $x) / -4;
|
||||
do {
|
||||
$fTerm *= $fSqrX;
|
||||
$fTerm /= ($ordK * ($ordK + $ord));
|
||||
$fResult += $fTerm;
|
||||
} while ((abs($fTerm) > 1e-12) && (++$ordK < 100));
|
||||
|
||||
return $fResult;
|
||||
// special cases
|
||||
switch ($ord) {
|
||||
case 0:
|
||||
return self::besselJ0($x);
|
||||
case 1:
|
||||
return self::besselJ1($x);
|
||||
}
|
||||
|
||||
$f_PI_DIV_2 = M_PI / 2;
|
||||
$f_PI_DIV_4 = M_PI / 4;
|
||||
return self::besselJ2($x, $ord);
|
||||
}
|
||||
|
||||
$fXAbs = abs($x);
|
||||
$fResult = sqrt(Functions::M_2DIVPI / $fXAbs) * cos($fXAbs - $ord * $f_PI_DIV_2 - $f_PI_DIV_4);
|
||||
if (($ord & 1) && ($x < 0)) {
|
||||
$fResult = -$fResult;
|
||||
private static function besselJ0(float $x): float
|
||||
{
|
||||
$ax = abs($x);
|
||||
|
||||
if ($ax < 8.0) {
|
||||
$y = $x * $x;
|
||||
$ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y *
|
||||
(77392.33017 + $y * (-184.9052456)))));
|
||||
$ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y *
|
||||
(267.8532712 + $y * 1.0))));
|
||||
|
||||
return $ans1 / $ans2;
|
||||
}
|
||||
|
||||
return $fResult;
|
||||
$z = 8.0 / $ax;
|
||||
$y = $z * $z;
|
||||
$xx = $ax - 0.785398164;
|
||||
$ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
|
||||
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y *
|
||||
(0.7621095161e-6 - $y * 0.934935152e-7)));
|
||||
|
||||
return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
|
||||
}
|
||||
|
||||
private static function besselJ1(float $x): float
|
||||
{
|
||||
$ax = abs($x);
|
||||
|
||||
if ($ax < 8.0) {
|
||||
$y = $x * $x;
|
||||
$ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y *
|
||||
(-2972611.439 + $y * (15704.48260 + $y * (-30.16036606))))));
|
||||
$ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y *
|
||||
(376.9991397 + $y * 1.0))));
|
||||
|
||||
return $ans1 / $ans2;
|
||||
}
|
||||
|
||||
$z = 8.0 / $ax;
|
||||
$y = $z * $z;
|
||||
$xx = $ax - 2.356194491;
|
||||
|
||||
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
|
||||
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y *
|
||||
(-0.88228987e-6 + $y * 0.105787412e-6)));
|
||||
$ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
|
||||
|
||||
return ($x < 0.0) ? -$ans : $ans;
|
||||
}
|
||||
|
||||
private static function besselJ2(float $x, int $ord): float
|
||||
{
|
||||
$ax = abs($x);
|
||||
if ($ax === 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if ($ax > $ord) {
|
||||
return self::besselj2a($ax, $ord, $x);
|
||||
}
|
||||
|
||||
return self::besselj2b($ax, $ord, $x);
|
||||
}
|
||||
|
||||
private static function besselj2a(float $ax, int $ord, float $x)
|
||||
{
|
||||
$tox = 2.0 / $ax;
|
||||
$bjm = self::besselJ0($ax);
|
||||
$bj = self::besselJ1($ax);
|
||||
for ($j = 1; $j < $ord; ++$j) {
|
||||
$bjp = $j * $tox * $bj - $bjm;
|
||||
$bjm = $bj;
|
||||
$bj = $bjp;
|
||||
}
|
||||
$ans = $bj;
|
||||
|
||||
return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans;
|
||||
}
|
||||
|
||||
private static function besselj2b(float $ax, int $ord, float $x)
|
||||
{
|
||||
$tox = 2.0 / $ax;
|
||||
$jsum = false;
|
||||
$bjp = $ans = $sum = 0.0;
|
||||
$bj = 1.0;
|
||||
for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
|
||||
$bjm = $j * $tox * $bj - $bjp;
|
||||
$bjp = $bj;
|
||||
$bj = $bjm;
|
||||
if (abs($bj) > 1.0e+10) {
|
||||
$bj *= 1.0e-10;
|
||||
$bjp *= 1.0e-10;
|
||||
$ans *= 1.0e-10;
|
||||
$sum *= 1.0e-10;
|
||||
}
|
||||
if ($jsum === true) {
|
||||
$sum += $bj;
|
||||
}
|
||||
$jsum = !$jsum;
|
||||
if ($j === $ord) {
|
||||
$ans = $bjp;
|
||||
}
|
||||
}
|
||||
$sum = 2.0 * $sum - $bj;
|
||||
$ans /= $sum;
|
||||
|
||||
return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class BesselK
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* BESSELK.
|
||||
*
|
||||
|
|
@ -15,11 +18,12 @@ class BesselK
|
|||
* Excel Function:
|
||||
* BESSELK(x,ord)
|
||||
*
|
||||
* @param float $x The value at which to evaluate the function.
|
||||
* @param mixed $x A float value at which to evaluate the function.
|
||||
* If x is nonnumeric, BESSELK returns the #VALUE! error value.
|
||||
* @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
|
||||
* @param mixed $ord The integer order of the Bessel function.
|
||||
* If ord is not an integer, it is truncated.
|
||||
* If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
|
||||
* If $ord < 0, BESSELK returns the #NUM! error value.
|
||||
* If $ord < 0, BESSELKI returns the #NUM! error value.
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
|
|
@ -28,62 +32,67 @@ class BesselK
|
|||
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
|
||||
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
|
||||
|
||||
if ((is_numeric($x)) && (is_numeric($ord))) {
|
||||
if (($ord < 0) || ($x == 0.0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
switch (floor($ord)) {
|
||||
case 0:
|
||||
$fBk = self::besselK0($x);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
$fBk = self::besselK1($x);
|
||||
|
||||
break;
|
||||
default:
|
||||
$fBk = self::besselK2($x, $ord);
|
||||
}
|
||||
|
||||
return (is_nan($fBk)) ? Functions::NAN() : $fBk;
|
||||
try {
|
||||
$x = self::validateFloat($x);
|
||||
$ord = self::validateInt($ord);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
if (($ord < 0) || ($x <= 0.0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fBk = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fBk)) ? Functions::NAN() : $fBk;
|
||||
}
|
||||
|
||||
private static function besselK0(float $fNum): float
|
||||
private static function calculate(float $x, int $ord): float
|
||||
{
|
||||
if ($fNum <= 2) {
|
||||
$fNum2 = $fNum * 0.5;
|
||||
// special cases
|
||||
switch ($ord) {
|
||||
case 0:
|
||||
return self::besselK0($x);
|
||||
case 1:
|
||||
return self::besselK1($x);
|
||||
}
|
||||
|
||||
return self::besselK2($x, $ord);
|
||||
}
|
||||
|
||||
private static function besselK0(float $x): float
|
||||
{
|
||||
if ($x <= 2) {
|
||||
$fNum2 = $x * 0.5;
|
||||
$y = ($fNum2 * $fNum2);
|
||||
|
||||
return -log($fNum2) * BesselI::BESSELI($fNum, 0) +
|
||||
return -log($fNum2) * BesselI::BESSELI($x, 0) +
|
||||
(-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
|
||||
(0.10750e-3 + $y * 0.74e-5))))));
|
||||
}
|
||||
|
||||
$y = 2 / $fNum;
|
||||
$y = 2 / $x;
|
||||
|
||||
return exp(-$fNum) / sqrt($fNum) *
|
||||
return exp(-$x) / sqrt($x) *
|
||||
(1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y *
|
||||
(0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
|
||||
}
|
||||
|
||||
private static function besselK1(float $fNum): float
|
||||
private static function besselK1(float $x): float
|
||||
{
|
||||
if ($fNum <= 2) {
|
||||
$fNum2 = $fNum * 0.5;
|
||||
if ($x <= 2) {
|
||||
$fNum2 = $x * 0.5;
|
||||
$y = ($fNum2 * $fNum2);
|
||||
|
||||
return log($fNum2) * BesselI::BESSELI($fNum, 1) +
|
||||
return log($fNum2) * BesselI::BESSELI($x, 1) +
|
||||
(1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y *
|
||||
(-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum;
|
||||
(-0.110404e-2 + $y * (-0.4686e-4))))))) / $x;
|
||||
}
|
||||
|
||||
$y = 2 / $fNum;
|
||||
$y = 2 / $x;
|
||||
|
||||
return exp(-$fNum) / sqrt($fNum) *
|
||||
return exp(-$x) / sqrt($x) *
|
||||
(1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y *
|
||||
(0.325614e-2 + $y * (-0.68245e-3)))))));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class BesselY
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* BESSELY.
|
||||
*
|
||||
|
|
@ -14,11 +17,12 @@ class BesselY
|
|||
* Excel Function:
|
||||
* BESSELY(x,ord)
|
||||
*
|
||||
* @param float $x The value at which to evaluate the function.
|
||||
* If x is nonnumeric, BESSELY returns the #VALUE! error value.
|
||||
* @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
|
||||
* If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
|
||||
* If $ord < 0, BESSELY returns the #NUM! error value.
|
||||
* @param mixed $x A float value at which to evaluate the function.
|
||||
* If x is nonnumeric, BESSELY returns the #VALUE! error value.
|
||||
* @param mixed $ord The integer order of the Bessel function.
|
||||
* If ord is not an integer, it is truncated.
|
||||
* If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
|
||||
* If $ord < 0, BESSELY returns the #NUM! error value.
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
|
|
@ -27,70 +31,82 @@ class BesselY
|
|||
$x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x);
|
||||
$ord = ($ord === null) ? 0 : Functions::flattenSingleValue($ord);
|
||||
|
||||
if ((is_numeric($x)) && (is_numeric($ord))) {
|
||||
if (($ord < 0) || ($x == 0.0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
switch (floor($ord)) {
|
||||
case 0:
|
||||
$fBy = self::besselY0($x);
|
||||
|
||||
break;
|
||||
case 1:
|
||||
$fBy = self::besselY1($x);
|
||||
|
||||
break;
|
||||
default:
|
||||
$fBy = self::besselY2($x, $ord);
|
||||
}
|
||||
|
||||
return (is_nan($fBy)) ? Functions::NAN() : $fBy;
|
||||
try {
|
||||
$x = self::validateFloat($x);
|
||||
$ord = self::validateInt($ord);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
if (($ord < 0) || ($x <= 0.0)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$fBy = self::calculate($x, $ord);
|
||||
|
||||
return (is_nan($fBy)) ? Functions::NAN() : $fBy;
|
||||
}
|
||||
|
||||
private static function besselY0(float $fNum): float
|
||||
private static function calculate(float $x, int $ord): float
|
||||
{
|
||||
if ($fNum < 8.0) {
|
||||
$y = ($fNum * $fNum);
|
||||
$f1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y *
|
||||
// special cases
|
||||
switch ($ord) {
|
||||
case 0:
|
||||
return self::besselY0($x);
|
||||
case 1:
|
||||
return self::besselY1($x);
|
||||
}
|
||||
|
||||
return self::besselY2($x, $ord);
|
||||
}
|
||||
|
||||
private static function besselY0(float $x): float
|
||||
{
|
||||
if ($x < 8.0) {
|
||||
$y = ($x * $x);
|
||||
$ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y *
|
||||
(-86327.92757 + $y * 228.4622733))));
|
||||
$f2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y *
|
||||
$ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y *
|
||||
(47447.26470 + $y * (226.1030244 + $y))));
|
||||
|
||||
return $f1 / $f2 + 0.636619772 * BesselJ::BESSELJ($fNum, 0) * log($fNum);
|
||||
return $ans1 / $ans2 + 0.636619772 * BesselJ::BESSELJ($x, 0) * log($x);
|
||||
}
|
||||
|
||||
$z = 8.0 / $fNum;
|
||||
$z = 8.0 / $x;
|
||||
$y = ($z * $z);
|
||||
$xx = $fNum - 0.785398164;
|
||||
$f1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
|
||||
$f2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y *
|
||||
$xx = $x - 0.785398164;
|
||||
$ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
|
||||
$ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y *
|
||||
(-0.934945152e-7))));
|
||||
|
||||
return sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2);
|
||||
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
|
||||
}
|
||||
|
||||
private static function besselY1(float $fNum): float
|
||||
private static function besselY1(float $x): float
|
||||
{
|
||||
if ($fNum < 8.0) {
|
||||
$y = ($fNum * $fNum);
|
||||
$f1 = $fNum * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y *
|
||||
if ($x < 8.0) {
|
||||
$y = ($x * $x);
|
||||
$ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y *
|
||||
(0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4)))));
|
||||
$f2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
|
||||
$ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
|
||||
(0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
|
||||
|
||||
return $f1 / $f2 + 0.636619772 * (BesselJ::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum);
|
||||
return ($ans1 / $ans2) + 0.636619772 * (BesselJ::BESSELJ($x, 1) * log($x) - 1 / $x);
|
||||
}
|
||||
|
||||
return sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491);
|
||||
$z = 8.0 / $x;
|
||||
$y = $z * $z;
|
||||
$xx = $x - 2.356194491;
|
||||
$ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
|
||||
$ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y *
|
||||
(-0.88228987e-6 + $y * 0.105787412e-6)));
|
||||
|
||||
return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
|
||||
}
|
||||
|
||||
private static function besselY2(float $x, int $ord)
|
||||
private static function besselY2(float $x, int $ord): float
|
||||
{
|
||||
$fTox = 2 / $x;
|
||||
$fTox = 2.0 / $x;
|
||||
$fBym = self::besselY0($x);
|
||||
$fBy = self::besselY1($x);
|
||||
for ($n = 1; $n < $ord; ++$n) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,18 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
|||
|
||||
class BitWise
|
||||
{
|
||||
const SPLIT_DIVISOR = 2 ** 24;
|
||||
|
||||
/**
|
||||
* Split a number into upper and lower portions for full 32-bit support.
|
||||
*
|
||||
* @param float|int $number
|
||||
*/
|
||||
private static function splitNumber($number): array
|
||||
{
|
||||
return [floor($number / self::SPLIT_DIVISOR), fmod($number, self::SPLIT_DIVISOR)];
|
||||
}
|
||||
|
||||
/**
|
||||
* BITAND.
|
||||
*
|
||||
|
|
@ -28,8 +40,10 @@ class BitWise
|
|||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$split1 = self::splitNumber($number1);
|
||||
$split2 = self::splitNumber($number2);
|
||||
|
||||
return $number1 & $number2;
|
||||
return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,7 +68,10 @@ class BitWise
|
|||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $number1 | $number2;
|
||||
$split1 = self::splitNumber($number1);
|
||||
$split2 = self::splitNumber($number2);
|
||||
|
||||
return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -79,7 +96,10 @@ class BitWise
|
|||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $number1 ^ $number2;
|
||||
$split1 = self::splitNumber($number1);
|
||||
$split2 = self::splitNumber($number2);
|
||||
|
||||
return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,19 +113,18 @@ class BitWise
|
|||
* @param int $number
|
||||
* @param int $shiftAmount
|
||||
*
|
||||
* @return int|string
|
||||
* @return float|int|string
|
||||
*/
|
||||
public static function BITLSHIFT($number, $shiftAmount)
|
||||
{
|
||||
try {
|
||||
$number = self::validateBitwiseArgument($number);
|
||||
$shiftAmount = self::validateShiftAmount($shiftAmount);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$shiftAmount = Functions::flattenSingleValue($shiftAmount);
|
||||
|
||||
$result = $number << $shiftAmount;
|
||||
$result = floor($number * (2 ** $shiftAmount));
|
||||
if ($result > 2 ** 48 - 1) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
|
@ -124,19 +143,49 @@ class BitWise
|
|||
* @param int $number
|
||||
* @param int $shiftAmount
|
||||
*
|
||||
* @return int|string
|
||||
* @return float|int|string
|
||||
*/
|
||||
public static function BITRSHIFT($number, $shiftAmount)
|
||||
{
|
||||
try {
|
||||
$number = self::validateBitwiseArgument($number);
|
||||
$shiftAmount = self::validateShiftAmount($shiftAmount);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$shiftAmount = Functions::flattenSingleValue($shiftAmount);
|
||||
$result = floor($number / (2 ** $shiftAmount));
|
||||
if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return $number >> $shiftAmount;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate arguments passed to the bitwise functions.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
private static function validateBitwiseArgument($value)
|
||||
{
|
||||
self::nullFalseTrueToNumber($value);
|
||||
|
||||
if (is_numeric($value)) {
|
||||
if ($value == floor($value)) {
|
||||
if (($value > 2 ** 48 - 1) || ($value < 0)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return floor($value);
|
||||
}
|
||||
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -146,25 +195,33 @@ class BitWise
|
|||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function validateBitwiseArgument($value)
|
||||
private static function validateShiftAmount($value)
|
||||
{
|
||||
$value = Functions::flattenSingleValue($value);
|
||||
self::nullFalseTrueToNumber($value);
|
||||
|
||||
if (is_int($value)) {
|
||||
return $value;
|
||||
} elseif (is_numeric($value)) {
|
||||
if ($value == (int) ($value)) {
|
||||
$value = (int) ($value);
|
||||
if (($value > 2 ** 48 - 1) || ($value < 0)) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $value;
|
||||
if (is_numeric($value)) {
|
||||
if (abs($value) > 53) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
throw new Exception(Functions::NAN());
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null/false/true argument treated as 0/0/1.
|
||||
*
|
||||
* @param mixed $number
|
||||
*/
|
||||
public static function nullFalseTrueToNumber(&$number): void
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
if ($number === null) {
|
||||
$number = 0;
|
||||
} elseif (is_bool($number)) {
|
||||
$number = (int) $number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Compare
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* DELTA.
|
||||
*
|
||||
|
|
@ -27,8 +30,11 @@ class Compare
|
|||
$a = Functions::flattenSingleValue($a);
|
||||
$b = Functions::flattenSingleValue($b);
|
||||
|
||||
if (!is_numeric($a) || !is_numeric($b)) {
|
||||
return Functions::VALUE();
|
||||
try {
|
||||
$a = self::validateFloat($a);
|
||||
$b = self::validateFloat($b);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return (int) ($a == $b);
|
||||
|
|
@ -54,8 +60,11 @@ class Compare
|
|||
$number = Functions::flattenSingleValue($number);
|
||||
$step = Functions::flattenSingleValue($step);
|
||||
|
||||
if (!is_numeric($number) || !is_numeric($step)) {
|
||||
return Functions::VALUE();
|
||||
try {
|
||||
$number = self::validateFloat($number);
|
||||
$step = self::validateFloat($step);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return (int) ($number >= $step);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use Complex\Complex as ComplexObject;
|
||||
use Complex\Exception as ComplexException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Complex
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* COMPLEX.
|
||||
*
|
||||
* Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
|
||||
*
|
||||
* Excel Function:
|
||||
* COMPLEX(realNumber,imaginary[,suffix])
|
||||
*
|
||||
* @param mixed $realNumber the real float coefficient of the complex number
|
||||
* @param mixed $imaginary the imaginary float coefficient of the complex number
|
||||
* @param mixed $suffix The character suffix for the imaginary component of the complex number.
|
||||
* If omitted, the suffix is assumed to be "i".
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i')
|
||||
{
|
||||
$realNumber = ($realNumber === null) ? 0.0 : Functions::flattenSingleValue($realNumber);
|
||||
$imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary);
|
||||
$suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix);
|
||||
|
||||
try {
|
||||
$realNumber = self::validateFloat($realNumber);
|
||||
$imaginary = self::validateFloat($imaginary);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
|
||||
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
|
||||
|
||||
return (string) $complex;
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMAGINARY.
|
||||
*
|
||||
* Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMAGINARY(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the imaginary
|
||||
* coefficient
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMAGINARY($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return $complex->getImaginary();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMREAL.
|
||||
*
|
||||
* Returns the real coefficient of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMREAL(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the real coefficient
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMREAL($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return $complex->getReal();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use Complex\Complex as ComplexObject;
|
||||
use Complex\Exception as ComplexException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class ComplexFunctions
|
||||
{
|
||||
/**
|
||||
* IMABS.
|
||||
*
|
||||
* Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMABS(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the absolute value
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMABS($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return $complex->abs();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMARGUMENT.
|
||||
*
|
||||
* Returns the argument theta of a complex number, i.e. the angle in radians from the real
|
||||
* axis to the representation of the number in polar coordinates.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMARGUMENT(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the argument theta
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMARGUMENT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
return $complex->argument();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCONJUGATE.
|
||||
*
|
||||
* Returns the complex conjugate of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCONJUGATE(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the conjugate
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMCONJUGATE($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->conjugate();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCOS.
|
||||
*
|
||||
* Returns the cosine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCOS(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cosine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOS($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->cos();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCOSH.
|
||||
*
|
||||
* Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCOSH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOSH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->cosh();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCOT.
|
||||
*
|
||||
* Returns the cotangent of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCOT(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cotangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCOT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->cot();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCSC.
|
||||
*
|
||||
* Returns the cosecant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCSC(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->csc();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMCSCH.
|
||||
*
|
||||
* Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMCSCH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic cosecant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMCSCH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->csch();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSIN.
|
||||
*
|
||||
* Returns the sine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSIN(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the sine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSIN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->sin();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSINH.
|
||||
*
|
||||
* Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSINH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic sine
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSINH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->sinh();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSEC.
|
||||
*
|
||||
* Returns the secant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSEC(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSEC($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->sec();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSECH.
|
||||
*
|
||||
* Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSECH(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the hyperbolic secant
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMSECH($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->sech();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMTAN.
|
||||
*
|
||||
* Returns the tangent of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMTAN(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the tangent
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function IMTAN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->tan();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSQRT.
|
||||
*
|
||||
* Returns the square root of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSQRT(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the square root
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMSQRT($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$theta = self::IMARGUMENT($complexNumber);
|
||||
if ($theta === Functions::DIV0()) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
return (string) $complex->sqrt();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMLN.
|
||||
*
|
||||
* Returns the natural logarithm of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMLN(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the natural logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLN($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->ln();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMLOG10.
|
||||
*
|
||||
* Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMLOG10(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the common logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLOG10($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->log10();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMLOG2.
|
||||
*
|
||||
* Returns the base-2 logarithm of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMLOG2(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the base-2 logarithm
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMLOG2($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->log2();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMEXP.
|
||||
*
|
||||
* Returns the exponential of a complex number in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMEXP(complexNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number for which you want the exponential
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMEXP($complexNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $complex->exp();
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPOWER.
|
||||
*
|
||||
* Returns a complex number in x + yi or x + yj text format raised to a power.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMPOWER(complexNumber,realNumber)
|
||||
*
|
||||
* @param string $complexNumber the complex number you want to raise to a power
|
||||
* @param float $realNumber the power to which you want to raise the complex number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMPOWER($complexNumber, $realNumber)
|
||||
{
|
||||
$complexNumber = Functions::flattenSingleValue($complexNumber);
|
||||
$realNumber = Functions::flattenSingleValue($realNumber);
|
||||
|
||||
try {
|
||||
$complex = new ComplexObject($complexNumber);
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
if (!is_numeric($realNumber)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return (string) $complex->pow($realNumber);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
use Complex\Complex as ComplexObject;
|
||||
use Complex\Exception as ComplexException;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class ComplexOperations
|
||||
{
|
||||
/**
|
||||
* IMDIV.
|
||||
*
|
||||
* Returns the quotient of two complex numbers in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMDIV(complexDividend,complexDivisor)
|
||||
*
|
||||
* @param string $complexDividend the complex numerator or dividend
|
||||
* @param string $complexDivisor the complex denominator or divisor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMDIV($complexDividend, $complexDivisor)
|
||||
{
|
||||
$complexDividend = Functions::flattenSingleValue($complexDividend);
|
||||
$complexDivisor = Functions::flattenSingleValue($complexDivisor);
|
||||
|
||||
try {
|
||||
return (string) (new ComplexObject($complexDividend))->divideby(new ComplexObject($complexDivisor));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSUB.
|
||||
*
|
||||
* Returns the difference of two complex numbers in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSUB(complexNumber1,complexNumber2)
|
||||
*
|
||||
* @param string $complexNumber1 the complex number from which to subtract complexNumber2
|
||||
* @param string $complexNumber2 the complex number to subtract from complexNumber1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMSUB($complexNumber1, $complexNumber2)
|
||||
{
|
||||
$complexNumber1 = Functions::flattenSingleValue($complexNumber1);
|
||||
$complexNumber2 = Functions::flattenSingleValue($complexNumber2);
|
||||
|
||||
try {
|
||||
return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2));
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IMSUM.
|
||||
*
|
||||
* Returns the sum of two or more complex numbers in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMSUM(complexNumber[,complexNumber[,...]])
|
||||
*
|
||||
* @param string ...$complexNumbers Series of complex numbers to add
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMSUM(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = new ComplexObject(0.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->add(new ComplexObject($complex));
|
||||
}
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPRODUCT.
|
||||
*
|
||||
* Returns the product of two or more complex numbers in x + yi or x + yj text format.
|
||||
*
|
||||
* Excel Function:
|
||||
* IMPRODUCT(complexNumber[,complexNumber[,...]])
|
||||
*
|
||||
* @param string ...$complexNumbers Series of complex numbers to multiply
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function IMPRODUCT(...$complexNumbers)
|
||||
{
|
||||
// Return value
|
||||
$returnValue = new ComplexObject(1.0);
|
||||
$aArgs = Functions::flattenArray($complexNumbers);
|
||||
|
||||
try {
|
||||
// Loop through the arguments
|
||||
foreach ($aArgs as $complex) {
|
||||
$returnValue = $returnValue->multiply(new ComplexObject($complex));
|
||||
}
|
||||
} catch (ComplexException $e) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (string) $returnValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
|
||||
|
||||
class Constants
|
||||
{
|
||||
/**
|
||||
* EULER.
|
||||
*/
|
||||
public const EULER = 2.71828182845904523536;
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
|||
|
||||
class ConvertBase
|
||||
{
|
||||
protected static function validateValue($value, bool $gnumericCheck = false): string
|
||||
protected static function validateValue($value): string
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
|
|
@ -16,8 +16,10 @@ class ConvertBase
|
|||
$value = (int) $value;
|
||||
}
|
||||
|
||||
if ($gnumericCheck && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
$value = floor((float) $value);
|
||||
if (is_numeric($value)) {
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
$value = floor((float) $value);
|
||||
}
|
||||
}
|
||||
|
||||
return strtoupper((string) $value);
|
||||
|
|
@ -30,7 +32,7 @@ class ConvertBase
|
|||
}
|
||||
|
||||
if (is_numeric($places)) {
|
||||
if ($places < 0) {
|
||||
if ($places < 0 || $places > 10) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class ConvertBinary extends ConvertBase
|
|||
public static function toDecimal($value): string
|
||||
{
|
||||
try {
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value), true);
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value));
|
||||
$value = self::validateBinary($value);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
|
|
@ -65,7 +65,7 @@ class ConvertBinary extends ConvertBase
|
|||
public static function toHex($value, $places = null): string
|
||||
{
|
||||
try {
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value), true);
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value));
|
||||
$value = self::validateBinary($value);
|
||||
$places = self::validatePlaces(Functions::flattenSingleValue($places));
|
||||
} catch (Exception $e) {
|
||||
|
|
@ -73,8 +73,11 @@ class ConvertBinary extends ConvertBase
|
|||
}
|
||||
|
||||
if (strlen($value) == 10) {
|
||||
// Two's Complement
|
||||
return str_repeat('F', 8) . substr(strtoupper(dechex((int) bindec(substr($value, -9)))), -2);
|
||||
$high2 = substr($value, 0, 2);
|
||||
$low8 = substr($value, 2);
|
||||
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
|
||||
|
||||
return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2));
|
||||
}
|
||||
$hexVal = (string) strtoupper(dechex((int) bindec($value)));
|
||||
|
||||
|
|
@ -105,16 +108,15 @@ class ConvertBinary extends ConvertBase
|
|||
public static function toOctal($value, $places = null): string
|
||||
{
|
||||
try {
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value), true);
|
||||
$value = self::validateValue(Functions::flattenSingleValue($value));
|
||||
$value = self::validateBinary($value);
|
||||
$places = self::validatePlaces(Functions::flattenSingleValue($places));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (strlen($value) == 10) {
|
||||
// Two's Complement
|
||||
return str_repeat('7', 7) . substr(strtoupper(decoct((int) bindec(substr($value, -9)))), -3);
|
||||
if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement
|
||||
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
|
||||
}
|
||||
$octVal = (string) decoct((int) bindec($value));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,13 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
|||
|
||||
class ConvertDecimal extends ConvertBase
|
||||
{
|
||||
const LARGEST_OCTAL_IN_DECIMAL = 536870911;
|
||||
const SMALLEST_OCTAL_IN_DECIMAL = -536870912;
|
||||
const LARGEST_BINARY_IN_DECIMAL = 511;
|
||||
const SMALLEST_BINARY_IN_DECIMAL = -512;
|
||||
const LARGEST_HEX_IN_DECIMAL = 549755813887;
|
||||
const SMALLEST_HEX_IN_DECIMAL = -549755813888;
|
||||
|
||||
/**
|
||||
* toBinary.
|
||||
*
|
||||
|
|
@ -43,16 +50,13 @@ class ConvertDecimal extends ConvertBase
|
|||
}
|
||||
|
||||
$value = (int) floor((float) $value);
|
||||
if ($value < -512 || $value > 511) {
|
||||
if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$r = decbin($value);
|
||||
// Two's Complement
|
||||
$r = substr($r, -10);
|
||||
if (strlen($r) >= 11) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return self::nbrConversionFormat($r, $places);
|
||||
}
|
||||
|
|
@ -92,16 +96,37 @@ class ConvertDecimal extends ConvertBase
|
|||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$value = (int) floor((float) $value);
|
||||
$r = strtoupper(dechex($value));
|
||||
if (strlen($r) == 8) {
|
||||
// Two's Complement
|
||||
$r = 'FF' . $r;
|
||||
$value = floor((float) $value);
|
||||
if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
$r = strtoupper(dechex((int) $value));
|
||||
$r = self::hex32bit($value, $r);
|
||||
|
||||
return self::nbrConversionFormat($r, $places);
|
||||
}
|
||||
|
||||
public static function hex32bit(float $value, string $hexstr, bool $force = false): string
|
||||
{
|
||||
if (PHP_INT_SIZE === 4 || $force) {
|
||||
if ($value >= 2 ** 32) {
|
||||
$quotient = (int) ($value / (2 ** 32));
|
||||
|
||||
return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr);
|
||||
}
|
||||
if ($value < -(2 ** 32)) {
|
||||
$quotient = 256 - (int) ceil((-$value) / (2 ** 32));
|
||||
|
||||
return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8));
|
||||
}
|
||||
if ($value < 0) {
|
||||
return "FF$hexstr";
|
||||
}
|
||||
}
|
||||
|
||||
return $hexstr;
|
||||
}
|
||||
|
||||
/**
|
||||
* toOctal.
|
||||
*
|
||||
|
|
@ -138,11 +163,11 @@ class ConvertDecimal extends ConvertBase
|
|||
}
|
||||
|
||||
$value = (int) floor((float) $value);
|
||||
$r = decoct($value);
|
||||
if (strlen($r) == 11) {
|
||||
// Two's Complement
|
||||
$r = substr($r, -10);
|
||||
if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
$r = decoct($value);
|
||||
$r = substr($r, -10);
|
||||
|
||||
return self::nbrConversionFormat($r, $places);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ class ConvertHex extends ConvertBase
|
|||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return ConvertDecimal::toBinary(self::toDecimal($value), $places);
|
||||
$dec = self::toDecimal($value);
|
||||
|
||||
return ConvertDecimal::toBinary($dec, $places);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,9 +131,6 @@ class ConvertHex extends ConvertBase
|
|||
}
|
||||
|
||||
$decimal = self::toDecimal($value);
|
||||
if ($decimal < -536870912 || $decimal > 536870911) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return ConvertDecimal::toOctal($decimal, $places);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,14 +127,16 @@ class ConvertOctal extends ConvertBase
|
|||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$hexVal = strtoupper(dechex((int) self::toDecimal((int) $value)));
|
||||
$hexVal = strtoupper(dechex((int) self::toDecimal($value)));
|
||||
$hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF$hexVal" : $hexVal;
|
||||
|
||||
return self::nbrConversionFormat($hexVal, $places);
|
||||
}
|
||||
|
||||
protected static function validateOctal(string $value): string
|
||||
{
|
||||
if (strlen($value) > preg_match_all('/[01234567]/', $value)) {
|
||||
$numDigits = (int) preg_match_all('/[01234567]/', $value);
|
||||
if (strlen($value) > $numDigits || $numDigits > 10) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ class Erf
|
|||
* Excel Function:
|
||||
* ERF(lower[,upper])
|
||||
*
|
||||
* @param float $lower lower bound for integrating ERF
|
||||
* @param float $upper upper bound for integrating ERF.
|
||||
* If omitted, ERF integrates between zero and lower_limit
|
||||
* @param mixed $lower Lower bound float for integrating ERF
|
||||
* @param mixed $upper Upper bound float for integrating ERF.
|
||||
* If omitted, ERF integrates between zero and lower_limit
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
|
|
@ -52,7 +52,7 @@ class Erf
|
|||
* Excel Function:
|
||||
* ERF.PRECISE(limit)
|
||||
*
|
||||
* @param float $limit bound for integrating ERF
|
||||
* @param mixed $limit Float bound for integrating ERF, other bound is zero
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ErfC
|
|||
* Excel Function:
|
||||
* ERFC(x)
|
||||
*
|
||||
* @param float $value The lower bound for integrating ERFC
|
||||
* @param mixed $value The float lower bound for integrating ERFC
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Amortization
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* AMORDEGRC.
|
||||
*
|
||||
* Returns the depreciation for each accounting period.
|
||||
* This function is provided for the French accounting system. If an asset is purchased in
|
||||
* the middle of the accounting period, the prorated depreciation is taken into account.
|
||||
* The function is similar to AMORLINC, except that a depreciation coefficient is applied in
|
||||
* the calculation depending on the life of the assets.
|
||||
* This function will return the depreciation until the last period of the life of the assets
|
||||
* or until the cumulated value of depreciation is greater than the cost of the assets minus
|
||||
* the salvage value.
|
||||
*
|
||||
* Excel Function:
|
||||
* AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
|
||||
*
|
||||
* @param mixed $cost The float cost of the asset
|
||||
* @param mixed $purchased Date of the purchase of the asset
|
||||
* @param mixed $firstPeriod Date of the end of the first period
|
||||
* @param mixed $salvage The salvage value at the end of the life of the asset
|
||||
* @param mixed $period the period (float)
|
||||
* @param mixed $rate rate of depreciation (float)
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 0 or omitted US (NASD) 30/360
|
||||
* 1 Actual/actual
|
||||
* 2 Actual/360
|
||||
* 3 Actual/365
|
||||
* 4 European 30/360
|
||||
*
|
||||
* @return float|string (string containing the error type if there is an error)
|
||||
*/
|
||||
public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$purchased = Functions::flattenSingleValue($purchased);
|
||||
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$period = floor(Functions::flattenSingleValue($period));
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$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);
|
||||
if (is_string($yearFrac)) {
|
||||
return $yearFrac;
|
||||
}
|
||||
|
||||
$amortiseCoeff = self::getAmortizationCoefficient($rate);
|
||||
|
||||
$rate *= $amortiseCoeff;
|
||||
$fNRate = round($yearFrac * $rate * $cost, 0);
|
||||
$cost -= $fNRate;
|
||||
$fRest = $cost - $salvage;
|
||||
|
||||
for ($n = 0; $n < $period; ++$n) {
|
||||
$fNRate = round($rate * $cost, 0);
|
||||
$fRest -= $fNRate;
|
||||
|
||||
if ($fRest < 0.0) {
|
||||
switch ($period - $n) {
|
||||
case 0:
|
||||
case 1:
|
||||
return round($cost * 0.5, 0);
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
$cost -= $fNRate;
|
||||
}
|
||||
|
||||
return $fNRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* AMORLINC.
|
||||
*
|
||||
* Returns the depreciation for each accounting period.
|
||||
* This function is provided for the French accounting system. If an asset is purchased in
|
||||
* the middle of the accounting period, the prorated depreciation is taken into account.
|
||||
*
|
||||
* Excel Function:
|
||||
* AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
|
||||
*
|
||||
* @param mixed $cost The cost of the asset as a float
|
||||
* @param mixed $purchased Date of the purchase of the asset
|
||||
* @param mixed $firstPeriod Date of the end of the first period
|
||||
* @param mixed $salvage The salvage value at the end of the life of the asset
|
||||
* @param mixed $period The period as a float
|
||||
* @param mixed $rate Rate of depreciation as float
|
||||
* @param mixed $basis Integer indicating 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 (string containing the error type if there is an error)
|
||||
*/
|
||||
public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$purchased = Functions::flattenSingleValue($purchased);
|
||||
$firstPeriod = Functions::flattenSingleValue($firstPeriod);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$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;
|
||||
$fCostDelta = $cost - $salvage;
|
||||
// Note, quirky variation for leap years on the YEARFRAC for this function
|
||||
$purchasedYear = DateTimeExcel\Year::funcYear($purchased);
|
||||
$yearFrac = DateTimeExcel\YearFrac::funcYearFrac($purchased, $firstPeriod, $basis);
|
||||
if (is_string($yearFrac)) {
|
||||
return $yearFrac;
|
||||
}
|
||||
|
||||
if (($basis == 1) && ($yearFrac < 1) && (DateTimeExcel\Helpers::isLeapYear($purchasedYear))) {
|
||||
$yearFrac *= 365 / 366;
|
||||
}
|
||||
|
||||
$f0Rate = $yearFrac * $rate * $cost;
|
||||
$nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
|
||||
|
||||
if ($period == 0) {
|
||||
return $f0Rate;
|
||||
} elseif ($period <= $nNumOfFullPeriods) {
|
||||
return $fOneRate;
|
||||
} elseif ($period == ($nNumOfFullPeriods + 1)) {
|
||||
return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private static function getAmortizationCoefficient(float $rate): float
|
||||
{
|
||||
// The depreciation coefficients are:
|
||||
// Life of assets (1/rate) Depreciation coefficient
|
||||
// Less than 3 years 1
|
||||
// Between 3 and 4 years 1.5
|
||||
// Between 5 and 6 years 2
|
||||
// More than 6 years 2.5
|
||||
$fUsePer = 1.0 / $rate;
|
||||
|
||||
if ($fUsePer < 3.0) {
|
||||
return 1.0;
|
||||
} elseif ($fUsePer < 4.0) {
|
||||
return 1.5;
|
||||
} elseif ($fUsePer <= 6.0) {
|
||||
return 2.0;
|
||||
}
|
||||
|
||||
return 2.5;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\BaseValidations;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Single
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* FVSCHEDULE.
|
||||
*
|
||||
* Returns the future value of an initial principal after applying a series of compound interest rates.
|
||||
* Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
|
||||
*
|
||||
* Excel Function:
|
||||
* FVSCHEDULE(principal,schedule)
|
||||
*
|
||||
* @param mixed $principal the present value
|
||||
* @param float[] $schedule an array of interest rates to apply
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function futureValue($principal, $schedule)
|
||||
{
|
||||
$principal = Functions::flattenSingleValue($principal);
|
||||
$schedule = Functions::flattenArray($schedule);
|
||||
|
||||
try {
|
||||
$principal = self::validateFloat($principal);
|
||||
|
||||
foreach ($schedule as $rate) {
|
||||
$rate = self::validateFloat($rate);
|
||||
$principal *= 1 + $rate;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return $principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* PDURATION.
|
||||
*
|
||||
* Calculates the number of periods required for an investment to reach a specified value.
|
||||
*
|
||||
* @param float $rate Interest rate per period
|
||||
* @param float $presentValue Present Value
|
||||
* @param float $futureValue Future Value
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function periods($rate = 0.0, $presentValue = 0.0, $futureValue = 0.0)
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||
|
||||
try {
|
||||
$rate = self::validateFloat($rate);
|
||||
$presentValue = self::validateFloat($presentValue);
|
||||
$futureValue = self::validateFloat($futureValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return (log($futureValue) - log($presentValue)) / log(1 + $rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* RRI.
|
||||
*
|
||||
* Calculates the interest rate required for an investment to grow to a specified future value .
|
||||
*
|
||||
* @param float $periods The number of periods over which the investment is made
|
||||
* @param float $presentValue Present Value
|
||||
* @param float $futureValue Future Value
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0)
|
||||
{
|
||||
$periods = Functions::flattenSingleValue($periods);
|
||||
$presentValue = Functions::flattenSingleValue($presentValue);
|
||||
$futureValue = Functions::flattenSingleValue($futureValue);
|
||||
|
||||
try {
|
||||
$periods = self::validateFloat($periods);
|
||||
$presentValue = self::validateFloat($presentValue);
|
||||
$futureValue = self::validateFloat($futureValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return ($futureValue / $presentValue) ** (1 / $periods) - 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class NonPeriodic
|
||||
{
|
||||
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||
|
||||
const FINANCIAL_PRECISION = 1.0e-08;
|
||||
|
||||
/**
|
||||
* XIRR.
|
||||
*
|
||||
* Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
|
||||
*
|
||||
* Excel Function:
|
||||
* =XIRR(values,dates,guess)
|
||||
*
|
||||
* @param float[] $values A series of cash flow payments
|
||||
* The series of values must contain at least one positive value & one negative value
|
||||
* @param mixed[] $dates A series of payment dates
|
||||
* The first payment date indicates the beginning of the schedule of payments
|
||||
* All other dates must be later than this date, but they may occur in any order
|
||||
* @param float $guess An optional guess at the expected answer
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function rate($values, $dates, $guess = 0.1)
|
||||
{
|
||||
$rslt = self::xirrPart1($values, $dates);
|
||||
if ($rslt) {
|
||||
return $rslt;
|
||||
}
|
||||
|
||||
// create an initial range, with a root somewhere between 0 and guess
|
||||
$guess = Functions::flattenSingleValue($guess);
|
||||
$x1 = 0.0;
|
||||
$x2 = $guess ?: 0.1;
|
||||
$f1 = self::xnpvOrdered($x1, $values, $dates, false);
|
||||
$f2 = self::xnpvOrdered($x2, $values, $dates, false);
|
||||
$found = false;
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
if (!is_numeric($f1) || !is_numeric($f2)) {
|
||||
break;
|
||||
}
|
||||
if (($f1 * $f2) < 0.0) {
|
||||
$found = true;
|
||||
|
||||
break;
|
||||
} elseif (abs($f1) < abs($f2)) {
|
||||
$f1 = self::xnpvOrdered($x1 += 1.6 * ($x1 - $x2), $values, $dates, false);
|
||||
} else {
|
||||
$f2 = self::xnpvOrdered($x2 += 1.6 * ($x2 - $x1), $values, $dates, false);
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return self::xirrPart3($values, $dates, $x1, $x2);
|
||||
}
|
||||
|
||||
/**
|
||||
* XNPV.
|
||||
*
|
||||
* Returns the net present value for a schedule of cash flows that is not necessarily periodic.
|
||||
* To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
|
||||
*
|
||||
* Excel Function:
|
||||
* =XNPV(rate,values,dates)
|
||||
*
|
||||
* @param float $rate the discount rate to apply to the cash flows
|
||||
* @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
|
||||
* The first payment is optional and corresponds to a cost or payment that occurs
|
||||
* at the beginning of the investment.
|
||||
* If the first value is a cost or payment, it must be a negative value.
|
||||
* All succeeding payments are discounted based on a 365-day year.
|
||||
* The series of values must contain at least one positive value and one negative value.
|
||||
* @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
|
||||
* The first payment date indicates the beginning of the schedule of payments.
|
||||
* All other dates must be later than this date, but they may occur in any order.
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function presentValue($rate, $values, $dates)
|
||||
{
|
||||
return self::xnpvOrdered($rate, $values, $dates, true);
|
||||
}
|
||||
|
||||
private static function bothNegAndPos($neg, $pos)
|
||||
{
|
||||
return $neg && $pos;
|
||||
}
|
||||
|
||||
private static function xirrPart1(&$values, &$dates)
|
||||
{
|
||||
if ((!is_array($values)) && (!is_array($dates))) {
|
||||
return Functions::NA();
|
||||
}
|
||||
$values = Functions::flattenArray($values);
|
||||
$dates = Functions::flattenArray($dates);
|
||||
if (count($values) != count($dates)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$datesCount = count($dates);
|
||||
for ($i = 0; $i < $datesCount; ++$i) {
|
||||
try {
|
||||
$dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return self::xirrPart2($values);
|
||||
}
|
||||
|
||||
private static function xirrPart2(&$values)
|
||||
{
|
||||
$valCount = count($values);
|
||||
$foundpos = false;
|
||||
$foundneg = false;
|
||||
for ($i = 0; $i < $valCount; ++$i) {
|
||||
$fld = $values[$i];
|
||||
if (!is_numeric($fld)) {
|
||||
return Functions::VALUE();
|
||||
} elseif ($fld > 0) {
|
||||
$foundpos = true;
|
||||
} elseif ($fld < 0) {
|
||||
$foundneg = true;
|
||||
}
|
||||
}
|
||||
if (!self::bothNegAndPos($foundneg, $foundpos)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function xirrPart3($values, $dates, $x1, $x2)
|
||||
{
|
||||
$f = self::xnpvOrdered($x1, $values, $dates, false);
|
||||
if ($f < 0.0) {
|
||||
$rtb = $x1;
|
||||
$dx = $x2 - $x1;
|
||||
} else {
|
||||
$rtb = $x2;
|
||||
$dx = $x1 - $x2;
|
||||
}
|
||||
|
||||
$rslt = Functions::VALUE();
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$dx *= 0.5;
|
||||
$x_mid = $rtb + $dx;
|
||||
$f_mid = self::xnpvOrdered($x_mid, $values, $dates, false);
|
||||
if ($f_mid <= 0.0) {
|
||||
$rtb = $x_mid;
|
||||
}
|
||||
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
||||
$rslt = $x_mid;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rslt;
|
||||
}
|
||||
|
||||
private static function xnpvOrdered($rate, $values, $dates, $ordered = true)
|
||||
{
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$values = Functions::flattenArray($values);
|
||||
$dates = Functions::flattenArray($dates);
|
||||
$valCount = count($values);
|
||||
|
||||
try {
|
||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$rslt = self::validateXnpv($rate, $values, $dates);
|
||||
if ($rslt) {
|
||||
return $rslt;
|
||||
}
|
||||
$xnpv = 0.0;
|
||||
for ($i = 0; $i < $valCount; ++$i) {
|
||||
if (!is_numeric($values[$i])) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
try {
|
||||
$datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if ($date0 > $datei) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$dif = $ordered ? Functions::NAN() : -DateTimeExcel\DateDif::funcDateDif($datei, $date0, 'd');
|
||||
} else {
|
||||
$dif = DateTimeExcel\DateDif::funcDateDif($date0, $datei, 'd');
|
||||
}
|
||||
if (!is_numeric($dif)) {
|
||||
return $dif;
|
||||
}
|
||||
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
|
||||
}
|
||||
|
||||
return is_finite($xnpv) ? $xnpv : Functions::VALUE();
|
||||
}
|
||||
|
||||
private static function validateXnpv($rate, $values, $dates)
|
||||
{
|
||||
if (!is_numeric($rate)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$valCount = count($values);
|
||||
if ($valCount != count($dates)) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
$date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
|
||||
if (is_string($date0)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Periodic
|
||||
{
|
||||
const FINANCIAL_MAX_ITERATIONS = 128;
|
||||
|
||||
const FINANCIAL_PRECISION = 1.0e-08;
|
||||
|
||||
/**
|
||||
* IRR.
|
||||
*
|
||||
* Returns the internal rate of return for a series of cash flows represented by the numbers in values.
|
||||
* These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
|
||||
* at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
|
||||
* for an investment consisting of payments (negative values) and income (positive values) that occur at regular
|
||||
* periods.
|
||||
*
|
||||
* Excel Function:
|
||||
* IRR(values[,guess])
|
||||
*
|
||||
* @param mixed $values An array or a reference to cells that contain numbers for which you want
|
||||
* to calculate the internal rate of return.
|
||||
* Values must contain at least one positive value and one negative value to
|
||||
* calculate the internal rate of return.
|
||||
* @param mixed $guess A number that you guess is close to the result of IRR
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function rate($values, $guess = 0.1)
|
||||
{
|
||||
if (!is_array($values)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$values = Functions::flattenArray($values);
|
||||
$guess = Functions::flattenSingleValue($guess);
|
||||
|
||||
// create an initial range, with a root somewhere between 0 and guess
|
||||
$x1 = 0.0;
|
||||
$x2 = $guess;
|
||||
$f1 = self::presentValue($x1, $values);
|
||||
$f2 = self::presentValue($x2, $values);
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
if (($f1 * $f2) < 0.0) {
|
||||
break;
|
||||
}
|
||||
if (abs($f1) < abs($f2)) {
|
||||
$f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values);
|
||||
} else {
|
||||
$f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values);
|
||||
}
|
||||
}
|
||||
if (($f1 * $f2) > 0.0) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$f = self::presentValue($x1, $values);
|
||||
if ($f < 0.0) {
|
||||
$rtb = $x1;
|
||||
$dx = $x2 - $x1;
|
||||
} else {
|
||||
$rtb = $x2;
|
||||
$dx = $x1 - $x2;
|
||||
}
|
||||
|
||||
for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
|
||||
$dx *= 0.5;
|
||||
$x_mid = $rtb + $dx;
|
||||
$f_mid = self::presentValue($x_mid, $values);
|
||||
if ($f_mid <= 0.0) {
|
||||
$rtb = $x_mid;
|
||||
}
|
||||
if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
|
||||
return $x_mid;
|
||||
}
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* MIRR.
|
||||
*
|
||||
* Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
|
||||
* the cost of the investment and the interest received on reinvestment of cash.
|
||||
*
|
||||
* Excel Function:
|
||||
* MIRR(values,finance_rate, reinvestment_rate)
|
||||
*
|
||||
* @param mixed $values An array or a reference to cells that contain a series of payments and
|
||||
* income occurring at regular intervals.
|
||||
* Payments are negative value, income is positive values.
|
||||
* @param mixed $financeRate The interest rate you pay on the money used in the cash flows
|
||||
* @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function modifiedRate($values, $financeRate, $reinvestmentRate)
|
||||
{
|
||||
if (!is_array($values)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$values = Functions::flattenArray($values);
|
||||
$financeRate = Functions::flattenSingleValue($financeRate);
|
||||
$reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
|
||||
$n = count($values);
|
||||
|
||||
$rr = 1.0 + $reinvestmentRate;
|
||||
$fr = 1.0 + $financeRate;
|
||||
|
||||
$npvPos = $npvNeg = 0.0;
|
||||
foreach ($values as $i => $v) {
|
||||
if ($v >= 0) {
|
||||
$npvPos += $v / $rr ** $i;
|
||||
} else {
|
||||
$npvNeg += $v / $fr ** $i;
|
||||
}
|
||||
}
|
||||
|
||||
if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
|
||||
$mirr = ((-$npvPos * $rr ** $n)
|
||||
/ ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
|
||||
|
||||
return is_finite($mirr) ? $mirr : Functions::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* NPV.
|
||||
*
|
||||
* Returns the Net Present Value of a cash flow series given a discount rate.
|
||||
*
|
||||
* @param mixed $rate
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function presentValue($rate, ...$args)
|
||||
{
|
||||
$returnValue = 0;
|
||||
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$aArgs = Functions::flattenArray($args);
|
||||
|
||||
// Calculate
|
||||
$countArgs = count($aArgs);
|
||||
for ($i = 1; $i <= $countArgs; ++$i) {
|
||||
// Is it a numeric value?
|
||||
if (is_numeric($aArgs[$i - 1])) {
|
||||
$returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
|
||||
}
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
class Coupons
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
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 mixed $frequency The number of coupon payments per year (int).
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $basis The type of day count to use (int).
|
||||
* 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(DateTimeExcel\Year::funcYear($settlement), $basis);
|
||||
if (is_string($daysPerYear)) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS);
|
||||
|
||||
if ($basis === Helpers::DAYS_PER_YEAR_ACTUAL) {
|
||||
return abs(DateTimeExcel\Days::funcDays($prev, $settlement));
|
||||
}
|
||||
|
||||
return DateTimeExcel\YearFrac::funcYearFrac($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 mixed $basis The type of day count to use (int).
|
||||
* 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(DateTimeExcel\Year::funcYear($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 mixed $basis The type of day count to use (int) .
|
||||
* 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(DateTimeExcel\Year::funcYear($settlement), $basis);
|
||||
$next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT);
|
||||
|
||||
if ($basis === Helpers::DAYS_PER_YEAR_NASD) {
|
||||
$settlementDate = Date::excelToDateTimeObject($settlement);
|
||||
$settlementEoM = Helpers::isLastDayOfMonth($settlementDate);
|
||||
if ($settlementEoM) {
|
||||
++$settlement;
|
||||
}
|
||||
}
|
||||
|
||||
return DateTimeExcel\YearFrac::funcYearFrac($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 mixed $basis The type of day count to use (int).
|
||||
* 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 mixed $basis The type of day count to use (int).
|
||||
* 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 = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, 0);
|
||||
|
||||
return (int) 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 mixed $basis The type of day count to use (int).
|
||||
* 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);
|
||||
}
|
||||
|
||||
private static function couponFirstPeriodDate($settlement, $maturity, int $frequency, $next)
|
||||
{
|
||||
$months = 12 / $frequency;
|
||||
|
||||
$result = Date::excelToDateTimeObject($maturity);
|
||||
$maturityEoM = Helpers::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 validateCouponPeriod($settlement, $maturity): void
|
||||
{
|
||||
if ($settlement >= $maturity) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Depreciation
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* DB.
|
||||
*
|
||||
* Returns the depreciation of an asset for a specified period using the
|
||||
* fixed-declining balance method.
|
||||
* This form of depreciation is used if you want to get a higher depreciation value
|
||||
* at the beginning of the depreciation (as opposed to linear depreciation). The
|
||||
* depreciation value is reduced with every depreciation period by the depreciation
|
||||
* already deducted from the initial cost.
|
||||
*
|
||||
* Excel Function:
|
||||
* DB(cost,salvage,life,period[,month])
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation.
|
||||
* (Sometimes called the salvage value of the asset)
|
||||
* @param mixed $life Number of periods over which the asset is depreciated.
|
||||
* (Sometimes called the useful life of the asset)
|
||||
* @param mixed $period The period for which you want to calculate the
|
||||
* depreciation. Period must use the same units as life.
|
||||
* @param mixed $month Number of months in the first year. If month is omitted,
|
||||
* it defaults to 12.
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DB($cost, $salvage, $life, $period, $month = 12)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$month = Functions::flattenSingleValue($month);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
$month = self::validateMonth($month);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($cost === 0.0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Set Fixed Depreciation Rate
|
||||
$fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life);
|
||||
$fixedDepreciationRate = round($fixedDepreciationRate, 3);
|
||||
|
||||
// Loop through each period calculating the depreciation
|
||||
// TODO Handle period value between 0 and 1 (e.g. 0.5)
|
||||
$previousDepreciation = 0;
|
||||
$depreciation = 0;
|
||||
for ($per = 1; $per <= $period; ++$per) {
|
||||
if ($per == 1) {
|
||||
$depreciation = $cost * $fixedDepreciationRate * $month / 12;
|
||||
} elseif ($per == ($life + 1)) {
|
||||
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12;
|
||||
} else {
|
||||
$depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate;
|
||||
}
|
||||
$previousDepreciation += $depreciation;
|
||||
}
|
||||
|
||||
return $depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* DDB.
|
||||
*
|
||||
* Returns the depreciation of an asset for a specified period using the
|
||||
* double-declining balance method or some other method you specify.
|
||||
*
|
||||
* Excel Function:
|
||||
* DDB(cost,salvage,life,period[,factor])
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation.
|
||||
* (Sometimes called the salvage value of the asset)
|
||||
* @param mixed $life Number of periods over which the asset is depreciated.
|
||||
* (Sometimes called the useful life of the asset)
|
||||
* @param mixed $period The period for which you want to calculate the
|
||||
* depreciation. Period must use the same units as life.
|
||||
* @param mixed $factor The rate at which the balance declines.
|
||||
* If factor is omitted, it is assumed to be 2 (the
|
||||
* double-declining balance method).
|
||||
*
|
||||
* @return float|string
|
||||
*/
|
||||
public static function DDB($cost, $salvage, $life, $period, $factor = 2.0)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
$factor = Functions::flattenSingleValue($factor);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
$factor = self::validateFactor($factor);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($period > $life) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
// Loop through each period calculating the depreciation
|
||||
// TODO Handling for fractional $period values
|
||||
$previousDepreciation = 0;
|
||||
$depreciation = 0;
|
||||
for ($per = 1; $per <= $period; ++$per) {
|
||||
$depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation));
|
||||
$previousDepreciation += $depreciation;
|
||||
}
|
||||
|
||||
return $depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* SLN.
|
||||
*
|
||||
* Returns the straight-line depreciation of an asset for one period
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function SLN($cost, $salvage, $life)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost, true);
|
||||
$salvage = self::validateSalvage($salvage, true);
|
||||
$life = self::validateLife($life, true);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($life === 0.0) {
|
||||
return Functions::DIV0();
|
||||
}
|
||||
|
||||
return ($cost - $salvage) / $life;
|
||||
}
|
||||
|
||||
/**
|
||||
* SYD.
|
||||
*
|
||||
* Returns the sum-of-years' digits depreciation of an asset for a specified period.
|
||||
*
|
||||
* @param mixed $cost Initial cost of the asset
|
||||
* @param mixed $salvage Value at the end of the depreciation
|
||||
* @param mixed $life Number of periods over which the asset is depreciated
|
||||
* @param mixed $period Period
|
||||
*
|
||||
* @return float|string Result, or a string containing an error
|
||||
*/
|
||||
public static function SYD($cost, $salvage, $life, $period)
|
||||
{
|
||||
$cost = Functions::flattenSingleValue($cost);
|
||||
$salvage = Functions::flattenSingleValue($salvage);
|
||||
$life = Functions::flattenSingleValue($life);
|
||||
$period = Functions::flattenSingleValue($period);
|
||||
|
||||
try {
|
||||
$cost = self::validateCost($cost, true);
|
||||
$salvage = self::validateSalvage($salvage);
|
||||
$life = self::validateLife($life);
|
||||
$period = self::validatePeriod($period);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($period > $life) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1));
|
||||
|
||||
return $syd;
|
||||
}
|
||||
|
||||
private static function validateCost($cost, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$cost = self::validateFloat($cost);
|
||||
if ($cost < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $cost;
|
||||
}
|
||||
|
||||
private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$salvage = self::validateFloat($salvage);
|
||||
if ($salvage < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $salvage;
|
||||
}
|
||||
|
||||
private static function validateLife($life, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$life = self::validateFloat($life);
|
||||
if ($life < 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $life;
|
||||
}
|
||||
|
||||
private static function validatePeriod($period, bool $negativeValueAllowed = false): float
|
||||
{
|
||||
$period = self::validateFloat($period);
|
||||
if ($period <= 0.0 && $negativeValueAllowed === false) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $period;
|
||||
}
|
||||
|
||||
private static function validateMonth($month): int
|
||||
{
|
||||
$month = self::validateInt($month);
|
||||
if ($month < 1) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $month;
|
||||
}
|
||||
|
||||
private static function validateFactor($factor): float
|
||||
{
|
||||
$factor = self::validateFloat($factor);
|
||||
if ($factor <= 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $factor;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 mixed $fractionalDollar Fractional Dollar
|
||||
* @param mixed $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 mixed $decimalDollar Decimal Dollar
|
||||
* @param mixed $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use DateTimeInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
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 (DateTimeExcel\Helpers::isLeapYear($year)) ? 366 : 365;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class InterestRate
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* 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 mixed $nominalRate Nominal interest rate as a float
|
||||
* @param mixed $periodsPerYear Integer 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);
|
||||
|
||||
try {
|
||||
$nominalRate = self::validateFloat($nominalRate);
|
||||
$periodsPerYear = self::validateInt($periodsPerYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($nominalRate <= 0 || $periodsPerYear < 1) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
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 mixed $effectiveRate Effective interest rate as a float
|
||||
* @param mixed $periodsPerYear Integer 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);
|
||||
|
||||
try {
|
||||
$effectiveRate = self::validateFloat($effectiveRate);
|
||||
$periodsPerYear = self::validateInt($periodsPerYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($effectiveRate <= 0 || $periodsPerYear < 1) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
// Calculate
|
||||
return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\YearFrac;
|
||||
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 $rate The security's annual coupon rate
|
||||
* @param mixed $parValue The security's par value.
|
||||
* If you omit par, ACCRINT uses $1,000.
|
||||
* @param mixed $frequency The number of coupon payments per year.
|
||||
* Valid frequency values are:
|
||||
* 1 Annual
|
||||
* 2 Semi-Annual
|
||||
* 4 Quarterly
|
||||
* @param mixed $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 $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 = YearFrac::funcYearFrac($issue, $settlement, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenFirstInterestAndSettlement = YearFrac::funcYearFrac($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 $rate The security's annual coupon rate
|
||||
* @param mixed $parValue The security's par value.
|
||||
* If you omit par, ACCRINT uses $1,000.
|
||||
* @param mixed $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 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 = YearFrac::funcYearFrac($issue, $settlement, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
|
||||
return $parValue * $rate * $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
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 validateFloat($value): float
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
return (float) $value;
|
||||
}
|
||||
|
||||
protected static function validateSettlementDate($settlement)
|
||||
{
|
||||
return self::validateDate($settlement);
|
||||
}
|
||||
|
||||
protected static function validateMaturityDate($maturity)
|
||||
{
|
||||
return self::validateDate($maturity);
|
||||
}
|
||||
|
||||
protected static function validateIssueDate($issue)
|
||||
{
|
||||
return self::validateDate($issue);
|
||||
}
|
||||
|
||||
protected static function validateSecurityPeriod($settlement, $maturity): void
|
||||
{
|
||||
if ($settlement >= $maturity) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
}
|
||||
|
||||
protected static function validateRate($rate): float
|
||||
{
|
||||
$rate = self::validateFloat($rate);
|
||||
if ($rate < 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
protected static function validateParValue($parValue): float
|
||||
{
|
||||
$parValue = self::validateFloat($parValue);
|
||||
if ($parValue < 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $parValue;
|
||||
}
|
||||
|
||||
protected static function validatePrice($price): float
|
||||
{
|
||||
$price = self::validateFloat($price);
|
||||
if ($price < 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
protected static function validateYield($yield): float
|
||||
{
|
||||
$yield = self::validateFloat($yield);
|
||||
if ($yield < 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $yield;
|
||||
}
|
||||
|
||||
protected static function validateRedemption($redemption): float
|
||||
{
|
||||
$redemption = self::validateFloat($redemption);
|
||||
if ($redemption <= 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $redemption;
|
||||
}
|
||||
|
||||
protected static function validateDiscount($discount): float
|
||||
{
|
||||
$discount = self::validateFloat($discount);
|
||||
if ($discount <= 0.0) {
|
||||
throw new Exception(Functions::NAN());
|
||||
}
|
||||
|
||||
return $discount;
|
||||
}
|
||||
|
||||
protected static function validateFrequency($frequency): int
|
||||
{
|
||||
if (!is_numeric($frequency)) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
$frequency = (int) $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
class Constants
|
||||
{
|
||||
public const FREQUENCY_ANNUAL = 1;
|
||||
public const FREQUENCY_SEMI_ANNUAL = 2;
|
||||
public const FREQUENCY_QUARTERLY = 4;
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Price
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* PRICE.
|
||||
*
|
||||
* Returns the price per $100 face value of a security that pays periodic interest.
|
||||
*
|
||||
* @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 $rate the security's annual coupon rate
|
||||
* @param mixed $yield the security's annual yield
|
||||
* @param mixed $redemption The number of coupon payments per year.
|
||||
* For annual payments, frequency = 1;
|
||||
* for semiannual, frequency = 2;
|
||||
* for quarterly, frequency = 4.
|
||||
* @param mixed $frequency
|
||||
* @param mixed $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 Result, or a string containing an error
|
||||
*/
|
||||
public static function price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0)
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$yield = Functions::flattenSingleValue($yield);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$frequency = Functions::flattenSingleValue($frequency);
|
||||
$basis = Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
self::validateSecurityPeriod($settlement, $maturity);
|
||||
$rate = self::validateRate($rate);
|
||||
$yield = self::validateYield($yield);
|
||||
$redemption = self::validateRedemption($redemption);
|
||||
$frequency = self::validateFrequency($frequency);
|
||||
$basis = self::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$dsc = Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis);
|
||||
$e = Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis);
|
||||
$n = Coupons::COUPNUM($settlement, $maturity, $frequency, $basis);
|
||||
$a = Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis);
|
||||
|
||||
$baseYF = 1.0 + ($yield / $frequency);
|
||||
$rfp = 100 * ($rate / $frequency);
|
||||
$de = $dsc / $e;
|
||||
|
||||
$result = $redemption / $baseYF ** (--$n + $de);
|
||||
for ($k = 0; $k <= $n; ++$k) {
|
||||
$result += $rfp / ($baseYF ** ($k + $de));
|
||||
}
|
||||
$result -= $rfp * ($a / $e);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* PRICEDISC.
|
||||
*
|
||||
* Returns the price per $100 face value of a discounted security.
|
||||
*
|
||||
* @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 $discount The security's discount rate
|
||||
* @param mixed $redemption The security's redemption value per $100 face value
|
||||
* @param mixed $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 Result, or a string containing an error
|
||||
*/
|
||||
public static function priceDiscounted($settlement, $maturity, $discount, $redemption, $basis = 0)
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$discount = Functions::flattenSingleValue($discount);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
self::validateSecurityPeriod($settlement, $maturity);
|
||||
$discount = self::validateDiscount($discount);
|
||||
$redemption = self::validateRedemption($redemption);
|
||||
$basis = self::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
|
||||
return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
/**
|
||||
* PRICEMAT.
|
||||
*
|
||||
* Returns the price per $100 face value of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's 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 $issue The security's issue date
|
||||
* @param mixed $rate The security's interest rate at date of issue
|
||||
* @param mixed $yield The security's annual yield
|
||||
* @param mixed $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 Result, or a string containing an error
|
||||
*/
|
||||
public static function priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis = 0)
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$yield = Functions::flattenSingleValue($yield);
|
||||
$basis = Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
self::validateSecurityPeriod($settlement, $maturity);
|
||||
$issue = self::validateIssueDate($issue);
|
||||
$rate = self::validateRate($rate);
|
||||
$yield = self::validateYield($yield);
|
||||
$basis = self::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::funcYearFrac($issue, $settlement, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement *= $daysPerYear;
|
||||
$daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($issue, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndMaturity;
|
||||
}
|
||||
$daysBetweenIssueAndMaturity *= $daysPerYear;
|
||||
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) /
|
||||
(1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) -
|
||||
(($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Yields
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* YIELDDISC.
|
||||
*
|
||||
* Returns the annual yield of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's 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 $price The security's price per $100 face value
|
||||
* @param int $redemption The security's redemption value per $100 face value
|
||||
* @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 Result, or a string containing an error
|
||||
*/
|
||||
public static function yieldDiscounted($settlement, $maturity, $price, $redemption, $basis = 0)
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
$redemption = Functions::flattenSingleValue($redemption);
|
||||
$basis = (int) Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
self::validateSecurityPeriod($settlement, $maturity);
|
||||
$price = self::validatePrice($price);
|
||||
$redemption = self::validateRedemption($redemption);
|
||||
$basis = self::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
/**
|
||||
* YIELDMAT.
|
||||
*
|
||||
* Returns the annual yield of a security that pays interest at maturity.
|
||||
*
|
||||
* @param mixed $settlement The security's settlement date.
|
||||
* The security's 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 $issue The security's issue date
|
||||
* @param int $rate The security's interest rate at date of issue
|
||||
* @param int $price The security's price per $100 face value
|
||||
* @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 Result, or a string containing an error
|
||||
*/
|
||||
public static function yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis = 0)
|
||||
{
|
||||
$settlement = Functions::flattenSingleValue($settlement);
|
||||
$maturity = Functions::flattenSingleValue($maturity);
|
||||
$issue = Functions::flattenSingleValue($issue);
|
||||
$rate = Functions::flattenSingleValue($rate);
|
||||
$price = Functions::flattenSingleValue($price);
|
||||
$basis = Functions::flattenSingleValue($basis);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
self::validateSecurityPeriod($settlement, $maturity);
|
||||
$issue = self::validateIssueDate($issue);
|
||||
$rate = self::validateRate($rate);
|
||||
$price = self::validatePrice($price);
|
||||
$basis = self::validateBasis($basis);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($settlement), $basis);
|
||||
if (!is_numeric($daysPerYear)) {
|
||||
return $daysPerYear;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement = DateTimeExcel\YearFrac::funcYearFrac($issue, $settlement, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndSettlement)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndSettlement;
|
||||
}
|
||||
$daysBetweenIssueAndSettlement *= $daysPerYear;
|
||||
$daysBetweenIssueAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($issue, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenIssueAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenIssueAndMaturity;
|
||||
}
|
||||
$daysBetweenIssueAndMaturity *= $daysPerYear;
|
||||
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::funcYearFrac($settlement, $maturity, $basis);
|
||||
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
|
||||
// return date error
|
||||
return $daysBetweenSettlementAndMaturity;
|
||||
}
|
||||
$daysBetweenSettlementAndMaturity *= $daysPerYear;
|
||||
|
||||
return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) /
|
||||
(($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) *
|
||||
($daysPerYear / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class TreasuryBill
|
||||
{
|
||||
use BaseValidations;
|
||||
|
||||
/**
|
||||
* 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 mixed $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);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (is_numeric($discount)) {
|
||||
if ($discount <= 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($maturity), Helpers::DAYS_PER_YEAR_ACTUAL);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $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 mixed $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);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (is_numeric($discount)) {
|
||||
if ($discount <= 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($maturity), Helpers::DAYS_PER_YEAR_ACTUAL);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $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 mixed $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);
|
||||
|
||||
try {
|
||||
$settlement = self::validateSettlementDate($settlement);
|
||||
$maturity = self::validateMaturityDate($maturity);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (is_numeric($price)) {
|
||||
if ($price <= 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
$daysBetweenSettlementAndMaturity = $maturity - $settlement;
|
||||
$daysPerYear = Helpers::daysPerYear(DateTimeExcel\Year::funcYear($maturity), Helpers::DAYS_PER_YEAR_ACTUAL);
|
||||
|
||||
if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) {
|
||||
return Functions::NAN();
|
||||
}
|
||||
|
||||
return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity);
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
}
|
||||
}
|
||||
|
|
@ -576,7 +576,7 @@ class Functions
|
|||
/**
|
||||
* Convert a multi-dimensional array to a simple 1-dimensional array.
|
||||
*
|
||||
* @param array $array Array to be flattened
|
||||
* @param array|mixed $array Array to be flattened
|
||||
*
|
||||
* @return array Flattened array
|
||||
*/
|
||||
|
|
@ -609,7 +609,7 @@ class Functions
|
|||
/**
|
||||
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
|
||||
*
|
||||
* @param array $array Array to be flattened
|
||||
* @param array|mixed $array Array to be flattened
|
||||
*
|
||||
* @return array Flattened array
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -83,11 +83,11 @@ class Conditional
|
|||
$targetValue = Functions::flattenSingleValue($arguments[0]);
|
||||
$argc = count($arguments) - 1;
|
||||
$switchCount = floor($argc / 2);
|
||||
$switchSatisfied = false;
|
||||
$hasDefaultClause = $argc % 2 !== 0;
|
||||
$defaultClause = $argc % 2 === 0 ? null : $arguments[count($arguments) - 1];
|
||||
$defaultClause = $argc % 2 === 0 ? null : $arguments[$argc];
|
||||
|
||||
if ($switchCount) {
|
||||
$switchSatisfied = false;
|
||||
if ($switchCount > 0) {
|
||||
for ($index = 0; $index < $switchCount; ++$index) {
|
||||
if ($targetValue == $arguments[$index * 2 + 1]) {
|
||||
$result = $arguments[$index * 2 + 2];
|
||||
|
|
@ -98,7 +98,7 @@ class Conditional
|
|||
}
|
||||
}
|
||||
|
||||
if (!$switchSatisfied) {
|
||||
if ($switchSatisfied !== true) {
|
||||
$result = $hasDefaultClause ? $defaultClause : Functions::NA();
|
||||
}
|
||||
}
|
||||
|
|
@ -161,12 +161,14 @@ class Conditional
|
|||
*/
|
||||
public static function IFS(...$arguments)
|
||||
{
|
||||
if (count($arguments) % 2 != 0) {
|
||||
$argumentCount = count($arguments);
|
||||
|
||||
if ($argumentCount % 2 != 0) {
|
||||
return Functions::NA();
|
||||
}
|
||||
// We use instance of Exception as a falseValue in order to prevent string collision with value in cell
|
||||
$falseValueException = new Exception();
|
||||
for ($i = 0; $i < count($arguments); $i += 2) {
|
||||
for ($i = 0; $i < $argumentCount; $i += 2) {
|
||||
$testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]);
|
||||
$returnIfTrue = ($arguments[$i + 1] === null) ? '' : Functions::flattenSingleValue($arguments[$i + 1]);
|
||||
$result = self::statementIf($testValue, $returnIfTrue, $falseValueException);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Lookup;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class LookupRef
|
||||
|
|
@ -147,8 +148,8 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =HYPERLINK(linkURL,displayName)
|
||||
*
|
||||
* @param string $linkURL Value to check, is also the value returned when no error
|
||||
* @param string $displayName Value to return when testValue is an error condition
|
||||
* @param mixed $linkURL URL Value to check, is also the value returned when no error
|
||||
* @param mixed $displayName String Value to return when testValue is an error condition
|
||||
* @param Cell $pCell The cell to set the hyperlink in
|
||||
*
|
||||
* @return mixed The value of $displayName (or $linkURL if $displayName was blank)
|
||||
|
|
@ -181,56 +182,22 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =INDIRECT(cellAddress)
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the INDIRECT() method in the LookupRef\Indirect class instead
|
||||
*
|
||||
* NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
|
||||
*
|
||||
* @param null|array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
|
||||
* @param Cell $pCell The current cell (containing this formula)
|
||||
*
|
||||
* @return mixed The cells referenced by cellAddress
|
||||
* @return array|string An array containing a cell or range of cells, or a string on error
|
||||
*
|
||||
* @TODO Support for the optional a1 parameter introduced in Excel 2010
|
||||
*/
|
||||
public static function INDIRECT($cellAddress = null, ?Cell $pCell = null)
|
||||
{
|
||||
$cellAddress = Functions::flattenSingleValue($cellAddress);
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
$cellAddress1 = $cellAddress;
|
||||
$cellAddress2 = null;
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
|
||||
}
|
||||
|
||||
if (
|
||||
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
|
||||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))
|
||||
) {
|
||||
if (!preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $cellAddress1, $matches)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
if (strpos($cellAddress, '!') !== false) {
|
||||
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
$sheetName = trim($sheetName, "'");
|
||||
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||
} else {
|
||||
$pSheet = $pCell->getWorksheet();
|
||||
}
|
||||
|
||||
return Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false);
|
||||
}
|
||||
|
||||
if (strpos($cellAddress, '!') !== false) {
|
||||
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
$sheetName = trim($sheetName, "'");
|
||||
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||
} else {
|
||||
$pSheet = $pCell->getWorksheet();
|
||||
}
|
||||
|
||||
return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
|
||||
return Indirect::INDIRECT($cellAddress, $pCell);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -243,87 +210,33 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =OFFSET(cellAddress, rows, cols, [height], [width])
|
||||
*
|
||||
* @param null|string $cellAddress The reference from which you want to base the offset. Reference must refer to a cell or
|
||||
* range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
|
||||
* @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
|
||||
* Using 5 as the rows argument specifies that the upper-left cell in the reference is
|
||||
* five rows below reference. Rows can be positive (which means below the starting reference)
|
||||
* or negative (which means above the starting reference).
|
||||
* @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell of the result
|
||||
* to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
|
||||
* reference is five columns to the right of reference. Cols can be positive (which means
|
||||
* to the right of the starting reference) or negative (which means to the left of the
|
||||
* starting reference).
|
||||
* @param mixed $height The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
|
||||
* @param mixed $width The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @return string A reference to a cell or range of cells
|
||||
* @see Use the OFFSET() method in the LookupRef\Offset class instead
|
||||
*
|
||||
* @param null|string $cellAddress The reference from which you want to base the offset.
|
||||
* Reference must refer to a cell or range of adjacent cells;
|
||||
* otherwise, OFFSET returns the #VALUE! error value.
|
||||
* @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
|
||||
* Using 5 as the rows argument specifies that the upper-left cell in the
|
||||
* reference is five rows below reference. Rows can be positive (which means
|
||||
* below the starting reference) or negative (which means above the starting
|
||||
* reference).
|
||||
* @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell
|
||||
* of the result to refer to. Using 5 as the cols argument specifies that the
|
||||
* upper-left cell in the reference is five columns to the right of reference.
|
||||
* Cols can be positive (which means to the right of the starting reference)
|
||||
* or negative (which means to the left of the starting reference).
|
||||
* @param mixed $height The height, in number of rows, that you want the returned reference to be.
|
||||
* Height must be a positive number.
|
||||
* @param mixed $width The width, in number of columns, that you want the returned reference to be.
|
||||
* Width must be a positive number.
|
||||
*
|
||||
* @return array|string An array containing a cell or range of cells, or a string on error
|
||||
*/
|
||||
public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $pCell = null)
|
||||
{
|
||||
$rows = Functions::flattenSingleValue($rows);
|
||||
$columns = Functions::flattenSingleValue($columns);
|
||||
$height = Functions::flattenSingleValue($height);
|
||||
$width = Functions::flattenSingleValue($width);
|
||||
if ($cellAddress === null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!is_object($pCell)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
$sheetName = null;
|
||||
if (strpos($cellAddress, '!')) {
|
||||
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
$sheetName = trim($sheetName, "'");
|
||||
}
|
||||
if (strpos($cellAddress, ':')) {
|
||||
[$startCell, $endCell] = explode(':', $cellAddress);
|
||||
} else {
|
||||
$startCell = $endCell = $cellAddress;
|
||||
}
|
||||
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
|
||||
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
|
||||
|
||||
$startCellRow += $rows;
|
||||
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
|
||||
$startCellColumn += $columns;
|
||||
|
||||
if (($startCellRow <= 0) || ($startCellColumn < 0)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
|
||||
if (($width != null) && (!is_object($width))) {
|
||||
$endCellColumn = $startCellColumn + $width - 1;
|
||||
} else {
|
||||
$endCellColumn += $columns;
|
||||
}
|
||||
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
|
||||
|
||||
if (($height != null) && (!is_object($height))) {
|
||||
$endCellRow = $startCellRow + $height - 1;
|
||||
} else {
|
||||
$endCellRow += $rows;
|
||||
}
|
||||
|
||||
if (($endCellRow <= 0) || ($endCellColumn < 0)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
|
||||
|
||||
$cellAddress = $startCellColumn . $startCellRow;
|
||||
if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
|
||||
$cellAddress .= ':' . $endCellColumn . $endCellRow;
|
||||
}
|
||||
|
||||
if ($sheetName !== null) {
|
||||
$pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
|
||||
} else {
|
||||
$pSheet = $pCell->getWorksheet();
|
||||
}
|
||||
|
||||
return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
|
||||
return Offset::OFFSET($cellAddress, $rows, $columns, $height, $width, $pCell);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -370,6 +283,10 @@ class LookupRef
|
|||
* Excel Function:
|
||||
* =MATCH(lookup_value, lookup_array, [match_type])
|
||||
*
|
||||
* @Deprecated 1.18.0
|
||||
*
|
||||
* @see Use the MATCH() method in the LookupRef\ExcelMatch class instead
|
||||
*
|
||||
* @param mixed $lookupValue The value that you want to match in lookup_array
|
||||
* @param mixed $lookupArray The range of cells being searched
|
||||
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ class Address
|
|||
* Excel Function:
|
||||
* =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
|
||||
*
|
||||
* @param mixed $row Row number to use in the cell reference
|
||||
* @param mixed $column Column number to use in the cell reference
|
||||
* @param int $relativity Flag indicating the type of reference to return
|
||||
* 1 or omitted Absolute
|
||||
* 2 Absolute row; relative column
|
||||
* 3 Relative row; absolute column
|
||||
* 4 Relative
|
||||
* @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style.
|
||||
* TRUE or omitted ADDRESS returns an A1-style reference
|
||||
* FALSE ADDRESS returns an R1C1-style reference
|
||||
* @param string $sheetName Optional Name of worksheet to use
|
||||
* @param mixed $row Row number (integer) to use in the cell reference
|
||||
* @param mixed $column Column number (integer) to use in the cell reference
|
||||
* @param mixed $relativity Integer flag indicating the type of reference to return
|
||||
* 1 or omitted Absolute
|
||||
* 2 Absolute row; relative column
|
||||
* 3 Relative row; absolute column
|
||||
* 4 Relative
|
||||
* @param mixed $referenceStyle A logical (boolean) value that specifies the A1 or R1C1 reference style.
|
||||
* TRUE or omitted ADDRESS returns an A1-style reference
|
||||
* FALSE ADDRESS returns an R1C1-style reference
|
||||
* @param mixed $sheetName Optional Name of worksheet to use
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class Indirect
|
||||
{
|
||||
/**
|
||||
* INDIRECT.
|
||||
*
|
||||
* Returns the reference specified by a text string.
|
||||
* References are immediately evaluated to display their contents.
|
||||
*
|
||||
* Excel Function:
|
||||
* =INDIRECT(cellAddress)
|
||||
*
|
||||
* NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
|
||||
*
|
||||
* @param null|array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
|
||||
* @param null|Cell $pCell The current cell (containing this formula)
|
||||
*
|
||||
* @return array|string An array containing a cell or range of cells, or a string on error
|
||||
*
|
||||
* @TODO Support for the optional a1 parameter introduced in Excel 2010
|
||||
*/
|
||||
public static function INDIRECT($cellAddress = null, ?Cell $pCell = null)
|
||||
{
|
||||
$cellAddress = Functions::flattenSingleValue($cellAddress);
|
||||
if ($cellAddress === null || $cellAddress === '' || !is_object($pCell)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
[$cellAddress, $pSheet] = self::extractWorksheet($cellAddress, $pCell);
|
||||
|
||||
$cellAddress1 = $cellAddress;
|
||||
$cellAddress2 = null;
|
||||
if (strpos($cellAddress, ':') !== false) {
|
||||
[$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
|
||||
}
|
||||
|
||||
if (
|
||||
(!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
|
||||
(($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))
|
||||
) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
return self::extractRequiredCells($pSheet, $cellAddress);
|
||||
}
|
||||
|
||||
private static function extractRequiredCells(?Worksheet $pSheet, string $cellAddress)
|
||||
{
|
||||
return Calculation::getInstance($pSheet !== null ? $pSheet->getParent() : null)
|
||||
->extractCellRange($cellAddress, $pSheet, false);
|
||||
}
|
||||
|
||||
private static function extractWorksheet($cellAddress, Cell $pCell): array
|
||||
{
|
||||
$sheetName = '';
|
||||
if (strpos($cellAddress, '!') !== false) {
|
||||
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
$sheetName = trim($sheetName, "'");
|
||||
}
|
||||
|
||||
$pSheet = ($sheetName !== '')
|
||||
? $pCell->getWorksheet()->getParent()->getSheetByName($sheetName)
|
||||
: $pCell->getWorksheet();
|
||||
|
||||
return [$cellAddress, $pSheet];
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ class Lookup
|
|||
$lookupColumns = self::columnCount($lookupVector);
|
||||
// we correctly orient our results
|
||||
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
|
||||
$lookupVector = LookupRef::TRANSPOSE($lookupVector);
|
||||
$lookupVector = LookupRef\Matrix::transpose($lookupVector);
|
||||
$lookupRows = self::rowCount($lookupVector);
|
||||
$lookupColumns = self::columnCount($lookupVector);
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ class Lookup
|
|||
|
||||
// we correctly orient our results
|
||||
if ($resultRows === 1 && $resultColumns > 1) {
|
||||
$resultVector = LookupRef::TRANSPOSE($resultVector);
|
||||
$resultVector = LookupRef\Matrix::transpose($resultVector);
|
||||
}
|
||||
|
||||
return $resultVector;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class Matrix
|
|||
/**
|
||||
* TRANSPOSE.
|
||||
*
|
||||
* @param array $matrixData A matrix of values
|
||||
* @param array|mixed $matrixData A matrix of values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class Offset
|
||||
{
|
||||
/**
|
||||
* OFFSET.
|
||||
*
|
||||
* Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
|
||||
* The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
|
||||
* the number of columns to be returned.
|
||||
*
|
||||
* Excel Function:
|
||||
* =OFFSET(cellAddress, rows, cols, [height], [width])
|
||||
*
|
||||
* @param null|string $cellAddress The reference from which you want to base the offset.
|
||||
* Reference must refer to a cell or range of adjacent cells;
|
||||
* otherwise, OFFSET returns the #VALUE! error value.
|
||||
* @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
|
||||
* Using 5 as the rows argument specifies that the upper-left cell in the
|
||||
* reference is five rows below reference. Rows can be positive (which means
|
||||
* below the starting reference) or negative (which means above the starting
|
||||
* reference).
|
||||
* @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell
|
||||
* of the result to refer to. Using 5 as the cols argument specifies that the
|
||||
* upper-left cell in the reference is five columns to the right of reference.
|
||||
* Cols can be positive (which means to the right of the starting reference)
|
||||
* or negative (which means to the left of the starting reference).
|
||||
* @param mixed $height The height, in number of rows, that you want the returned reference to be.
|
||||
* Height must be a positive number.
|
||||
* @param mixed $width The width, in number of columns, that you want the returned reference to be.
|
||||
* Width must be a positive number.
|
||||
*
|
||||
* @return array|string An array containing a cell or range of cells, or a string on error
|
||||
*/
|
||||
public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $pCell = null)
|
||||
{
|
||||
$rows = Functions::flattenSingleValue($rows);
|
||||
$columns = Functions::flattenSingleValue($columns);
|
||||
$height = Functions::flattenSingleValue($height);
|
||||
$width = Functions::flattenSingleValue($width);
|
||||
|
||||
if ($cellAddress === null || $cellAddress === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!is_object($pCell)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
[$cellAddress, $pSheet] = self::extractWorksheet($cellAddress, $pCell);
|
||||
|
||||
$startCell = $endCell = $cellAddress;
|
||||
if (strpos($cellAddress, ':')) {
|
||||
[$startCell, $endCell] = explode(':', $cellAddress);
|
||||
}
|
||||
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
|
||||
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
|
||||
|
||||
$startCellRow += $rows;
|
||||
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
|
||||
$startCellColumn += $columns;
|
||||
|
||||
if (($startCellRow <= 0) || ($startCellColumn < 0)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
|
||||
$endCellColumn = self::adjustEndCellColumnForWidth($endCellColumn, $width, $startCellColumn, $columns);
|
||||
$startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
|
||||
|
||||
$endCellRow = self::adustEndCellRowForHeight($height, $startCellRow, $rows, $endCellRow);
|
||||
|
||||
if (($endCellRow <= 0) || ($endCellColumn < 0)) {
|
||||
return Functions::REF();
|
||||
}
|
||||
$endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
|
||||
|
||||
$cellAddress = "{$startCellColumn}{$startCellRow}";
|
||||
if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
|
||||
$cellAddress .= ":{$endCellColumn}{$endCellRow}";
|
||||
}
|
||||
|
||||
return self::extractRequiredCells($pSheet, $cellAddress);
|
||||
}
|
||||
|
||||
private static function extractRequiredCells(?Worksheet $pSheet, string $cellAddress)
|
||||
{
|
||||
return Calculation::getInstance($pSheet !== null ? $pSheet->getParent() : null)
|
||||
->extractCellRange($cellAddress, $pSheet, false);
|
||||
}
|
||||
|
||||
private static function extractWorksheet($cellAddress, Cell $pCell): array
|
||||
{
|
||||
$sheetName = '';
|
||||
if (strpos($cellAddress, '!') !== false) {
|
||||
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||
$sheetName = trim($sheetName, "'");
|
||||
}
|
||||
|
||||
$pSheet = ($sheetName !== '')
|
||||
? $pCell->getWorksheet()->getParent()->getSheetByName($sheetName)
|
||||
: $pCell->getWorksheet();
|
||||
|
||||
return [$cellAddress, $pSheet];
|
||||
}
|
||||
|
||||
private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns)
|
||||
{
|
||||
$endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
|
||||
if (($width !== null) && (!is_object($width))) {
|
||||
$endCellColumn = $startCellColumn + (int) $width - 1;
|
||||
} else {
|
||||
$endCellColumn += (int) $columns;
|
||||
}
|
||||
|
||||
return $endCellColumn;
|
||||
}
|
||||
|
||||
private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int
|
||||
{
|
||||
if (($height !== null) && (!is_object($height))) {
|
||||
$endCellRow = $startCellRow + (int) $height - 1;
|
||||
} else {
|
||||
$endCellRow += (int) $rows;
|
||||
}
|
||||
|
||||
return $endCellRow;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Arabic
|
||||
{
|
||||
private const ROMAN_LOOKUP = [
|
||||
'M' => 1000,
|
||||
'D' => 500,
|
||||
'C' => 100,
|
||||
'L' => 50,
|
||||
'X' => 10,
|
||||
'V' => 5,
|
||||
'I' => 1,
|
||||
];
|
||||
|
||||
/**
|
||||
* Recursively calculate the arabic value of a roman numeral.
|
||||
*
|
||||
* @param int $sum
|
||||
* @param int $subtract
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0)
|
||||
{
|
||||
$numeral = array_shift($roman);
|
||||
if (!isset(self::ROMAN_LOOKUP[$numeral])) {
|
||||
throw new Exception('Invalid character detected');
|
||||
}
|
||||
|
||||
$arabic = self::ROMAN_LOOKUP[$numeral];
|
||||
if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) {
|
||||
$subtract += $arabic;
|
||||
} else {
|
||||
$sum += ($arabic - $subtract);
|
||||
$subtract = 0;
|
||||
}
|
||||
|
||||
if (count($roman) > 0) {
|
||||
self::calculateArabic($roman, $sum, $subtract);
|
||||
}
|
||||
|
||||
return $sum;
|
||||
}
|
||||
|
||||
private static function strSplit(string $roman): array
|
||||
{
|
||||
$rslt = str_split($roman);
|
||||
|
||||
return is_array($rslt) ? $rslt : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* ARABIC.
|
||||
*
|
||||
* Converts a Roman numeral to an Arabic numeral.
|
||||
*
|
||||
* Excel Function:
|
||||
* ARABIC(text)
|
||||
*
|
||||
* @param string $roman
|
||||
*
|
||||
* @return int|string the arabic numberal contrived from the roman numeral
|
||||
*/
|
||||
public static function evaluate($roman)
|
||||
{
|
||||
// An empty string should return 0
|
||||
$roman = substr(trim(strtoupper((string) Functions::flattenSingleValue($roman))), 0, 255);
|
||||
if ($roman === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert the roman numeral to an arabic number
|
||||
$negativeNumber = $roman[0] === '-';
|
||||
if ($negativeNumber) {
|
||||
$roman = substr($roman, 1);
|
||||
}
|
||||
|
||||
try {
|
||||
$arabic = self::calculateArabic(self::strSplit($roman));
|
||||
} catch (Exception $e) {
|
||||
return Functions::VALUE(); // Invalid character detected
|
||||
}
|
||||
|
||||
if ($negativeNumber) {
|
||||
$arabic *= -1; // The number should be negative
|
||||
}
|
||||
|
||||
return $arabic;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Base
|
||||
{
|
||||
/**
|
||||
* BASE.
|
||||
*
|
||||
* Converts a number into a text representation with the given radix (base).
|
||||
*
|
||||
* Excel Function:
|
||||
* BASE(Number, Radix [Min_length])
|
||||
*
|
||||
* @param mixed $number expect float
|
||||
* @param mixed $radix expect float
|
||||
* @param mixed $minLength expect int or null
|
||||
*
|
||||
* @return string the text representation with the given radix (base)
|
||||
*/
|
||||
public static function funcBase($number, $radix, $minLength = null)
|
||||
{
|
||||
try {
|
||||
$number = (int) Helpers::validateNumericNullBool($number);
|
||||
$radix = (int) Helpers::validateNumericNullBool($radix);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$minLength = Functions::flattenSingleValue($minLength);
|
||||
|
||||
if ($minLength === null || is_numeric($minLength)) {
|
||||
if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) {
|
||||
return Functions::NAN(); // Numeric range constraints
|
||||
}
|
||||
|
||||
$outcome = strtoupper((string) base_convert($number, 10, $radix));
|
||||
if ($minLength !== null) {
|
||||
$outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding
|
||||
}
|
||||
|
||||
return $outcome;
|
||||
}
|
||||
|
||||
return Functions::VALUE();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Combinations
|
||||
{
|
||||
/**
|
||||
* COMBIN.
|
||||
*
|
||||
* Returns the number of combinations for a given number of items. Use COMBIN to
|
||||
* determine the total possible number of groups for a given number of items.
|
||||
*
|
||||
* Excel Function:
|
||||
* COMBIN(numObjs,numInSet)
|
||||
*
|
||||
* @param mixed $numObjs Number of different objects
|
||||
* @param mixed $numInSet Number of objects in each combination
|
||||
*
|
||||
* @return float|int|string Number of combinations, or a string containing an error
|
||||
*/
|
||||
public static function withoutRepetition($numObjs, $numInSet)
|
||||
{
|
||||
try {
|
||||
$numObjs = Helpers::validateNumericNullSubstitution($numObjs, null);
|
||||
$numInSet = Helpers::validateNumericNullSubstitution($numInSet, null);
|
||||
Helpers::validateNotNegative($numInSet);
|
||||
Helpers::validateNotNegative($numObjs - $numInSet);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return round(Fact::funcFact($numObjs) / Fact::funcFact($numObjs - $numInSet)) / Fact::funcFact($numInSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* COMBIN.
|
||||
*
|
||||
* Returns the number of combinations for a given number of items. Use COMBIN to
|
||||
* determine the total possible number of groups for a given number of items.
|
||||
*
|
||||
* Excel Function:
|
||||
* COMBIN(numObjs,numInSet)
|
||||
*
|
||||
* @param mixed $numObjs Number of different objects
|
||||
* @param mixed $numInSet Number of objects in each combination
|
||||
*
|
||||
* @return float|int|string Number of combinations, or a string containing an error
|
||||
*/
|
||||
public static function withRepetition($numObjs, $numInSet)
|
||||
{
|
||||
try {
|
||||
$numObjs = Helpers::validateNumericNullSubstitution($numObjs, null);
|
||||
$numInSet = Helpers::validateNumericNullSubstitution($numInSet, null);
|
||||
Helpers::validateNotNegative($numInSet);
|
||||
Helpers::validateNotNegative($numObjs);
|
||||
$numObjs = (int) $numObjs;
|
||||
$numInSet = (int) $numInSet;
|
||||
// Microsoft documentation says following is true, but Excel
|
||||
// does not enforce this restriction.
|
||||
//Helpers::validateNotNegative($numObjs - $numInSet);
|
||||
if ($numObjs === 0) {
|
||||
Helpers::validateNotNegative(-$numInSet);
|
||||
|
||||
return 1;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return round(Fact::funcFact($numObjs + $numInSet - 1) / Fact::funcFact($numObjs - 1)) / Fact::funcFact($numInSet);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical;
|
||||
|
||||
class Fact
|
||||
{
|
||||
/**
|
||||
* FACT.
|
||||
*
|
||||
* Returns the factorial of a number.
|
||||
* The factorial of a number is equal to 1*2*3*...* number.
|
||||
*
|
||||
* Excel Function:
|
||||
* FACT(factVal)
|
||||
*
|
||||
* @param float $factVal Factorial Value
|
||||
*
|
||||
* @return int|string Factorial, or a string containing an error
|
||||
*/
|
||||
public static function funcFact($factVal)
|
||||
{
|
||||
try {
|
||||
$factVal = Helpers::validateNumericNullBool($factVal);
|
||||
Helpers::validateNotNegative($factVal);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$factLoop = floor($factVal);
|
||||
if ($factVal > $factLoop) {
|
||||
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
|
||||
return Statistical\Distributions\Gamma::gammaValue($factVal + 1);
|
||||
}
|
||||
}
|
||||
|
||||
$factorial = 1;
|
||||
while ($factLoop > 1) {
|
||||
$factorial *= $factLoop--;
|
||||
}
|
||||
|
||||
return $factorial;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
|
||||
class FactDouble
|
||||
{
|
||||
/**
|
||||
* FACTDOUBLE.
|
||||
*
|
||||
* Returns the double factorial of a number.
|
||||
*
|
||||
* Excel Function:
|
||||
* FACTDOUBLE(factVal)
|
||||
*
|
||||
* @param float $factVal Factorial Value
|
||||
*
|
||||
* @return float|int|string Double Factorial, or a string containing an error
|
||||
*/
|
||||
public static function evaluate($factVal)
|
||||
{
|
||||
try {
|
||||
$factVal = Helpers::validateNumericNullSubstitution($factVal, 0);
|
||||
Helpers::validateNotNegative($factVal);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$factLoop = floor($factVal);
|
||||
$factorial = 1;
|
||||
while ($factLoop > 1) {
|
||||
$factorial *= $factLoop;
|
||||
$factLoop -= 2;
|
||||
}
|
||||
|
||||
return $factorial;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Gcd
|
||||
{
|
||||
/**
|
||||
* Recursively determine GCD.
|
||||
*
|
||||
* Returns the greatest common divisor of a series of numbers.
|
||||
* The greatest common divisor is the largest integer that divides both
|
||||
* number1 and number2 without a remainder.
|
||||
*
|
||||
* Excel Function:
|
||||
* GCD(number1[,number2[, ...]])
|
||||
*
|
||||
* @param float|int $a
|
||||
* @param float|int $b
|
||||
*
|
||||
* @return float|int
|
||||
*/
|
||||
private static function evaluateGCD($a, $b)
|
||||
{
|
||||
return $b ? self::evaluateGCD($b, $a % $b) : $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* GCD.
|
||||
*
|
||||
* Returns the greatest common divisor of a series of numbers.
|
||||
* The greatest common divisor is the largest integer that divides both
|
||||
* number1 and number2 without a remainder.
|
||||
*
|
||||
* Excel Function:
|
||||
* GCD(number1[,number2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return float|int|string Greatest Common Divisor, or a string containing an error
|
||||
*/
|
||||
public static function evaluate(...$args)
|
||||
{
|
||||
try {
|
||||
$arrayArgs = [];
|
||||
foreach (Functions::flattenArray($args) as $value1) {
|
||||
if ($value1 !== null) {
|
||||
$value = Helpers::validateNumericNullSubstitution($value1, 1);
|
||||
Helpers::validateNotNegative($value);
|
||||
$arrayArgs[] = (int) $value;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (count($arrayArgs) <= 0) {
|
||||
return Functions::VALUE();
|
||||
}
|
||||
$gcd = (int) array_pop($arrayArgs);
|
||||
do {
|
||||
$gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs));
|
||||
} while (!empty($arrayArgs));
|
||||
|
||||
return $gcd;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,48 @@ class Helpers
|
|||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm number >= 0.
|
||||
*
|
||||
* @param float|int $number
|
||||
*/
|
||||
public static function validateNotNegative($number, ?string $except = null): void
|
||||
{
|
||||
if ($number >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception($except ?? Functions::NAN());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm number > 0.
|
||||
*
|
||||
* @param float|int $number
|
||||
*/
|
||||
public static function validatePositive($number, ?string $except = null): void
|
||||
{
|
||||
if ($number > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception($except ?? Functions::NAN());
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm number != 0.
|
||||
*
|
||||
* @param float|int $number
|
||||
*/
|
||||
public static function validateNotZero($number): void
|
||||
{
|
||||
if ($number) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Functions::DIV0());
|
||||
}
|
||||
|
||||
public static function returnSign(float $number): int
|
||||
{
|
||||
return $number ? (($number > 0) ? 1 : -1) : 0;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
use Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class Lcm
|
||||
{
|
||||
//
|
||||
// Private method to return an array of the factors of the input value
|
||||
//
|
||||
private static function factors($value)
|
||||
{
|
||||
$startVal = floor(sqrt($value));
|
||||
|
||||
$factorArray = [];
|
||||
for ($i = $startVal; $i > 1; --$i) {
|
||||
if (($value % $i) == 0) {
|
||||
$factorArray = array_merge($factorArray, self::factors($value / $i));
|
||||
$factorArray = array_merge($factorArray, self::factors($i));
|
||||
if ($i <= sqrt($value)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($factorArray)) {
|
||||
rsort($factorArray);
|
||||
|
||||
return $factorArray;
|
||||
}
|
||||
|
||||
return [(int) $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* LCM.
|
||||
*
|
||||
* Returns the lowest common multiplier of a series of numbers
|
||||
* The least common multiple is the smallest positive integer that is a multiple
|
||||
* of all integer arguments number1, number2, and so on. Use LCM to add fractions
|
||||
* with different denominators.
|
||||
*
|
||||
* Excel Function:
|
||||
* LCM(number1[,number2[, ...]])
|
||||
*
|
||||
* @param mixed ...$args Data values
|
||||
*
|
||||
* @return int|string Lowest Common Multiplier, or a string containing an error
|
||||
*/
|
||||
public static function funcLcm(...$args)
|
||||
{
|
||||
try {
|
||||
$arrayArgs = [];
|
||||
$anyZeros = 0;
|
||||
$anyNonNulls = 0;
|
||||
foreach (Functions::flattenArray($args) as $value1) {
|
||||
$anyNonNulls += (int) ($value1 !== null);
|
||||
$value = Helpers::validateNumericNullSubstitution($value1, 1);
|
||||
Helpers::validateNotNegative($value);
|
||||
$arrayArgs[] = (int) $value;
|
||||
$anyZeros += (int) !((bool) $value);
|
||||
}
|
||||
self::testNonNulls($anyNonNulls);
|
||||
if ($anyZeros) {
|
||||
return 0;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$returnValue = 1;
|
||||
$allPoweredFactors = [];
|
||||
// Loop through arguments
|
||||
foreach ($arrayArgs as $value) {
|
||||
$myFactors = self::factors(floor($value));
|
||||
$myCountedFactors = array_count_values($myFactors);
|
||||
$myPoweredFactors = [];
|
||||
foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
|
||||
$myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower;
|
||||
}
|
||||
self::processPoweredFactors($allPoweredFactors, $myPoweredFactors);
|
||||
}
|
||||
foreach ($allPoweredFactors as $allPoweredFactor) {
|
||||
$returnValue *= (int) $allPoweredFactor;
|
||||
}
|
||||
|
||||
return $returnValue;
|
||||
}
|
||||
|
||||
private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void
|
||||
{
|
||||
foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
|
||||
if (isset($allPoweredFactors[$myPoweredValue])) {
|
||||
if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
|
||||
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
|
||||
}
|
||||
} else {
|
||||
$allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function testNonNulls(int $anyNonNulls): void
|
||||
{
|
||||
if (!$anyNonNulls) {
|
||||
throw new Exception(Functions::VALUE());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue