feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles

- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD)
  * DEV: Clés TEST Pierre (mode test)
  * REC: Clés TEST Client (mode test)
  * PROD: Clés LIVE Client (mode live)
- Ajout de la gestion des bases de données immeubles/bâtiments
  * Configuration buildings_database pour DEV/REC/PROD
  * Service BuildingService pour enrichissement des adresses
- Optimisations pages et améliorations ergonomie
- Mises à jour des dépendances Composer
- Nettoyage des fichiers obsolètes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -12,21 +12,25 @@ trait ArrayEnabled
private static ArrayArgumentHelper $arrayArgumentHelper;
/**
* @param array|false $arguments Can be changed to array for Php8.1+
* @param mixed[] $arguments
*/
private static function initialiseHelper($arguments): void
private static function initialiseHelper(array $arguments): void
{
if (self::$initializationNeeded === true) {
self::$arrayArgumentHelper = new ArrayArgumentHelper();
self::$initializationNeeded = false;
}
self::$arrayArgumentHelper->initialise(($arguments === false) ? [] : $arguments);
self::$arrayArgumentHelper->initialise($arguments);
}
/**
* Handles array argument processing when the function accepts a single argument that can be an array argument.
* Example use for:
* DAYOFMONTH() or FACT().
*
* @param mixed[] $values
*
* @return mixed[]
*/
protected static function evaluateSingleArgumentArray(callable $method, array $values): array
{
@@ -43,6 +47,8 @@ trait ArrayEnabled
* and any of them can be an array argument.
* Example use for:
* ROUND() or DATE().
*
* @return mixed[]
*/
protected static function evaluateArrayArguments(callable $method, mixed ...$arguments): array
{
@@ -58,6 +64,8 @@ trait ArrayEnabled
* Example use for:
* NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
* to be treated as a such rather than as an array arguments.
*
* @return mixed[]
*/
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, mixed ...$arguments): array
{
@@ -80,6 +88,8 @@ trait ArrayEnabled
* Example use for:
* Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
* rather than as an array argument.
*
* @return mixed[]
*/
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, mixed ...$arguments): array
{
@@ -105,6 +115,8 @@ trait ArrayEnabled
* Example use for:
* HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
* rather than as an array argument.
*
* @return mixed[]
*/
protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, mixed ...$arguments): array
{

View File

@@ -14,13 +14,15 @@ class BinaryComparison
/**
* Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
*
* @param null|string $str1 First string value for the comparison
* @param null|string $str2 Second string value for the comparison
* @param mixed $str1 First string value for the comparison, expect ?string
* @param mixed $str2 Second string value for the comparison, expect ?string
*/
private static function strcmpLowercaseFirst(?string $str1, ?string $str2): int
private static function strcmpLowercaseFirst(mixed $str1, mixed $str2): int
{
$inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
$inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
$str1 = StringHelper::convertToString($str1);
$str2 = StringHelper::convertToString($str2);
$inversedStr1 = StringHelper::strCaseReverse($str1);
$inversedStr2 = StringHelper::strCaseReverse($str2);
return strcmp($inversedStr1, $inversedStr2);
}
@@ -28,12 +30,15 @@ class BinaryComparison
/**
* PHP8.1 deprecates passing null to strcmp.
*
* @param null|string $str1 First string value for the comparison
* @param null|string $str2 Second string value for the comparison
* @param mixed $str1 First string value for the comparison, expect ?string
* @param mixed $str2 Second string value for the comparison, expect ?string
*/
private static function strcmpAllowNull(?string $str1, ?string $str2): int
private static function strcmpAllowNull(mixed $str1, mixed $str2): int
{
return strcmp($str1 ?? '', $str2 ?? '');
$str1 = StringHelper::convertToString($str1);
$str2 = StringHelper::convertToString($str2);
return strcmp($str1, $str2);
}
public static function compare(mixed $operand1, mixed $operand2, string $operator): bool

View File

@@ -18,4 +18,5 @@ abstract class Category
const CATEGORY_TEXT_AND_DATA = 'Text and Data';
const CATEGORY_WEB = 'Web';
const CATEGORY_UNCATEGORISED = 'Uncategorised';
const CATEGORY_MICROSOFT_INTERNAL = 'MS Internal';
}

View File

@@ -19,12 +19,12 @@ class DAverage extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DCount extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -19,12 +19,12 @@ class DCountA extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -19,12 +19,12 @@ class DGet extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
@@ -42,6 +42,7 @@ class DGet extends DatabaseAbstract
return ExcelError::NAN();
}
/** @var array<null|float|int|string> */
$row = array_pop($columnData);
return array_pop($row);

View File

@@ -20,12 +20,12 @@ class DMax extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DMin extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -19,12 +19,12 @@ class DProduct extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DStDev extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DStDevP extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -19,12 +19,12 @@ class DSum extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DVar extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -20,12 +20,12 @@ class DVarP extends DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array|int|string $field Indicates which column is used in the function. Enter the
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the

View File

@@ -5,9 +5,26 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
abstract class DatabaseAbstract
{
/**
* @param mixed[] $database The range of cells that makes up the list or database.
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param null|array<mixed>|int|string $field Indicates which column is used in the function. Enter the
* column label enclosed between double quotation marks, such as
* "Age" or "Yield," or a number (without quotation marks) that
* represents the position of the column within the list: 1 for
* the first column, 2 for the second column, and so on.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
* column.
*/
abstract public static function evaluate(array $database, array|null|int|string $field, array $criteria): null|float|int|string;
/**
@@ -27,12 +44,16 @@ abstract class DatabaseAbstract
*/
protected static function fieldExtract(array $database, mixed $field): ?int
{
$field = strtoupper(Functions::flattenSingleValue($field) ?? '');
/** @var ?string */
$single = Functions::flattenSingleValue($field);
$field = strtoupper($single ?? '');
if ($field === '') {
return null;
}
$fieldNames = array_map('strtoupper', array_shift($database));
/** @var callable */
$callable = 'strtoupper';
$fieldNames = array_map($callable, array_shift($database)); //* @phpstan-ignore-line
if (is_numeric($field)) {
$field = (int) $field - 1;
if ($field < 0 || $field >= count($fieldNames)) {
@@ -56,7 +77,7 @@ abstract class DatabaseAbstract
* A database is a list of related data in which rows of related
* information are records, and columns of data are fields. The
* first row of the list contains labels for each column.
* @param mixed[] $criteria The range of cells that contains the conditions you specify.
* @param mixed[][] $criteria The range of cells that contains the conditions you specify.
* You can use any range for the criteria argument, as long as it
* includes at least one column label and at least one cell below
* the column label in which you specify a condition for the
@@ -66,16 +87,25 @@ abstract class DatabaseAbstract
*/
protected static function filter(array $database, array $criteria): array
{
/** @var mixed[] */
$fieldNames = array_shift($database);
$criteriaNames = array_shift($criteria);
// Convert the criteria into a set of AND/OR conditions with [:placeholders]
/** @var string[] $criteriaNames */
$query = self::buildQuery($criteriaNames, $criteria);
// Loop through each row of the database
/** @var mixed[][] $criteriaNames */
return self::executeQuery($database, $query, $criteriaNames, $fieldNames);
}
/**
* @param mixed[] $database The range of cells that makes up the list or database
* @param mixed[][] $criteria
*
* @return mixed[]
*/
protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array
{
// reduce the database to a set of rows that match all the criteria
@@ -84,6 +114,7 @@ abstract class DatabaseAbstract
// extract an array of values for the requested column
$columnData = [];
/** @var mixed[] $row */
foreach ($database as $rowKey => $row) {
$keys = array_keys($row);
$key = $keys[$field] ?? null;
@@ -94,6 +125,10 @@ abstract class DatabaseAbstract
return $columnData;
}
/**
* @param string[] $criteriaNames
* @param mixed[][] $criteria
*/
private static function buildQuery(array $criteriaNames, array $criteria): string
{
$baseQuery = [];
@@ -108,7 +143,7 @@ abstract class DatabaseAbstract
}
$rowQuery = array_map(
fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''),
fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''), // @phpstan-ignore-line
$baseQuery
);
@@ -135,12 +170,21 @@ abstract class DatabaseAbstract
return $condition;
}
/**
* @param mixed[] $database
* @param mixed[][] $criteria
* @param array<mixed> $fields
*
* @return mixed[]
*/
private static function executeQuery(array $database, string $query, array $criteria, array $fields): array
{
foreach ($database as $dataRow => $dataValues) {
// Substitute actual values from the database row for our [:placeholders]
$conditions = $query;
foreach ($criteria as $criterion) {
/** @var string $criterion */
/** @var mixed[] $dataValues */
$conditions = self::processCondition($criterion, $fields, $dataValues, $conditions);
}
@@ -156,6 +200,10 @@ abstract class DatabaseAbstract
return $database;
}
/**
* @param array<mixed> $fields
* @param array<mixed> $dataValues
*/
private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions): string
{
$key = array_search($criterion, $fields, true);
@@ -169,7 +217,10 @@ abstract class DatabaseAbstract
if (is_string($dataValue) && str_contains($dataValue, '"')) {
$dataValue = str_replace('"', '""', $dataValue);
}
$dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
if (is_string($dataValue)) {
$dataValue = Calculation::wrapResult(strtoupper($dataValue));
}
$dataValue = StringHelper::convertToString($dataValue);
}
return str_replace('[:' . $criterion . ']', $dataValue, $conditions);

View File

@@ -28,7 +28,7 @@ class Date
* 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 array|float|int|string $year The value of the year argument can include one to four digits.
* @param array<mixed>|float|int|string $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
@@ -39,7 +39,7 @@ class Date
* 2008.
* If year is less than 0 or is 10000 or greater, Excel returns the
* #NUM! error value.
* @param array|float|int|string $month A positive or negative integer representing the month of the year
* @param array<mixed>|float|int|string $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)
@@ -48,7 +48,7 @@ class Date
* 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 array|float|int|string $day A positive or negative integer representing the day of the month
* @param array<mixed>|float|int|string $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
@@ -59,12 +59,12 @@ class Date
* example, DATE(2008,1,-15) returns the serial number representing
* December 16, 2007.
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
public static function fromYMD(array|float|int|string $year, array|float|int|string $month, array|float|int|string $day): float|int|DateTime|string|array
public static function fromYMD(array|float|int|string $year, null|array|bool|float|int|string $month, array|float|int|string $day): float|int|DateTime|string|array
{
if (is_array($year) || is_array($month) || is_array($day)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day);
@@ -92,7 +92,11 @@ class Date
*/
private static function getYear(mixed $year, int $baseYear): int
{
$year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0;
if ($year === null) {
$year = 0;
} elseif (is_scalar($year)) {
$year = StringHelper::testStringAsNumeric((string) $year);
}
if (!is_numeric($year)) {
throw new Exception(ExcelError::VALUE());
}
@@ -117,11 +121,15 @@ class Date
*/
private static function getMonth(mixed $month): int
{
if (($month !== null) && (!is_numeric($month))) {
$month = SharedDateHelper::monthStringToNumber($month);
if (is_string($month)) {
if (!is_numeric($month)) {
$month = SharedDateHelper::monthStringToNumber($month);
}
} elseif ($month === null) {
$month = 0;
} elseif (is_bool($month)) {
$month = (int) $month;
}
$month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0;
if (!is_numeric($month)) {
throw new Exception(ExcelError::VALUE());
}
@@ -134,11 +142,15 @@ class Date
*/
private static function getDay(mixed $day): int
{
if (($day !== null) && (!is_numeric($day))) {
if (is_string($day) && !is_numeric($day)) {
$day = SharedDateHelper::dayStringToNumber($day);
}
$day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0;
if ($day === null) {
$day = 0;
} elseif (is_scalar($day)) {
$day = StringHelper::testStringAsNumeric((string) $day);
}
if (!is_numeric($day)) {
throw new Exception(ExcelError::VALUE());
}
@@ -151,11 +163,11 @@ class Date
if ($month < 1) {
// Handle year/month adjustment if month < 1
--$month;
$year += ceil($month / 12) - 1;
$year += (int) (ceil($month / 12) - 1);
$month = 13 - abs($month % 12);
} elseif ($month > 12) {
// Handle year/month adjustment if month > 12
$year += floor($month / 12);
$year += intdiv($month, 12);
$month = ($month % 12);
}

View File

@@ -24,7 +24,7 @@ class DateParts
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Day of the month
* @return array<mixed>|int|string Day of the month
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -65,7 +65,7 @@ class DateParts
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Month of the year
* @return array<mixed>|int|string Month of the year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -104,7 +104,7 @@ class DateParts
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Year
* @return array<mixed>|int|string Year
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -25,7 +25,7 @@ class DateValue
* Excel Function:
* DATEVALUE(dateValue)
*
* @param null|array|bool|float|int|string $dateValue Text that represents a date in a Microsoft Excel date format.
* @param null|array<mixed>|bool|float|int|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
@@ -35,7 +35,7 @@ class DateValue
* #VALUE! error value if date_text is out of this range.
* Or can be an array of date values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
@@ -47,7 +47,7 @@ class DateValue
}
// try to parse as date iff there is at least one digit
if (is_string($dateValue) && preg_match('/\\d/', $dateValue) !== 1) {
if (is_string($dateValue) && preg_match('/\d/', $dateValue) !== 1) {
return ExcelError::VALUE();
}
@@ -86,6 +86,7 @@ class DateValue
return self::finalResults($PHPDateArray, $dti, $baseYear);
}
/** @param mixed[] $t1 */
private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string
{
if (count($t1) == 2) {
@@ -108,6 +109,8 @@ class DateValue
/**
* Parse date.
*
* @return mixed[]
*/
private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array
{
@@ -132,6 +135,8 @@ class DateValue
/**
* Final results.
*
* @param mixed[] $PHPDateArray
*
* @return DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
*/
@@ -139,6 +144,7 @@ class DateValue
{
$retValue = ExcelError::Value();
if (Helpers::dateParseSucceeded($PHPDateArray)) {
/** @var array{year: int, month: int, day: int, hour: int, minute: int, second: int} $PHPDateArray */
// Execute function
Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
if ($PHPDateArray['year'] < $baseYear) {
@@ -146,12 +152,13 @@ class DateValue
}
Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
/** @var array{year: int, month: int, day: int, hour: int, minute: int, second: int} $PHPDateArray */
$PHPDateArray['hour'] = 0;
$PHPDateArray['minute'] = 0;
$PHPDateArray['second'] = 0;
$month = (int) $PHPDateArray['month'];
$day = (int) $PHPDateArray['day'];
$year = (int) $PHPDateArray['year'];
$month = self::getInt($PHPDateArray, 'month');
$day = self::getInt($PHPDateArray, 'day');
$year = self::getInt($PHPDateArray, 'year');
if (!checkdate($month, $day, $year)) {
return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE();
}
@@ -160,4 +167,10 @@ class DateValue
return $retValue;
}
/** @param mixed[] $array */
private static function getInt(array $array, string $index): int
{
return (array_key_exists($index, $array) && is_numeric($array[$index])) ? (int) $array[$index] : 0;
}
}

View File

@@ -20,14 +20,14 @@ class Days
* Excel Function:
* DAYS(endDate, startDate)
*
* @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* @param array<mixed>|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* @param array<mixed>|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Number of days between start date and end date or an error
* @return array<mixed>|int|string Number of days between start date and end date or an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
@@ -50,7 +50,7 @@ class Days
$days = ExcelError::VALUE();
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
if ($diff !== false && !is_bool($diff->days)) {
if (!is_bool($diff->days)) {
$days = $diff->days;
if ($diff->invert) {
$days = -$days;

View File

@@ -40,7 +40,7 @@ class Days360
* same month.
* Or can be an array of methods
*
* @return array|int|string Number of days between start date and end date
* @return array<mixed>|int|string Number of days between start date and end date
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/

View File

@@ -22,9 +22,9 @@ class Difference
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* Or can be an array of date values
* @param array|string $unit Or can be an array of unit values
* @param array<mixed>|string $unit Or can be an array of unit values
*
* @return array|int|string Interval between the dates
* @return array<mixed>|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/

View File

@@ -44,7 +44,9 @@ class Helpers
if (!is_numeric($dateValue)) {
$saveReturnDateType = Functions::getReturnDateType();
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
$dateValue = DateValue::fromString($dateValue);
if (is_string($dateValue)) {
$dateValue = DateValue::fromString($dateValue);
}
Functions::setReturnDateType($saveReturnDateType);
if (!is_numeric($dateValue)) {
throw new Exception(ExcelError::VALUE());
@@ -75,8 +77,10 @@ class Helpers
/**
* Adjust date by given months.
*
* @param float|int $dateValue date to be adjusted
*/
public static function adjustDateByMonths(mixed $dateValue = 0, float $adjustmentMonths = 0): DateTime
public static function adjustDateByMonths($dateValue = 0, float $adjustmentMonths = 0): DateTime
{
// Execute function
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
@@ -119,7 +123,7 @@ class Helpers
if (!is_numeric($testVal1) || $testVal1 < 31) {
if (!is_numeric($testVal2) || $testVal2 < 12) {
if (is_numeric($testVal3) && $testVal3 < 12) {
$testVal3 += 2000;
$testVal3 = (string) ($testVal3 + 2000);
}
}
}
@@ -127,6 +131,8 @@ class Helpers
/**
* Return result in one of three formats.
*
* @param array{year: int, month: int, day: int, hour: int, minute: int, second: int} $dateArray
*/
public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false): DateTime|float|int
{
@@ -264,11 +270,16 @@ class Helpers
}
}
/** @return array{year: int, month: int, day: int, hour: int, minute: int, second: int} */
public static function dateParse(string $string): array
{
return self::forceArray(date_parse($string));
/** @var array{year: int, month: int, day: int, hour: int, minute: int, second: int} */
$temp = self::forceArray(date_parse($string));
return $temp;
}
/** @param mixed[] $dateArray */
public static function dateParseSucceeded(array $dateArray): bool
{
return $dateArray['error_count'] === 0;
@@ -278,10 +289,19 @@ class Helpers
* Despite documentation, date_parse probably never returns false.
* Just in case, this routine helps guarantee it.
*
* @param array|false $dateArray
* @param array<mixed>|false $dateArray
*
* @return mixed[]
*/
private static function forceArray(array|bool $dateArray): array
{
return is_array($dateArray) ? $dateArray : ['error_count' => 1];
}
public static function floatOrInt(mixed $value): float|int
{
$result = Functions::scalar($value);
return is_numeric($result) ? ($result + 0) : 0;
}
}

View File

@@ -24,12 +24,12 @@ class Month
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* @param array<mixed>|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.
* Or can be an array of adjustment values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
@@ -68,12 +68,12 @@ class Month
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $adjustmentMonths The number of months before or after start_date.
* @param array<mixed>|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.
* Or can be an array of adjustment values
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions

View File

@@ -29,7 +29,7 @@ class NetworkDays
* Or can be an array of date values
* @param mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return array|int|string Interval between the dates
* @return array<mixed>|int|string Interval between the dates
* If an array of values is passed for the $startDate or $endDate arguments, then the returned result
* will also be an array with matching dimensions
*/

View File

@@ -24,21 +24,21 @@ class Time
* Excel Function:
* TIME(hour,minute,second)
*
* @param null|array|bool|float|int|string $hour A number from 0 (zero) to 32767 representing the hour.
* @param null|array<mixed>|bool|float|int|string $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 null|array|bool|float|int|string $minute A number from 0 to 32767 representing the minute.
* @param null|array<mixed>|bool|float|int|string $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 null|array|bool|float|int|string $second A number from 0 to 32767 representing the second.
* @param null|array<mixed>|bool|float|int|string $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
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
@@ -87,13 +87,13 @@ class Time
private static function adjustSecond(int &$second, int &$minute): void
{
if ($second < 0) {
$minute += floor($second / 60);
$minute += (int) floor($second / 60);
$second = 60 - abs($second % 60);
if ($second == 60) {
$second = 0;
}
} elseif ($second >= 60) {
$minute += floor($second / 60);
$minute += intdiv($second, 60);
$second = $second % 60;
}
}
@@ -101,13 +101,13 @@ class Time
private static function adjustMinute(int &$minute, int &$hour): void
{
if ($minute < 0) {
$hour += floor($minute / 60);
$hour += (int) floor($minute / 60);
$minute = 60 - abs($minute % 60);
if ($minute == 60) {
$minute = 0;
}
} elseif ($minute >= 60) {
$hour += floor($minute / 60);
$hour += intdiv($minute, 60);
$minute = $minute % 60;
}
}

View File

@@ -23,7 +23,7 @@ class TimeParts
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Hour
* @return array<mixed>|int|string Hour
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -35,7 +35,7 @@ class TimeParts
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
if (is_string($timeValue) && !is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);
@@ -64,7 +64,7 @@ class TimeParts
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Minute
* @return array<mixed>|int|string Minute
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -76,7 +76,7 @@ class TimeParts
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
if (is_string($timeValue) && !is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);
@@ -105,7 +105,7 @@ class TimeParts
* PHP DateTime object, or a standard time string
* Or can be an array of date/time values
*
* @return array|int|string Second
* @return array<mixed>|int|string Second
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -117,7 +117,7 @@ class TimeParts
try {
Helpers::nullFalseTrueToNumber($timeValue);
if (!is_numeric($timeValue)) {
if (is_string($timeValue) && !is_numeric($timeValue)) {
$timeValue = Helpers::getTimeValue($timeValue);
}
Helpers::validateNotNegative($timeValue);

View File

@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use Composer\Pcre\Preg;
use Datetime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
@@ -12,6 +13,19 @@ class TimeValue
{
use ArrayEnabled;
private const EXTRACT_TIME = '/\b'
. '(\d+)' // match[1] - hour
. '(:' // start of match[2] (rest of string) - colon
. '(\d+' // start of match[3] - minute
. '(:\d+' // start of match[4] - colon and seconds
. '([.]\d+)?' // match[5] - optional decimal point followed by fractional seconds
. ')?' // end of match[4], which is optional
. ')' // end of match 3
// Excel does not require 'm' to trail 'a' or 'p'; Php does
. '(\s*(a|p))?' // match[6] optional whitespace followed by optional match[7] a or p
. ')' // end of match[2]
. '/i';
/**
* TIMEVALUE.
*
@@ -25,13 +39,13 @@ class TimeValue
* Excel Function:
* TIMEVALUE(timeValue)
*
* @param null|array|bool|float|int|string $timeValue A text string that represents a time in any one of the Microsoft
* @param null|array<mixed>|bool|float|int|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.
* Or can be an array of date/time values
*
* @return array|Datetime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|Datetime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
@@ -43,17 +57,20 @@ class TimeValue
}
// try to parse as time iff there is at least one digit
if (is_string($timeValue) && preg_match('/\\d/', $timeValue) !== 1) {
if (is_string($timeValue) && !Preg::isMatch('/\d/', $timeValue)) {
return ExcelError::VALUE();
}
$timeValue = trim((string) $timeValue, '"');
$timeValue = str_replace(['/', '.'], '-', $timeValue);
$arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
$arraySplit[0] = ((int) $arraySplit[0] % 24);
$timeValue = implode(':', $arraySplit);
if (Preg::isMatch(self::EXTRACT_TIME, $timeValue, $matches)) {
if (empty($matches[6])) { // am/pm
$hour = (int) $matches[0];
$timeValue = ($hour % 24) . $matches[2];
} elseif ($matches[6] === $matches[7]) { // Excel wants space before am/pm
return ExcelError::VALUE();
} else {
$timeValue = $matches[0] . 'm';
}
}
$PHPDateArray = Helpers::dateParse($timeValue);

View File

@@ -28,7 +28,7 @@ class Week
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $method Week begins on Sunday or Monday
* @param array<mixed>|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.
@@ -41,7 +41,7 @@ class Week
* 21 ISO (Jan. 4 is week 1, begins on Monday).
* Or can be an array of methods
*
* @return array|int|string Week Number
* @return array<mixed>|int|string Week Number
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -101,7 +101,7 @@ class Week
* PHP DateTime object, or a standard date string
* Or can be an array of date values
*
* @return array|int|string Week Number
* @return array<mixed>|int|string Week Number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -137,7 +137,7 @@ class Week
* Excel Function:
* WEEKDAY(dateValue[,style])
*
* @param null|array|bool|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
* @param null|array<mixed>|bool|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param mixed $style A number that determines the type of return value
@@ -146,7 +146,7 @@ class Week
* 3 Numbers 0 (Monday) through 6 (Sunday).
* Or can be an array of styles
*
* @return array|int|string Day of the week value
* @return array<mixed>|int|string Day of the week value
* If an array of values is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -22,16 +22,16 @@ class WorkDay
* Excel Function:
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
*
* @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* @param array<mixed>|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of date values
* @param array|int $endDays The number of nonweekend and nonholiday days before or after
* @param array<mixed>|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.
* Or can be an array of int values
* @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return array|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array<mixed>|DateTime|float|int|string Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
@@ -72,6 +72,8 @@ class WorkDay
/**
* Use incrementing logic to determine Workday.
*
* @param array<mixed> $holidayArray
*/
private static function incrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
{
@@ -103,10 +105,12 @@ class WorkDay
return Helpers::returnIn3FormatsFloat($endDate);
}
/** @param array<mixed> $holidayArray */
private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
{
$holidayCountedArray = $holidayDates = [];
foreach ($holidayArray as $holidayDate) {
/** @var float $holidayDate */
if (self::getWeekDay($holidayDate, 3) < 5) {
$holidayDates[] = $holidayDate;
}
@@ -131,6 +135,8 @@ class WorkDay
/**
* Use decrementing logic to determine Workday.
*
* @param array<mixed> $holidayArray
*/
private static function decrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
{
@@ -162,10 +168,12 @@ class WorkDay
return Helpers::returnIn3FormatsFloat($endDate);
}
/** @param array<mixed> $holidayArray */
private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
{
$holidayCountedArray = $holidayDates = [];
foreach ($holidayArray as $holidayDate) {
/** @var float $holidayDate */
if (self::getWeekDay($holidayDate, 3) < 5) {
$holidayDates[] = $holidayDate;
}

View File

@@ -31,7 +31,7 @@ class YearFrac
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* Or can be an array of methods
* @param array|int $method Method used for the calculation
* @param array<mixed>|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
@@ -39,7 +39,7 @@ class YearFrac
* 4 European 30/360
* Or can be an array of methods
*
* @return array|float|int|string fraction of the year, or a string containing an error
* @return array<mixed>|float|int|string fraction of the year, or a string containing an error
* If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
* will also be an array with matching dimensions
*/
@@ -62,11 +62,11 @@ class YearFrac
}
return match ($method) {
0 => Functions::scalar(Days360::between($startDate, $endDate)) / 360,
0 => Helpers::floatOrInt(Days360::between($startDate, $endDate)) / 360,
1 => self::method1($startDate, $endDate),
2 => Functions::scalar(Difference::interval($startDate, $endDate)) / 360,
3 => Functions::scalar(Difference::interval($startDate, $endDate)) / 365,
4 => Functions::scalar(Days360::between($startDate, $endDate, true)) / 360,
2 => Helpers::floatOrInt(Difference::interval($startDate, $endDate)) / 360,
3 => Helpers::floatOrInt(Difference::interval($startDate, $endDate)) / 365,
4 => Helpers::floatOrInt(Days360::between($startDate, $endDate, true)) / 360,
default => ExcelError::NAN(),
};
}
@@ -91,7 +91,7 @@ class YearFrac
private static function method1(float $startDate, float $endDate): float
{
$days = Functions::scalar(Difference::interval($startDate, $endDate));
$days = Helpers::floatOrInt(Difference::interval($startDate, $endDate));
$startYear = (int) DateParts::year($startDate);
$endYear = (int) DateParts::year($endDate);
$years = $endYear - $startYear + 1;

View File

@@ -8,14 +8,18 @@ class ArrayArgumentHelper
{
protected int $indexStart = 0;
/** @var mixed[] */
protected array $arguments;
protected int $argumentCount;
/** @var int[] */
protected array $rows;
/** @var int[] */
protected array $columns;
/** @param mixed[] $arguments */
public function initialise(array $arguments): void
{
$keys = array_keys($arguments);
@@ -34,6 +38,7 @@ class ArrayArgumentHelper
}
}
/** @return mixed[] */
public function arguments(): array
{
return $this->arguments;
@@ -65,6 +70,7 @@ class ArrayArgumentHelper
return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
}
/** @return int[] */
private function getRowVectors(): array
{
$rowVectors = [];
@@ -84,6 +90,7 @@ class ArrayArgumentHelper
return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
}
/** @return int[] */
private function getColumnVectors(): array
{
$columnVectors = [];
@@ -96,6 +103,7 @@ class ArrayArgumentHelper
return $columnVectors;
}
/** @return int[] */
public function getMatrixPair(): array
{
for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) {
@@ -134,6 +142,11 @@ class ArrayArgumentHelper
return $this->columns[$argument];
}
/**
* @param mixed[] $arguments
*
* @return int[]
*/
private function rows(array $arguments): array
{
return array_map(
@@ -142,14 +155,17 @@ class ArrayArgumentHelper
);
}
/**
* @param mixed[] $arguments
*
* @return int[]
*/
private function columns(array $arguments): array
{
return array_map(
function (mixed $argument): int {
return is_array($argument) && is_array($argument[array_keys($argument)[0]])
fn (mixed $argument): int => is_array($argument) && is_array($argument[array_keys($argument)[0]])
? count($argument[array_keys($argument)[0]])
: 1;
},
: 1,
$arguments
);
}
@@ -166,6 +182,13 @@ class ArrayArgumentHelper
return $count;
}
/**
* @param mixed[] $arguments
* @param int[] $rows
* @param int[] $columns
*
* @return mixed[]
*/
private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
{
foreach ($arguments as $index => $argument) {
@@ -180,6 +203,11 @@ class ArrayArgumentHelper
return $arguments;
}
/**
* @param mixed[] $array
*
* @return mixed[]
*/
private function filterArray(array $array): array
{
return array_filter(

View File

@@ -2,12 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class ArrayArgumentProcessor
{
private static ArrayArgumentHelper $arrayArgumentHelper;
/** @return mixed[] */
public static function processArguments(
ArrayArgumentHelper $arrayArgumentHelper,
callable $method,
@@ -54,23 +56,30 @@ class ArrayArgumentProcessor
return ['#VALUE!'];
}
/**
* @param int[] $matrixIndexes
*
* @return mixed[]
*/
private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, mixed ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
$matrix2 = array_pop($matrixIndexes) ?? throw new Exception('empty array 2');
/** @var mixed[][] $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
$matrix1 = array_pop($matrixIndexes) ?? throw new Exception('empty array 1');
/** @var mixed[][] $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
/** @var non-empty-array<int> */
$matrix12 = [$matrix1, $matrix2];
$rows = min(array_map(self::$arrayArgumentHelper->rowCount(...), $matrix12));
$columns = min(array_map(self::$arrayArgumentHelper->columnCount(...), $matrix12));
if ($rows === 1) {
$rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
$rows = max(array_map(self::$arrayArgumentHelper->rowCount(...), $matrix12));
}
if ($columns === 1) {
$columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
$columns = max(array_map(self::$arrayArgumentHelper->columnCount(...), $matrix12));
}
$result = [];
@@ -92,13 +101,18 @@ class ArrayArgumentProcessor
return $result;
}
/**
* @param mixed[] $matrixIndexes
*
* @return mixed[]
*/
private static function evaluateMatrixPair(callable $method, array $matrixIndexes, mixed ...$arguments): array
{
$matrix2 = array_pop($matrixIndexes);
/** @var array $matrixValues2 */
/** @var mixed[][] $matrixValues2 */
$matrixValues2 = $arguments[$matrix2];
$matrix1 = array_pop($matrixIndexes);
/** @var array $matrixValues1 */
/** @var mixed[][] $matrixValues1 */
$matrixValues1 = $arguments[$matrix1];
$result = [];
@@ -119,6 +133,7 @@ class ArrayArgumentProcessor
return $result;
}
/** @return mixed[] */
private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, mixed ...$arguments): array
{
$rowVector = Functions::flattenArray($arguments[$rowIndex]);
@@ -141,11 +156,13 @@ class ArrayArgumentProcessor
/**
* Note, offset is from 1 (for the first argument) rather than from 0.
*
* @return mixed[]
*/
private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, mixed ...$arguments): array
{
$values = array_slice($arguments, $nthArgument - 1, 1);
/** @var array $values */
/** @var mixed[] $values */
$values = array_pop($values);
$result = [];

View File

@@ -16,40 +16,30 @@ class FormattedNumber
// preg_quoted string for major currency symbols, with a %s for locale currency
private const CURRENCY_CONVERSION_LIST = '\$€£¥%s';
private const STRING_CONVERSION_LIST = [
[self::class, 'convertToNumberIfNumeric'],
[self::class, 'convertToNumberIfFraction'],
[self::class, 'convertToNumberIfPercent'],
[self::class, 'convertToNumberIfCurrency'],
];
/**
* Identify whether a string contains a formatted numeric value,
* and convert it to a numeric if it is.
*
* @param string $operand string value to test
* @param float|string $operand string value to test
*/
public static function convertToNumberIfFormatted(string &$operand): bool
public static function convertToNumberIfFormatted(float|string &$operand): bool
{
foreach (self::STRING_CONVERSION_LIST as $conversionMethod) {
if ($conversionMethod($operand) === true) {
return true;
}
}
return false;
return self::convertToNumberIfNumeric($operand)
|| self::convertToNumberIfFraction($operand)
|| self::convertToNumberIfPercent($operand)
|| self::convertToNumberIfCurrency($operand);
}
/**
* Identify whether a string contains a numeric value,
* and convert it to a numeric if it is.
*
* @param string $operand string value to test
* @param float|string $operand string value to test
*/
public static function convertToNumberIfNumeric(string &$operand): bool
public static function convertToNumberIfNumeric(float|string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand));
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim("$operand"));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
@@ -68,13 +58,15 @@ class FormattedNumber
*
* @param string $operand string value to test
*/
public static function convertToNumberIfFraction(string &$operand): bool
public static function convertToNumberIfFraction(float|string &$operand): bool
{
if (preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) {
if (is_string($operand) && preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) {
$sign = ($match[1] === '-') ? '-' : '+';
$wholePart = ($match[3] === '') ? '' : ($sign . $match[3]);
$fractionFormula = '=' . $wholePart . $sign . $match[4];
$operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
/** @var string */
$operandx = Calculation::getInstance()->_calculateFormulaValue($fractionFormula);
$operand = $operandx;
return true;
}
@@ -86,12 +78,12 @@ class FormattedNumber
* Identify whether a string contains a percentage, and if so,
* convert it to a numeric.
*
* @param string $operand string value to test
* @param float|string $operand string value to test
*/
public static function convertToNumberIfPercent(string &$operand): bool
public static function convertToNumberIfPercent(float|string &$operand): bool
{
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim("$operand"));
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
@@ -111,13 +103,13 @@ class FormattedNumber
* Identify whether a string contains a currency value, and if so,
* convert it to a numeric.
*
* @param string $operand string value to test
* @param float|string $operand string value to test
*/
public static function convertToNumberIfCurrency(string &$operand): bool
public static function convertToNumberIfCurrency(float|string &$operand): bool
{
$currencyRegexp = self::currencyMatcherRegexp();
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand);
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', "$operand");
$match = [];
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) {

View File

@@ -78,7 +78,7 @@ class Logger
{
// Only write the debug log if logging is enabled
if ($this->writeDebugLog) {
$message = sprintf($message, ...$args);
$message = sprintf($message, ...$args); //* @phpstan-ignore-line
$cellReference = implode(' -> ', $this->cellStack->showStack());
if ($this->echoDebugLog) {
echo $cellReference,

View File

@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands;
interface Operand
{
/** @param string[] $matches */
public static function fromParser(string $formula, int $index, array $matches): self;
public function value(): string;

View File

@@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use Stringable;
@@ -29,7 +30,7 @@ final class StructuredReference implements Operand, Stringable
self::ITEM_SPECIFIER_TOTALS,
];
private const TABLE_REFERENCE = '/([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu';
private const TABLE_REFERENCE = '/([\p{L}_\\\][\p{L}\p{N}\._]+)?(\[(?:[^\]\[]+|(?R))*+\])/miu';
private string $value;
@@ -47,6 +48,7 @@ final class StructuredReference implements Operand, Stringable
private ?int $totalsRow;
/** @var mixed[] */
private array $columns;
public function __construct(string $structuredReference)
@@ -54,6 +56,7 @@ final class StructuredReference implements Operand, Stringable
$this->value = $structuredReference;
}
/** @param string[] $matches */
public static function fromParser(string $formula, int $index, array $matches): self
{
$val = $matches[0];
@@ -171,14 +174,20 @@ final class StructuredReference implements Operand, Stringable
return $table;
}
/**
* @param array{array{string, int}, array{string, int}} $tableRange
*
* @return mixed[]
*/
private function getColumns(Cell $cell, array $tableRange): array
{
$worksheet = $cell->getWorksheet();
$cellReference = $cell->getCoordinate();
$columns = [];
$lastColumn = ++$tableRange[1][0];
for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) {
$lastColumn = StringHelper::stringIncrement($tableRange[1][0]);
for ($column = $tableRange[0][0]; $column !== $lastColumn; StringHelper::stringIncrement($column)) {
/** @var string $column */
$columns[$column] = $worksheet
->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1)))
->getCalculatedValue();
@@ -196,7 +205,7 @@ final class StructuredReference implements Operand, Stringable
$reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference);
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName);
$columnName = str_replace("\u{a0}", ' ', $columnName); //* @phpstan-ignore-line
$reference = $this->adjustRowReference($columnName, $reference, $cell, $columnId);
}
@@ -330,7 +339,7 @@ final class StructuredReference implements Operand, Stringable
{
$columnsSelected = false;
foreach ($this->columns as $columnId => $columnName) {
$columnName = str_replace("\u{a0}", ' ', $columnName ?? '');
$columnName = str_replace("\u{a0}", ' ', $columnName ?? ''); //* @phpstan-ignore-line
$cellFrom = "{$columnId}{$startRow}";
$cellTo = "{$columnId}{$endRow}";
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";

View File

@@ -31,7 +31,7 @@ class BesselI
* If $ord < 0, BESSELI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* @return array<mixed>|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -30,7 +30,7 @@ class BesselJ
* If $ord < 0, BESSELJ returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* @return array<mixed>|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -28,7 +28,7 @@ class BesselK
* If $ord < 0, BESSELKI returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* @return array<mixed>|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -27,7 +27,7 @@ class BesselY
* If $ord < 0, BESSELY returns the #NUM! error value.
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* @return array<mixed>|float|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -30,10 +30,10 @@ class BitWise
* Excel Function:
* BITAND(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number1 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITAND(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
@@ -62,10 +62,10 @@ class BitWise
* Excel Function:
* BITOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number1 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
@@ -95,10 +95,10 @@ class BitWise
* Excel Function:
* BITXOR(number1, number2)
*
* @param null|array|bool|float|int|string $number1 Or can be an array of values
* @param null|array|bool|float|int|string $number2 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number1 Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number2 Or can be an array of values
*
* @return array|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|int|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITXOR(null|array|bool|float|int|string $number1, null|array|bool|float|int|string $number2): array|string|int|float
@@ -128,10 +128,10 @@ class BitWise
* Excel Function:
* BITLSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITLSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
@@ -163,10 +163,10 @@ class BitWise
* Excel Function:
* BITRSHIFT(number, shift_amount)
*
* @param null|array|bool|float|int|string $number Or can be an array of values
* @param null|array|bool|float|int|string $shiftAmount Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $number Or can be an array of values
* @param null|array<mixed>|bool|float|int|string $shiftAmount Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function BITRSHIFT(null|array|bool|float|int|string $number, null|array|bool|float|int|string $shiftAmount): array|string|float
@@ -221,7 +221,7 @@ class BitWise
$value = self::nullFalseTrueToNumber($value);
if (is_numeric($value)) {
if (abs($value) > 53) {
if (abs($value + 0) > 53) {
throw new Exception(ExcelError::NAN());
}

View File

@@ -20,12 +20,12 @@ class Compare
* functions you calculate the count of equal pairs. This function is also known as the
* Kronecker Delta function.
*
* @param array|bool|float|int|string $a the first number
* @param array<mixed>|bool|float|int|string $a the first number
* Or can be an array of values
* @param array|bool|float|int|string $b The second number. If omitted, b is assumed to be zero.
* @param array<mixed>|bool|float|int|string $b The second number. If omitted, b is assumed to be zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* @return array<mixed>|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -55,12 +55,12 @@ class Compare
* Use this function to filter a set of values. For example, by summing several GESTEP
* functions you calculate the count of values that exceed a threshold.
*
* @param array|bool|float|int|string $number the value to test against step
* @param array<mixed>|bool|float|int|string $number the value to test against step
* Or can be an array of values
* @param null|array|bool|float|int|string $step The threshold value. If you omit a value for step, GESTEP uses zero.
* @param null|array<mixed>|bool|float|int|string $step The threshold value. If you omit a value for step, GESTEP uses zero.
* Or can be an array of values
*
* @return array|int|string (string in the event of an error)
* @return array<mixed>|int|string (string in the event of an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -28,7 +28,7 @@ class Complex
* If omitted, the suffix is assumed to be "i".
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function COMPLEX(mixed $realNumber = 0.0, mixed $imaginary = 0.0, mixed $suffix = 'i'): array|string
@@ -65,11 +65,11 @@ class Complex
* Excel Function:
* IMAGINARY(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the imaginary
* @param array<mixed>|string $complexNumber the complex number for which you want the imaginary
* coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* @return array<mixed>|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -96,10 +96,10 @@ class Complex
* Excel Function:
* IMREAL(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the real coefficient
* @param array<mixed>|string $complexNumber the complex number for which you want the real coefficient
* Or can be an array of values
*
* @return array|float|string (string if an error)
* @return array<mixed>|float|string (string if an error)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -19,10 +19,10 @@ class ComplexFunctions
* Excel Function:
* IMABS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the absolute value
* @param array<mixed>|string $complexNumber the complex number for which you want the absolute value
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMABS(array|string $complexNumber): array|float|string
@@ -49,10 +49,10 @@ class ComplexFunctions
* Excel Function:
* IMARGUMENT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the argument theta
* @param array<mixed>|string $complexNumber the complex number for which you want the argument theta
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMARGUMENT(array|string $complexNumber): array|float|string
@@ -82,10 +82,10 @@ class ComplexFunctions
* Excel Function:
* IMCONJUGATE(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the conjugate
* @param array<mixed>|string $complexNumber the complex number for which you want the conjugate
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCONJUGATE(array|string $complexNumber): array|string
@@ -111,10 +111,10 @@ class ComplexFunctions
* Excel Function:
* IMCOS(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosine
* @param array<mixed>|string $complexNumber the complex number for which you want the cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOS(array|string $complexNumber): array|string
@@ -140,10 +140,10 @@ class ComplexFunctions
* Excel Function:
* IMCOSH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
* @param array<mixed>|string $complexNumber the complex number for which you want the hyperbolic cosine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOSH(array|string $complexNumber): array|string
@@ -169,10 +169,10 @@ class ComplexFunctions
* Excel Function:
* IMCOT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cotangent
* @param array<mixed>|string $complexNumber the complex number for which you want the cotangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCOT(array|string $complexNumber): array|string
@@ -198,10 +198,10 @@ class ComplexFunctions
* Excel Function:
* IMCSC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the cosecant
* @param array<mixed>|string $complexNumber the complex number for which you want the cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSC(array|string $complexNumber): array|string
@@ -227,10 +227,10 @@ class ComplexFunctions
* Excel Function:
* IMCSCH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
* @param array<mixed>|string $complexNumber the complex number for which you want the hyperbolic cosecant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMCSCH(array|string $complexNumber): array|string
@@ -256,10 +256,10 @@ class ComplexFunctions
* Excel Function:
* IMSIN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the sine
* @param array<mixed>|string $complexNumber the complex number for which you want the sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSIN(array|string $complexNumber): array|string
@@ -285,10 +285,10 @@ class ComplexFunctions
* Excel Function:
* IMSINH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic sine
* @param array<mixed>|string $complexNumber the complex number for which you want the hyperbolic sine
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSINH(array|string $complexNumber): array|string
@@ -314,10 +314,10 @@ class ComplexFunctions
* Excel Function:
* IMSEC(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the secant
* @param array<mixed>|string $complexNumber the complex number for which you want the secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSEC(array|string $complexNumber): array|string
@@ -343,10 +343,10 @@ class ComplexFunctions
* Excel Function:
* IMSECH(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the hyperbolic secant
* @param array<mixed>|string $complexNumber the complex number for which you want the hyperbolic secant
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSECH(array|string $complexNumber): array|string
@@ -372,10 +372,10 @@ class ComplexFunctions
* Excel Function:
* IMTAN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the tangent
* @param array<mixed>|string $complexNumber the complex number for which you want the tangent
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMTAN(array|string $complexNumber): array|string
@@ -401,10 +401,10 @@ class ComplexFunctions
* Excel Function:
* IMSQRT(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the square root
* @param array<mixed>|string $complexNumber the complex number for which you want the square root
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSQRT(array|string $complexNumber): array|string
@@ -435,10 +435,10 @@ class ComplexFunctions
* Excel Function:
* IMLN(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the natural logarithm
* @param array<mixed>|string $complexNumber the complex number for which you want the natural logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLN(array|string $complexNumber): array|string
@@ -468,10 +468,10 @@ class ComplexFunctions
* Excel Function:
* IMLOG10(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the common logarithm
* @param array<mixed>|string $complexNumber the complex number for which you want the common logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG10(array|string $complexNumber): array|string
@@ -501,10 +501,10 @@ class ComplexFunctions
* Excel Function:
* IMLOG2(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the base-2 logarithm
* @param array<mixed>|string $complexNumber the complex number for which you want the base-2 logarithm
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMLOG2(array|string $complexNumber): array|string
@@ -534,10 +534,10 @@ class ComplexFunctions
* Excel Function:
* IMEXP(complexNumber)
*
* @param array|string $complexNumber the complex number for which you want the exponential
* @param array<mixed>|string $complexNumber the complex number for which you want the exponential
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMEXP(array|string $complexNumber): array|string
@@ -563,12 +563,12 @@ class ComplexFunctions
* Excel Function:
* IMPOWER(complexNumber,realNumber)
*
* @param array|string $complexNumber the complex number you want to raise to a power
* @param array<mixed>|string $complexNumber the complex number you want to raise to a power
* Or can be an array of values
* @param array|float|int|string $realNumber the power to which you want to raise the complex number
* @param array<mixed>|float|int|string $realNumber the power to which you want to raise the complex number
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMPOWER(array|string $complexNumber, array|float|int|string $realNumber): array|string

View File

@@ -20,12 +20,12 @@ class ComplexOperations
* Excel Function:
* IMDIV(complexDividend,complexDivisor)
*
* @param array|string $complexDividend the complex numerator or dividend
* @param array<mixed>|string $complexDividend the complex numerator or dividend
* Or can be an array of values
* @param array|string $complexDivisor the complex denominator or divisor
* @param array<mixed>|string $complexDivisor the complex denominator or divisor
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMDIV(array|string $complexDividend, array|string $complexDivisor): array|string
@@ -49,12 +49,12 @@ class ComplexOperations
* Excel Function:
* IMSUB(complexNumber1,complexNumber2)
*
* @param array|string $complexNumber1 the complex number from which to subtract complexNumber2
* @param array<mixed>|string $complexNumber1 the complex number from which to subtract complexNumber2
* Or can be an array of values
* @param array|string $complexNumber2 the complex number to subtract from complexNumber1
* @param array<mixed>|string $complexNumber2 the complex number to subtract from complexNumber1
* Or can be an array of values
*
* @return array|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function IMSUB(array|string $complexNumber1, array|string $complexNumber2): array|string

View File

@@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
abstract class ConvertBase
{
@@ -26,7 +27,7 @@ abstract class ConvertBase
}
}
return strtoupper((string) $value);
return strtoupper(StringHelper::convertToString($value));
}
protected static function validatePlaces(mixed $places = null): ?int

View File

@@ -15,7 +15,7 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2DEC(x)
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* @param array<mixed>|bool|float|int|string $value 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.
@@ -23,7 +23,7 @@ class ConvertBinary extends ConvertBase
* 10 characters (10 bits), BIN2DEC returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|float|int|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -44,10 +44,10 @@ class ConvertBinary extends ConvertBase
// Two's Complement
$value = substr($value, -9);
return '-' . (512 - bindec($value));
return -(512 - bindec($value));
}
return (string) bindec($value);
return bindec($value);
}
/**
@@ -58,14 +58,14 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2HEX(x[,places])
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* @param array<mixed>|bool|float|int|string $value 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.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2HEX uses the
* @param null|array<mixed>|float|int|string $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.
@@ -73,7 +73,7 @@ class ConvertBinary extends ConvertBase
* If places is negative, BIN2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -111,14 +111,14 @@ class ConvertBinary extends ConvertBase
* Excel Function:
* BIN2OCT(x[,places])
*
* @param array|bool|float|int|string $value The binary number (as a string) that you want to convert. The number
* @param array<mixed>|bool|float|int|string $value 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.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, BIN2OCT uses the
* @param null|array<mixed>|float|int|string $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.
@@ -126,7 +126,7 @@ class ConvertBinary extends ConvertBase
* If places is negative, BIN2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -22,7 +22,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2BIN(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* @param array<mixed>|bool|float|int|string $value 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
@@ -33,7 +33,7 @@ class ConvertDecimal extends ConvertBase
* If DEC2BIN requires more than places characters, it returns the #NUM!
* error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2BIN uses
* @param null|array<mixed>|float|int|string $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.
@@ -41,7 +41,7 @@ class ConvertDecimal extends ConvertBase
* If places is zero or negative, DEC2BIN returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -79,7 +79,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2HEX(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* @param array<mixed>|bool|float|int|string $value 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
@@ -90,7 +90,7 @@ class ConvertDecimal extends ConvertBase
* If DEC2HEX requires more than places characters, it returns the
* #NUM! error value.
* Or can be an array of values
* @param null|array|float|int|string $places The number of characters to use. If places is omitted, DEC2HEX uses
* @param null|array<mixed>|float|int|string $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.
@@ -98,7 +98,7 @@ class ConvertDecimal extends ConvertBase
* If places is zero or negative, DEC2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -155,7 +155,7 @@ class ConvertDecimal extends ConvertBase
* Excel Function:
* DEC2OCT(x[,places])
*
* @param array|bool|float|int|string $value The decimal integer you want to convert. If number is negative,
* @param array<mixed>|bool|float|int|string $value 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
@@ -166,7 +166,7 @@ class ConvertDecimal extends ConvertBase
* If DEC2OCT requires more than places characters, it returns the
* #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, DEC2OCT uses
* @param array<mixed>|int $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.
@@ -174,7 +174,7 @@ class ConvertDecimal extends ConvertBase
* If places is zero or negative, DEC2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -15,7 +15,7 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2BIN(x[,places])
*
* @param array|bool|float|string $value The hexadecimal number you want to convert.
* @param array<mixed>|bool|float|string $value The hexadecimal number 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.
@@ -26,7 +26,7 @@ class ConvertHex extends ConvertBase
* 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.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted,
* @param array<mixed>|int $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.
@@ -34,7 +34,7 @@ class ConvertHex extends ConvertBase
* If places is negative, HEX2BIN returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -65,7 +65,7 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2DEC(x)
*
* @param array|bool|float|int|string $value The hexadecimal number you want to convert. This number cannot
* @param array<mixed>|bool|float|int|string $value The hexadecimal number 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
@@ -74,7 +74,7 @@ class ConvertHex extends ConvertBase
* #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|float|int|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -96,7 +96,7 @@ class ConvertHex extends ConvertBase
}
$binX = '';
foreach (str_split($value) as $char) {
foreach (mb_str_split($value, 1, 'UTF-8') as $char) {
$binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 40 && $binX[0] == '1') {
@@ -104,10 +104,10 @@ class ConvertHex extends ConvertBase
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (string) ((bindec($binX) + 1) * -1);
return (bindec($binX) + 1) * -1;
}
return (string) bindec($binX);
return bindec($binX);
}
/**
@@ -118,7 +118,7 @@ class ConvertHex extends ConvertBase
* Excel Function:
* HEX2OCT(x[,places])
*
* @param array|bool|float|int|string $value The hexadecimal number you want to convert. Number cannot
* @param array<mixed>|bool|float|int|string $value The hexadecimal number 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
@@ -132,7 +132,7 @@ class ConvertHex extends ConvertBase
* If HEX2OCT requires more than places characters, it returns
* the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, HEX2OCT
* @param array<mixed>|int $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.
@@ -141,7 +141,7 @@ class ConvertHex extends ConvertBase
* If places is negative, HEX2OCT returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -15,7 +15,7 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2BIN(x[,places])
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not
* @param array<mixed>|bool|float|int|string $value 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
@@ -29,7 +29,7 @@ class ConvertOctal extends ConvertBase
* If OCT2BIN requires more than places characters, it
* returns the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted,
* @param array<mixed>|int $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).
@@ -40,7 +40,7 @@ class ConvertOctal extends ConvertBase
* value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -69,7 +69,7 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2DEC(x)
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain
* @param array<mixed>|bool|float|int|string $value 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
@@ -78,7 +78,7 @@ class ConvertOctal extends ConvertBase
* #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|float|int|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -96,7 +96,7 @@ class ConvertOctal extends ConvertBase
}
$binX = '';
foreach (str_split($value) as $char) {
foreach (mb_str_split($value, 1, 'UTF-8') as $char) {
$binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT);
}
if (strlen($binX) == 30 && $binX[0] == '1') {
@@ -104,10 +104,10 @@ class ConvertOctal extends ConvertBase
$binX[$i] = ($binX[$i] == '1' ? '0' : '1');
}
return (string) ((bindec($binX) + 1) * -1);
return (bindec($binX) + 1) * -1;
}
return (string) bindec($binX);
return bindec($binX);
}
/**
@@ -118,7 +118,7 @@ class ConvertOctal extends ConvertBase
* Excel Function:
* OCT2HEX(x[,places])
*
* @param array|bool|float|int|string $value The octal number you want to convert. Number may not contain
* @param array<mixed>|bool|float|int|string $value 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
@@ -130,7 +130,7 @@ class ConvertOctal extends ConvertBase
* If OCT2HEX requires more than places characters, it returns
* the #NUM! error value.
* Or can be an array of values
* @param array|int $places The number of characters to use. If places is omitted, OCT2HEX
* @param array<mixed>|int $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.
@@ -138,7 +138,7 @@ class ConvertOctal extends ConvertBase
* If places is negative, OCT2HEX returns the #NUM! error value.
* Or can be an array of values
*
* @return array|string Result, or an error
* @return array<mixed>|string Result, or an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -230,7 +230,7 @@ class ConvertUOM
/**
* Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
** @var array<string, array{multiplier: float|int, name: string}>
* @var array<string, array{multiplier: float|int, name: string}>
*/
private static array $binaryConversionMultipliers = [
'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'],
@@ -435,6 +435,8 @@ class ConvertUOM
/**
* getConversionGroups
* Returns a list of the different conversion groups for UOM conversions.
*
* @return string[]
*/
public static function getConversionCategories(): array
{
@@ -451,6 +453,8 @@ class ConvertUOM
* Returns an array of units of measure, for a specified conversion group, or for all groups.
*
* @param ?string $category The group whose units of measure you want to retrieve
*
* @return string[][]
*/
public static function getConversionCategoryUnits(?string $category = null): array
{
@@ -468,6 +472,8 @@ class ConvertUOM
* getConversionGroupUnitDetails.
*
* @param ?string $category The group whose units of measure you want to retrieve
*
* @return array<string, list<array<string, string>>>
*/
public static function getConversionCategoryUnitDetails(?string $category = null): array
{
@@ -488,7 +494,7 @@ class ConvertUOM
* getConversionMultipliers
* Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
*
* @return mixed[]
* @return array<string, array{multiplier: float, name: string}>
*/
public static function getConversionMultipliers(): array
{
@@ -499,7 +505,7 @@ class ConvertUOM
* getBinaryConversionMultipliers
* Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM().
*
* @return mixed[]
* @return array<string, array{multiplier: float|int, name: string}>
*/
public static function getBinaryConversionMultipliers(): array
{
@@ -516,14 +522,14 @@ class ConvertUOM
* Excel Function:
* CONVERT(value,fromUOM,toUOM)
*
* @param array|float|int|string $value the value in fromUOM to convert
* @param array<mixed>|float|int|string $value the value in fromUOM to convert
* Or can be an array of values
* @param array|string $fromUOM the units for value
* @param string|string[] $fromUOM the units for value
* Or can be an array of values
* @param array|string $toUOM the units for the result
* @param string|string[] $toUOM the units for the result
* Or can be an array of values
*
* @return array|float|string Result, or a string containing an error
* @return float|mixed[]|string Result, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -566,6 +572,7 @@ class ConvertUOM
return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
}
/** @return array{0: string, 1: string, 2: float} */
private static function getUOMDetails(string $uom): array
{
if (isset(self::$conversionUnits[$uom])) {

View File

@@ -31,7 +31,7 @@ class Erf
* If omitted, ERF integrates between zero and lower_limit
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERF(mixed $lower, mixed $upper = null): array|float|string
@@ -63,7 +63,7 @@ class Erf
* @param mixed $limit Float bound for integrating ERF, other bound is zero
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFPRECISE(mixed $limit)

View File

@@ -26,7 +26,7 @@ class ErfC
* @param mixed $value The float lower bound for integrating ERFC
* Or can be an array of values
*
* @return array|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|float|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function ERFC(mixed $value)

View File

@@ -54,9 +54,7 @@ class Amortization
$salvage = Functions::flattenSingleValue($salvage);
$period = Functions::flattenSingleValue($period);
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$cost = FinancialValidations::validateFloat($cost);
@@ -141,9 +139,7 @@ class Amortization
$salvage = Functions::flattenSingleValue($salvage);
$period = Functions::flattenSingleValue($period);
$rate = Functions::flattenSingleValue($rate);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$cost = FinancialValidations::validateFloat($cost);
@@ -171,9 +167,13 @@ class Amortization
if (
$basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL
&& $yearFrac < 1
&& DateTimeExcel\Helpers::isLeapYear(Functions::scalar($purchasedYear))
) {
$yearFrac *= 365 / 366;
$temp = Functions::scalar($purchasedYear);
if (is_int($temp) || is_string($temp)) {
if (DateTimeExcel\Helpers::isLeapYear($temp)) {
$yearFrac *= 365 / 366;
}
}
}
$f0Rate = $yearFrac * $rate * $cost;

View File

@@ -38,9 +38,9 @@ class Periodic
): string|float {
$rate = Functions::flattenSingleValue($rate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
$presentValue = ($presentValue === null) ? 0.0 : Functions::flattenSingleValue($presentValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$payment = Functions::flattenSingleValue($payment) ?? 0.0;
$presentValue = Functions::flattenSingleValue($presentValue) ?? 0.0;
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$rate = CashFlowValidations::validateRate($rate);
@@ -77,9 +77,9 @@ class Periodic
): string|float {
$rate = Functions::flattenSingleValue($rate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$payment = Functions::flattenSingleValue($payment) ?? 0.0;
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$rate = CashFlowValidations::validateRate($rate);
@@ -122,8 +122,8 @@ class Periodic
$rate = Functions::flattenSingleValue($rate);
$payment = Functions::flattenSingleValue($payment);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$rate = CashFlowValidations::validateRate($rate);
@@ -150,7 +150,7 @@ class Periodic
float $presentValue,
int $type
): float {
if ($rate !== null && $rate != 0) {
if ($rate != 0) {
return -$presentValue
* (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1)
/ $rate;

View File

@@ -41,7 +41,7 @@ class Cumulative
$presentValue = Functions::flattenSingleValue($presentValue);
$start = Functions::flattenSingleValue($start);
$end = Functions::flattenSingleValue($end);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$rate = CashFlowValidations::validateRate($rate);
@@ -104,7 +104,7 @@ class Cumulative
$presentValue = Functions::flattenSingleValue($presentValue);
$start = Functions::flattenSingleValue($start);
$end = Functions::flattenSingleValue($end);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$rate = CashFlowValidations::validateRate($rate);

View File

@@ -43,7 +43,7 @@ class Interest
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
@@ -160,9 +160,9 @@ class Interest
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$payment = Functions::flattenSingleValue($payment);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
$guess = Functions::flattenSingleValue($guess) ?? 0.1;
try {
$numberOfPeriods = CashFlowValidations::validateFloat($numberOfPeriods);

View File

@@ -27,14 +27,14 @@ class Payments
mixed $interestRate,
mixed $numberOfPeriods,
mixed $presentValue,
mixed $futureValue = 0,
mixed $futureValue = 0.0,
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
): string|float {
$interestRate = Functions::flattenSingleValue($interestRate);
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$interestRate = CashFlowValidations::validateRate($interestRate);
@@ -83,7 +83,7 @@ class Payments
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
$presentValue = Functions::flattenSingleValue($presentValue);
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
try {
$interestRate = CashFlowValidations::validateRate($interestRate);

View File

@@ -77,13 +77,13 @@ class Single
*
* Calculates the interest rate required for an investment to grow to a specified future value .
*
* @param array|float $periods The number of periods over which the investment is made
* @param array|float $presentValue Present Value
* @param array|float $futureValue Future Value
* @param mixed $periods The number of periods over which the investment is made, expect array|float
* @param mixed $presentValue Present Value, expect array|float
* @param mixed $futureValue Future Value, expect array|float
*
* @return float|string Result, or a string containing an error
*/
public static function interestRate(array|float $periods = 0.0, array|float $presentValue = 0.0, array|float $futureValue = 0.0): string|float
public static function interestRate(mixed $periods = 0.0, mixed $presentValue = 0.0, mixed $futureValue = 0.0): string|float
{
$periods = Functions::flattenSingleValue($periods);
$presentValue = Functions::flattenSingleValue($presentValue);

View File

@@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class NonPeriodic
{
@@ -23,16 +24,17 @@ class NonPeriodic
* Excel Function:
* =XIRR(values,dates,guess)
*
* @param float[] $values A series of cash flow payments
* @param array<int, float|int|numeric-string> $values A series of cash flow payments, expecting float[]
* The series of values must contain at least one positive value & one negative value
* @param mixed[] $dates A series of payment dates
* @param array<int, float|int|numeric-string> $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 mixed $guess An optional guess at the expected answer
*/
public static function rate(array $values, array $dates, mixed $guess = self::DEFAULT_GUESS): float|string
public static function rate(mixed $values, $dates, mixed $guess = self::DEFAULT_GUESS): float|string
{
$rslt = self::xirrPart1($values, $dates);
/** @var array<int, float|int|numeric-string> $dates */
if ($rslt !== '') {
return $rslt;
}
@@ -91,6 +93,7 @@ class NonPeriodic
$x2 += 0.5;
}
if ($found) {
/** @var array<int, float|int|numeric-string> $dates */
return self::xirrBisection($values, $dates, $x1, $x2);
}
@@ -106,18 +109,18 @@ class NonPeriodic
* Excel Function:
* =XNPV(rate,values,dates)
*
* @param array|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.
* @param mixed $rate the discount rate to apply to the cash flows, expect array|float
* @param array<int,float|int|numeric-string> $values A series of cash flows that corresponds to a schedule of payments in dates, expecting float[].
* 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.
* @param mixed $dates A schedule of payment dates that corresponds to the cash flow payments, expecting mixed[].
* 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.
*/
public static function presentValue(array|float $rate, array $values, array $dates): float|string
public static function presentValue(mixed $rate, mixed $values, mixed $dates): float|string
{
return self::xnpvOrdered($rate, $values, $dates, true);
}
@@ -127,9 +130,12 @@ class NonPeriodic
return $neg && $pos;
}
/** @param array<int, float|int|numeric-string> $values */
private static function xirrPart1(mixed &$values, mixed &$dates): string
{
$values = Functions::flattenArray($values);
/** @var array<int, float|int|numeric-string> */
$temp = Functions::flattenArray($values);
$values = $temp;
$dates = Functions::flattenArray($dates);
$valuesIsArray = count($values) > 1;
$datesIsArray = count($dates) > 1;
@@ -152,6 +158,7 @@ class NonPeriodic
return self::xirrPart2($values);
}
/** @param array<int, float|int|numeric-string> $values */
private static function xirrPart2(array &$values): string
{
$valCount = count($values);
@@ -159,7 +166,7 @@ class NonPeriodic
$foundneg = false;
for ($i = 0; $i < $valCount; ++$i) {
$fld = $values[$i];
if (!is_numeric($fld)) {
if (!is_numeric($fld)) { //* @phpstan-ignore-line
return ExcelError::VALUE();
} elseif ($fld > 0) {
$foundpos = true;
@@ -174,6 +181,10 @@ class NonPeriodic
return '';
}
/**
* @param array<int, float|int|numeric-string> $values
* @param array<int, float|int|numeric-string> $dates
*/
private static function xirrPart3(array $values, array $dates, float $x1, float $x2): float|string
{
$f = self::xnpvOrdered($x1, $values, $dates, false);
@@ -203,6 +214,10 @@ class NonPeriodic
return $rslt;
}
/**
* @param array<int, float|int|numeric-string> $values
* @param array<int, float|int|numeric-string> $dates
*/
private static function xirrBisection(array $values, array $dates, float $x1, float $x2): string|float
{
$rslt = ExcelError::NAN();
@@ -239,9 +254,13 @@ class NonPeriodic
return $rslt;
}
/** @param array<int,float|int|numeric-string> $values> */
private static function xnpvOrdered(mixed $rate, mixed $values, mixed $dates, bool $ordered = true, bool $capAtNegative1 = false): float|string
{
$rate = Functions::flattenSingleValue($rate);
if (!is_numeric($rate)) {
return ExcelError::VALUE();
}
$values = Functions::flattenArray($values);
$dates = Functions::flattenArray($dates);
$valCount = count($values);
@@ -273,10 +292,10 @@ class NonPeriodic
$dif = Functions::scalar(DateTimeExcel\Difference::interval($date0, $datei, 'd'));
}
if (!is_numeric($dif)) {
return $dif;
return StringHelper::convertToString($dif);
}
if ($rate <= -1.0) {
$xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
$xnpv += -abs($values[$i] + 0) / (-1 - $rate) ** ($dif / 365);
} else {
$xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
}
@@ -285,6 +304,10 @@ class NonPeriodic
return is_finite($xnpv) ? $xnpv : ExcelError::VALUE();
}
/**
* @param mixed[] $values
* @param mixed[] $dates
*/
private static function validateXnpv(mixed $rate, array $values, array $dates): void
{
if (!is_numeric($rate)) {
@@ -294,7 +317,7 @@ class NonPeriodic
if ($valCount != count($dates)) {
throw new Exception(ExcelError::NAN());
}
if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
if (count($values) > 1 && ((min($values) > 0) || (max($values) < 0))) {
throw new Exception(ExcelError::NAN());
}
}

View File

@@ -36,6 +36,9 @@ class Periodic
}
$values = Functions::flattenArray($values);
$guess = Functions::flattenSingleValue($guess);
if (!is_numeric($guess)) {
return ExcelError::VALUE();
}
// create an initial range, with a root somewhere between 0 and guess
$x1 = 0.0;
@@ -103,7 +106,9 @@ class Periodic
return ExcelError::DIV0();
}
$values = Functions::flattenArray($values);
/** @var float */
$financeRate = Functions::flattenSingleValue($financeRate);
/** @var float */
$reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
$n = count($values);
@@ -112,6 +117,7 @@ class Periodic
$npvPos = $npvNeg = 0.0;
foreach ($values as $i => $v) {
/** @var float $v */
if ($v >= 0) {
$npvPos += $v / $rr ** $i;
} else {
@@ -134,12 +140,13 @@ class Periodic
*
* Returns the Net Present Value of a cash flow series given a discount rate.
*
* @param array $args
* @param array<mixed> $args
*/
public static function presentValue(mixed $rate, ...$args): int|float
{
$returnValue = 0;
/** @var float */
$rate = Functions::flattenSingleValue($rate);
$aArgs = Functions::flattenArray($args);

View File

@@ -49,9 +49,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -110,9 +108,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -179,9 +175,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -244,9 +238,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -296,9 +288,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -355,9 +345,7 @@ class Coupons
$settlement = Functions::flattenSingleValue($settlement);
$maturity = Functions::flattenSingleValue($maturity);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = FinancialValidations::validateSettlementDate($settlement);

View File

@@ -25,7 +25,7 @@ class Dollar
* If you omit precision, it is assumed to be 2
* Or can be an array of precision values
*
* @return array|string If an array of values is passed for either of the arguments, then the returned result
* @return array<mixed>|string If an array of values is passed for either of the arguments, then the returned result
* will also be an array with matching dimensions
*/
public static function format(mixed $number, mixed $precision = 2)
@@ -47,6 +47,8 @@ class Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*
* @return array<mixed>|float|string
*/
public static function decimal(mixed $fractionalDollar = null, mixed $fraction = 0): array|string|float
{
@@ -93,6 +95,8 @@ class Dollar
* Or can be an array of values
* @param mixed $fraction Fraction
* Or can be an array of values
*
* @return array<mixed>|float|string
*/
public static function fractional(mixed $decimalDollar = null, mixed $fraction = 0): array|string|float
{

View File

@@ -14,7 +14,7 @@ class Helpers
*
* 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 mixed $year The year against which we're testing, expect int|string
* @param int|string $basis The type of day count:
* 0 or omitted US (NASD) 360
* 1 Actual (365 or 366 in a leap year)
@@ -24,8 +24,11 @@ class Helpers
*
* @return int|string Result, or a string containing an error
*/
public static function daysPerYear($year, $basis = 0): string|int
public static function daysPerYear(mixed $year, $basis = 0): string|int
{
if (!is_int($year) && !is_string($year)) {
return ExcelError::VALUE();
}
if (!is_numeric($basis)) {
return ExcelError::NAN();
}

View File

@@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\YearFrac;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class AccruedInterest
{
@@ -59,12 +60,8 @@ class AccruedInterest
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$frequency = ($frequency === null)
? FinancialConstants::FREQUENCY_ANNUAL
: Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$frequency = Functions::flattenSingleValue($frequency) ?? FinancialConstants::FREQUENCY_ANNUAL;
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$issue = SecurityValidations::validateIssueDate($issue);
@@ -81,12 +78,12 @@ class AccruedInterest
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
return StringHelper::convertToString($daysBetweenIssueAndSettlement);
}
$daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis));
if (!is_numeric($daysBetweenFirstInterestAndSettlement)) {
// return date error
return $daysBetweenFirstInterestAndSettlement;
return StringHelper::convertToString($daysBetweenFirstInterestAndSettlement);
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;
@@ -125,9 +122,7 @@ class AccruedInterest
$settlement = Functions::flattenSingleValue($settlement);
$rate = Functions::flattenSingleValue($rate);
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$issue = SecurityValidations::validateIssueDate($issue);
@@ -143,7 +138,7 @@ class AccruedInterest
$daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
return StringHelper::convertToString($daysBetweenIssueAndSettlement);
}
return $parValue * $rate * $daysBetweenIssueAndSettlement;

View File

@@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Price
{
@@ -52,9 +53,7 @@ class Price
$yield = Functions::flattenSingleValue($yield);
$redemption = Functions::flattenSingleValue($redemption);
$frequency = Functions::flattenSingleValue($frequency);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -119,9 +118,7 @@ class Price
$maturity = Functions::flattenSingleValue($maturity);
$discount = Functions::flattenSingleValue($discount);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -137,7 +134,7 @@ class Price
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity);
@@ -178,9 +175,7 @@ class Price
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$yield = Functions::flattenSingleValue($yield);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -201,19 +196,19 @@ class Price
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
return StringHelper::convertToString($daysBetweenIssueAndSettlement);
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
return StringHelper::convertToString($daysBetweenIssueAndMaturity);
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
@@ -254,9 +249,7 @@ class Price
$maturity = Functions::flattenSingleValue($maturity);
$investment = Functions::flattenSingleValue($investment);
$discount = Functions::flattenSingleValue($discount);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -275,7 +268,7 @@ class Price
$daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis);
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return Functions::scalar($daysBetweenSettlementAndMaturity);
return StringHelper::convertToString(Functions::scalar($daysBetweenSettlementAndMaturity));
}
return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity));

View File

@@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Rates
{
@@ -43,9 +44,7 @@ class Rates
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -65,7 +64,7 @@ class Rates
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity;
@@ -104,9 +103,7 @@ class Rates
$maturity = Functions::flattenSingleValue($maturity);
$investment = Functions::flattenSingleValue($investment);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -126,7 +123,7 @@ class Rates
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity);

View File

@@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
use PhpOffice\PhpSpreadsheet\Calculation\Financial\Helpers;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Yields
{
@@ -42,9 +43,7 @@ class Yields
$maturity = Functions::flattenSingleValue($maturity);
$price = Functions::flattenSingleValue($price);
$redemption = Functions::flattenSingleValue($redemption);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -64,7 +63,7 @@ class Yields
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;
@@ -106,9 +105,7 @@ class Yields
$issue = Functions::flattenSingleValue($issue);
$rate = Functions::flattenSingleValue($rate);
$price = Functions::flattenSingleValue($price);
$basis = ($basis === null)
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
: Functions::flattenSingleValue($basis);
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
try {
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -129,19 +126,19 @@ class Yields
$daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis));
if (!is_numeric($daysBetweenIssueAndSettlement)) {
// return date error
return $daysBetweenIssueAndSettlement;
return StringHelper::convertToString($daysBetweenIssueAndSettlement);
}
$daysBetweenIssueAndSettlement *= $daysPerYear;
$daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis));
if (!is_numeric($daysBetweenIssueAndMaturity)) {
// return date error
return $daysBetweenIssueAndMaturity;
return StringHelper::convertToString($daysBetweenIssueAndMaturity);
}
$daysBetweenIssueAndMaturity *= $daysPerYear;
$daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis));
if (!is_numeric($daysBetweenSettlementAndMaturity)) {
// return date error
return $daysBetweenSettlementAndMaturity;
return StringHelper::convertToString($daysBetweenSettlementAndMaturity);
}
$daysBetweenSettlementAndMaturity *= $daysPerYear;

View File

@@ -213,7 +213,7 @@ class FormulaParser
// scientific notation check
if (str_contains(self::OPERATORS_SN, $this->formula[$index])) {
if (strlen($value) > 1) {
if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) {
if (preg_match('/^[1-9]{1}(\.\d+)?E{1}$/', $this->formula[$index]) != 0) {
$value .= $this->formula[$index];
++$index;

View File

@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Functions
{
@@ -130,16 +131,22 @@ class Functions
public static function isMatrixValue(mixed $idx): bool
{
$idx = StringHelper::convertToString($idx);
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
}
public static function isValue(mixed $idx): bool
{
$idx = StringHelper::convertToString($idx);
return substr_count($idx, '.') === 0;
}
public static function isCellValue(mixed $idx): bool
{
$idx = StringHelper::convertToString($idx);
return substr_count($idx, '.') > 1;
}
@@ -154,7 +161,8 @@ class Functions
$condition = self::operandSpecialHandling($condition);
if (is_bool($condition)) {
return '=' . ($condition ? 'TRUE' : 'FALSE');
} elseif (!is_numeric($condition)) {
}
if (!is_numeric($condition)) {
if ($condition !== '""') { // Not an empty string
// Escape any quotes in the string value
$condition = (string) preg_replace('/"/ui', '""', $condition);
@@ -162,27 +170,32 @@ class Functions
$condition = Calculation::wrapResult(strtoupper($condition));
}
return str_replace('""""', '""', '=' . $condition);
return str_replace('""""', '""', '=' . StringHelper::convertToString($condition));
}
$operator = $operand = '';
if (1 === preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches)) {
[, $operator, $operand] = $matches;
}
preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
[, $operator, $operand] = $matches;
$operand = self::operandSpecialHandling($operand);
$operand = (string) self::operandSpecialHandling($operand);
if (is_numeric(trim($operand, '"'))) {
$operand = trim($operand, '"');
} elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') {
$operand = str_replace('"', '""', $operand);
$operand = Calculation::wrapResult(strtoupper($operand));
$operand = StringHelper::convertToString($operand);
}
return str_replace('""""', '""', $operator . $operand);
}
private static function operandSpecialHandling(mixed $operand): mixed
private static function operandSpecialHandling(mixed $operand): bool|float|int|string
{
if (is_numeric($operand) || is_bool($operand)) {
return $operand;
} elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) {
}
$operand = StringHelper::convertToString($operand);
if (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) {
return strtoupper($operand);
}
@@ -204,7 +217,7 @@ class Functions
*
* @param mixed $array Array to be flattened
*
* @return array Flattened array
* @return array<mixed> Flattened array
*/
public static function flattenArray(mixed $array): array
{
@@ -228,6 +241,32 @@ class Functions
return $flattened;
}
/**
* Convert a multi-dimensional array to a simple 1-dimensional array.
* Same as above but argument is specified in ... format.
*
* @param mixed $array Array to be flattened
*
* @return array<mixed> Flattened array
*/
public static function flattenArray2(mixed ...$array): array
{
$flattened = [];
$stack = array_values($array);
while (!empty($stack)) {
$value = array_shift($stack);
if (is_array($value)) {
array_unshift($stack, ...array_values($value));
} else {
$flattened[] = $value;
}
}
return $flattened;
}
public static function scalar(mixed $value): mixed
{
if (!is_array($value)) {
@@ -246,7 +285,7 @@ class Functions
*
* @param array|mixed $array Array to be flattened
*
* @return array Flattened array
* @return array<mixed> Flattened array
*/
public static function flattenArrayIndexed($array): array
{
@@ -310,7 +349,7 @@ class Functions
public static function trimTrailingRange(string $coordinate): string
{
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate);
return (string) preg_replace('/:[\w\$]+$/', '', $coordinate);
}
public static function trimSheetFromCellReference(string $coordinate): string

View File

@@ -15,7 +15,7 @@ class ErrorValue
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isErr(mixed $value = ''): array|bool
@@ -33,7 +33,7 @@ class ErrorValue
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isError(mixed $value = '', bool $tryNotImplemented = false): array|bool
@@ -58,7 +58,7 @@ class ErrorValue
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNa(mixed $value = ''): array|bool

View File

@@ -39,6 +39,8 @@ class ExcelError
* ERROR_TYPE.
*
* @param mixed $value Value to check
*
* @return array<mixed>|int|string
*/
public static function type(mixed $value = ''): array|int|string
{
@@ -152,4 +154,14 @@ class ExcelError
{
return self::ERROR_CODES['calculation'];
}
/**
* SPILL.
*
* @return string #SPILL!
*/
public static function SPILL(): string
{
return self::ERROR_CODES['spill'];
}
}

View File

@@ -7,7 +7,9 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Value
@@ -20,7 +22,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isBlank(mixed $value = null): array|bool
@@ -39,13 +41,14 @@ class Value
*/
public static function isRef(mixed $value, ?Cell $cell = null): bool
{
if ($cell === null || $value === $cell->getCoordinate()) {
if ($cell === null) {
return false;
}
$value = StringHelper::convertToString($value);
$cellValue = Functions::trimTrailingRange($value);
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) {
[$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true);
[$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true, true);
if (!empty($worksheet) && $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) {
return false;
}
@@ -68,7 +71,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isEven(mixed $value = null): array|string|bool
@@ -79,11 +82,12 @@ class Value
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
}
if (!is_numeric($value)) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) === 0;
return ((int) fmod($value + 0, 2)) === 0;
}
/**
@@ -92,7 +96,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool|string If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isOdd(mixed $value = null): array|string|bool
@@ -103,11 +107,12 @@ class Value
if ($value === null) {
return ExcelError::NAME();
} elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
}
if (!is_numeric($value)) {
return ExcelError::VALUE();
}
return ((int) fmod($value, 2)) !== 0;
return ((int) fmod($value + 0, 2)) !== 0;
}
/**
@@ -116,7 +121,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNumber(mixed $value = null): array|bool
@@ -138,7 +143,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isLogical(mixed $value = null): array|bool
@@ -156,7 +161,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isText(mixed $value = null): array|bool
@@ -174,7 +179,7 @@ class Value
* @param mixed $value Value to check
* Or can be an array of values
*
* @return array|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* @return array<mixed>|bool If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function isNonText(mixed $value = null): array|bool
@@ -191,14 +196,17 @@ class Value
*
* @param mixed $cellReference The cell to check
* @param ?Cell $cell The current cell (containing this formula)
*
* @return array<mixed>|bool|string
*/
public static function isFormula(mixed $cellReference = '', ?Cell $cell = null): array|bool|string
{
if ($cell === null) {
return ExcelError::REF();
}
$cellReference = StringHelper::convertToString($cellReference);
$fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell);
$fullCellReference = Functions::expandDefinedName($cellReference, $cell);
if (str_contains($cellReference, '!')) {
$cellReference = Functions::trimSheetFromCellReference($cellReference);
@@ -210,16 +218,24 @@ class Value
$fullCellReference = Functions::trimTrailingRange($fullCellReference);
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches);
$fullCellReference = $matches[6] . $matches[7];
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
$worksheetName = '';
if (1 == preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches)) {
$fullCellReference = $matches[6] . $matches[7];
$worksheetName = str_replace("''", "'", trim($matches[2], "'"));
}
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
if ($worksheet === null) {
return ExcelError::REF();
}
return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF();
try {
return $worksheet->getCell($fullCellReference)->isFormula();
} catch (SpreadsheetException) {
return true;
}
}
/**
@@ -243,21 +259,14 @@ class Value
while (is_array($value)) {
$value = array_shift($value);
}
switch (gettype($value)) {
case 'double':
case 'float':
case 'integer':
return $value;
case 'boolean':
return (int) $value;
case 'string':
// Errors
if (($value !== '') && ($value[0] == '#')) {
return $value;
}
break;
if (is_float($value) || is_int($value)) {
return $value;
}
if (is_bool($value)) {
return (int) $value;
}
if (is_string($value) && substr($value, 0, 1) === '#') {
return $value;
}
return 0;
@@ -281,7 +290,7 @@ class Value
public static function type($value = null): int
{
$value = Functions::flattenArrayIndexed($value);
if (is_array($value) && (count($value) > 1)) {
if (count($value) > 1) {
end($value);
$a = key($value);
// Range of cells is an error

View File

@@ -4,7 +4,11 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\Internal;
class MakeMatrix
{
/** @param array $args */
/**
* @param mixed[] $args
*
* @return mixed[]
*/
public static function make(...$args): array
{
return $args;

View File

@@ -6,10 +6,10 @@ class WildcardMatch
{
private const SEARCH_SET = [
'~~', // convert double tilde to unprintable value
'~\\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp)
'\\*', // convert backslash asterisk to .* (matches string of any length in regexp)
'~\\?', // convert tilde backslash question to [?] (matches literal question mark in regexp)
'\\?', // convert backslash question to . (matches one character in regexp)
'~\*', // convert tilde backslash asterisk to [*] (matches literal asterisk in regexp)
'\*', // convert backslash asterisk to .* (matches string of any length in regexp)
'~\?', // convert tilde backslash question to [?] (matches literal question mark in regexp)
'\?', // convert backslash question to . (matches one character in regexp)
"\x1c", // convert original double tilde to single tilde
];

View File

@@ -106,7 +106,7 @@ class Operations
* @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
* Or can be an array of values
*
* @return array|bool|string the boolean inverse of the argument
* @return array<mixed>|bool|string the boolean inverse of the argument
* If an array of values is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -130,6 +130,10 @@ class Operations
return !$logical;
}
/**
* @param mixed[] $args
* @param callable(int, int): bool $func
*/
private static function countTrueValues(array $args, callable $func): bool|string
{
$trueValueCount = 0;

View File

@@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Address
{
@@ -44,7 +45,7 @@ class Address
* @param mixed $sheetName Optional Name of worksheet to use
* Or can be an array of values
*
* @return array|string If an array of values is passed as the $testValue argument, then the returned result will also be
* @return mixed[]|string If an array of values is passed as the $testValue argument, then the returned result will also be
* an array with the same dimensions
*/
public static function cell(mixed $row, mixed $column, mixed $relativity = 1, mixed $referenceStyle = true, mixed $sheetName = ''): array|string
@@ -63,14 +64,16 @@ class Address
);
}
$relativity = $relativity ?? 1;
$relativity = ($relativity === null) ? 1 : (int) StringHelper::convertToString($relativity);
$referenceStyle = $referenceStyle ?? true;
$row = (int) StringHelper::convertToString($row);
$column = (int) StringHelper::convertToString($column);
if (($row < 1) || ($column < 1)) {
return ExcelError::VALUE();
}
$sheetName = self::sheetName($sheetName);
$sheetName = self::sheetName(StringHelper::convertToString($sheetName));
if (is_int($referenceStyle)) {
$referenceStyle = (bool) $referenceStyle;

View File

@@ -30,7 +30,7 @@ class ExcelMatch
* @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below.
* If match_type is 1 or -1, the list has to be ordered.
*
* @return array|float|int|string The relative position of the found item
* @return array<mixed>|float|int|string The relative position of the found item
*/
public static function MATCH(mixed $lookupValue, mixed $lookupArray, mixed $matchType = self::MATCHTYPE_LARGEST_VALUE): array|string|int|float
{
@@ -70,13 +70,14 @@ class ExcelMatch
};
if ($valueKey !== null) {
return ++$valueKey;
return ++$valueKey; //* @phpstan-ignore-line
}
// Unsuccessful in finding a match, return #N/A error value
return ExcelError::NA();
}
/** @param mixed[] $lookupArray */
private static function matchFirstValue(array $lookupArray, mixed $lookupValue): int|string|null
{
if (is_string($lookupValue)) {
@@ -113,6 +114,10 @@ class ExcelMatch
return null;
}
/**
* @param mixed[] $lookupArray
* @param mixed[] $keySet
*/
private static function matchLargestValue(array $lookupArray, mixed $lookupValue, array $keySet): mixed
{
if (is_string($lookupValue)) {
@@ -147,6 +152,7 @@ class ExcelMatch
return null;
}
/** @param mixed[] $lookupArray */
private static function matchSmallestValue(array $lookupArray, mixed $lookupValue): int|string|null
{
$valueKey = null;
@@ -215,6 +221,7 @@ class ExcelMatch
return self::MATCHTYPE_FIRST_VALUE;
}
/** @param mixed[] $lookupArray */
private static function validateLookupArray(array $lookupArray): void
{
// Lookup_array should not be empty
@@ -224,6 +231,11 @@ class ExcelMatch
}
}
/**
* @param mixed[] $lookupArray
*
* @return mixed[]
*/
private static function prepareLookupArray(array $lookupArray, mixed $matchType): array
{
// Lookup_array should contain only number, text, or logical values, or empty (null) cells

View File

@@ -6,8 +6,12 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
class Filter
{
public static function filter(array $lookupArray, mixed $matchArray, mixed $ifEmpty = null): mixed
public static function filter(mixed $lookupArray, mixed $matchArray, mixed $ifEmpty = null): mixed
{
if (!is_array($lookupArray)) {
return ExcelError::VALUE();
}
/** @var mixed[] $lookupArray */
if (!is_array($matchArray)) {
return ExcelError::VALUE();
}
@@ -21,10 +25,17 @@ class Filter
if (empty($result)) {
return $ifEmpty ?? ExcelError::CALC();
}
/** @var callable(mixed): mixed */
$func = 'array_values';
return array_values(array_map('array_values', $result));
return array_values(array_map($func, $result));
}
/**
* @param mixed[] $sortArray
*
* @return mixed[]
*/
private static function enumerateArrayKeys(array $sortArray): array
{
array_walk(
@@ -39,17 +50,29 @@ class Filter
return array_values($sortArray);
}
/**
* @param mixed[] $lookupArray
* @param mixed[] $matchArray
*
* @return mixed[]
*/
private static function filterByRow(array $lookupArray, array $matchArray): array
{
$matchArray = array_values(array_column($matchArray, 0));
$matchArray = array_values(array_column($matchArray, 0)); // @phpstan-ignore-line
return array_filter(
array_values($lookupArray),
fn ($index): bool => (bool) $matchArray[$index],
fn ($index): bool => (bool) ($matchArray[$index] ?? null),
ARRAY_FILTER_USE_KEY
);
}
/**
* @param mixed[] $lookupArray
* @param mixed[] $matchArray
*
* @return mixed[]
*/
private static function filterByColumn(array $lookupArray, array $matchArray): array
{
$lookupArray = Matrix::transpose($lookupArray);
@@ -57,7 +80,7 @@ class Filter
if (count($matchArray) === 1) {
$matchArray = array_pop($matchArray);
}
/** @var mixed[] $matchArray */
array_walk(
$matchArray,
function (&$value): void {

View File

@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Formula
{
@@ -20,13 +21,15 @@ class Formula
return ExcelError::REF();
}
preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
$cellReference = $matches[6] . $matches[7];
$worksheetName = trim($matches[3], "'");
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
$worksheet = null;
$cellReference = StringHelper::convertToString($cellReference);
if (1 === preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches)) {
$cellReference = $matches[6] . $matches[7];
$worksheetName = trim($matches[3], "'");
$worksheet = (!empty($worksheetName))
? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheetName)
: $cell->getWorksheet();
}
if (
$worksheet === null
@@ -36,6 +39,6 @@ class Formula
return ExcelError::NA();
}
return $worksheet->getCell($cellReference)->getValue();
return $worksheet->getCell($cellReference)->getValueString();
}
}

View File

@@ -18,14 +18,14 @@ class HLookup extends LookupBase
* in the same column based on the index_number.
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $indexNumber The row number in table_array from which the matching value must be returned.
* @param mixed[][] $lookupArray The range of cells being searched
* @param array<mixed>|float|int|string $indexNumber The row number in table_array from which the matching value must be returned.
* The first row is 1.
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
*
* @return mixed The value of the found cell
*/
public static function lookup(mixed $lookupValue, mixed $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed
public static function lookup(mixed $lookupValue, $lookupArray, $indexNumber, mixed $notExactMatch = true): mixed
{
if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
@@ -49,6 +49,7 @@ class HLookup extends LookupBase
$firstkey = $f[0] - 1;
$returnColumn = $firstkey + $indexNumber;
/** @var mixed[][] $lookupArray */
$firstColumn = array_shift($f) ?? 1;
$rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
@@ -62,17 +63,20 @@ class HLookup extends LookupBase
/**
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed[][] $lookupArray
* @param int|string $column
*/
private static function hLookupSearch(mixed $lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{
$lookupLower = StringHelper::strToLower((string) $lookupValue);
$lookupLower = StringHelper::strToLower(StringHelper::convertToString($lookupValue));
$rowNumber = null;
foreach ($lookupArray[$column] as $rowKey => $rowData) {
// break if we have passed possible keys
/** @var string $rowKey */
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData);
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData);
/** @var scalar $rowData */
$cellDataLower = StringHelper::strToLower((string) $rowData);
if (
@@ -96,6 +100,11 @@ class HLookup extends LookupBase
return $rowNumber;
}
/**
* @param mixed[] $lookupArray
*
* @return mixed[]
*/
private static function convertLiteralArray(array $lookupArray): array
{
if (array_key_exists(0, $lookupArray)) {

View File

@@ -35,6 +35,7 @@ class Helpers
}
}
/** @return array{string, ?string, string} */
public static function extractCellAddresses(string $cellAddress, bool $a1, Worksheet $sheet, string $sheetName = '', ?int $baseRow = null, ?int $baseCol = null): array
{
$cellAddress1 = $cellAddress;
@@ -57,12 +58,12 @@ class Helpers
return [$cellAddress1, $cellAddress2, $cellAddress];
}
/** @return array{string, ?Worksheet, string} */
public static function extractWorksheet(string $cellAddress, Cell $cell): array
{
$sheetName = '';
if (str_contains($cellAddress, '!')) {
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true);
}
$worksheet = ($sheetName !== '')

View File

@@ -5,6 +5,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Hyperlink
{
@@ -22,18 +23,23 @@ class Hyperlink
*/
public static function set(mixed $linkURL = '', mixed $displayName = null, ?Cell $cell = null): string
{
$linkURL = ($linkURL === null) ? '' : Functions::flattenSingleValue($linkURL);
$linkURL = ($linkURL === null) ? '' : StringHelper::convertToString(Functions::flattenSingleValue($linkURL));
$displayName = ($displayName === null) ? '' : Functions::flattenSingleValue($displayName);
if ((!is_object($cell)) || (trim($linkURL) == '')) {
return ExcelError::REF();
}
if ((is_object($displayName)) || trim($displayName) == '') {
if (is_object($displayName)) {
$displayName = $linkURL;
}
$displayName = StringHelper::convertToString($displayName);
if (trim($displayName) === '') {
$displayName = $linkURL;
}
$cell->getHyperlink()->setUrl($linkURL);
$cell->getHyperlink()
->setUrl($linkURL);
$cell->getHyperlink()->setTooltip($displayName);
return $displayName;

View File

@@ -34,6 +34,8 @@ class Indirect
/**
* Convert cellAddress to string, verify not null string.
*
* @param null|mixed[]|string $cellAddress
*/
private static function validateAddress(array|string|null $cellAddress): string
{
@@ -54,12 +56,12 @@ class Indirect
* Excel Function:
* =INDIRECT(cellAddress, bool) where the bool argument is optional
*
* @param array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
* @param mixed[]|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
* @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
* but can be provided as numeric which is cast to bool
* @param Cell $cell The current cell (containing this formula)
*
* @return array|string An array containing a cell or range of cells, or a string on error
* @return mixed[]|string An array containing a cell or range of cells, or a string on error
*/
public static function INDIRECT($cellAddress, mixed $a1fmt, Cell $cell): string|array
{
@@ -99,13 +101,13 @@ class Indirect
/**
* Extract range values.
*
* @return array Array of values in range if range contains more than one element.
* @return mixed[] Array of values in range if range contains more than one element.
* Otherwise, a single value is returned.
*/
private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array
{
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
->extractCellRange($cellAddress, $worksheet, false);
->extractCellRange($cellAddress, $worksheet, false, createCell: true);
}
private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string

View File

@@ -28,6 +28,7 @@ class Lookup
if (!is_array($lookupVector)) {
return ExcelError::NA();
}
/** @var mixed[][] $lookupVector */
$hasResultVector = isset($resultVector);
$lookupRows = self::rowCount($lookupVector);
$lookupColumns = self::columnCount($lookupVector);
@@ -35,16 +36,19 @@ class Lookup
if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
$lookupVector = Matrix::transpose($lookupVector);
$lookupRows = self::rowCount($lookupVector);
/** @var mixed[][] $lookupVector */
$lookupColumns = self::columnCount($lookupVector);
}
$resultVector = self::verifyResultVector($resultVector ?? $lookupVector);
$resultVector = self::verifyResultVector($resultVector ?? $lookupVector); //* @phpstan-ignore-line
if ($lookupRows === 2 && !$hasResultVector) {
$resultVector = array_pop($lookupVector);
$lookupVector = array_shift($lookupVector);
}
/** @var mixed[] $lookupVector */
/** @var mixed[] $resultVector */
if ($lookupColumns !== 2) {
$lookupVector = self::verifyLookupValues($lookupVector, $resultVector);
}
@@ -52,6 +56,12 @@ class Lookup
return VLookup::lookup($lookupValue, $lookupVector, 2);
}
/**
* @param mixed[] $lookupVector
* @param mixed[] $resultVector
*
* @return mixed[]
*/
private static function verifyLookupValues(array $lookupVector, array $resultVector): array
{
foreach ($lookupVector as &$value) {
@@ -77,6 +87,11 @@ class Lookup
return $lookupVector;
}
/**
* @param mixed[][] $resultVector
*
* @return mixed[]
*/
private static function verifyResultVector(array $resultVector): array
{
$resultRows = self::rowCount($resultVector);
@@ -90,11 +105,13 @@ class Lookup
return $resultVector;
}
/** @param mixed[] $dataArray */
private static function rowCount(array $dataArray): int
{
return count($dataArray);
}
/** @param mixed[][] $dataArray */
private static function columnCount(array $dataArray): int
{
$rowKeys = array_keys($dataArray);

View File

@@ -7,15 +7,18 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
abstract class LookupBase
{
protected static function validateLookupArray(mixed $lookup_array): void
protected static function validateLookupArray(mixed $lookupArray): void
{
if (!is_array($lookup_array)) {
if (!is_array($lookupArray)) {
throw new Exception(ExcelError::REF());
}
}
/** @param float|int|string $index_number */
protected static function validateIndexLookup(array $lookup_array, $index_number): int
/**
* @param mixed[] $lookupArray
* @param float|int|string $index_number number >= 1
*/
protected static function validateIndexLookup(array $lookupArray, $index_number): int
{
// index_number must be a number greater than or equal to 1.
// Excel results are inconsistent when index is non-numeric.
@@ -30,8 +33,8 @@ abstract class LookupBase
throw new Exception(ExcelError::VALUE());
}
// index_number must be less than or equal to the number of columns in lookup_array
if (empty($lookup_array)) {
// index_number must be less than or equal to the number of columns in lookupArray
if (empty($lookupArray)) {
throw new Exception(ExcelError::REF());
}

View File

@@ -11,7 +11,7 @@ class LookupRefValidations
public static function validateInt(mixed $value): int
{
if (!is_numeric($value)) {
if (ErrorValue::isError($value)) {
if (is_string($value) && ErrorValue::isError($value)) {
throw new Exception($value);
}

View File

@@ -12,6 +12,8 @@ class Matrix
/**
* Helper function; NOT an implementation of any Excel Function.
*
* @param mixed[] $values
*/
public static function isColumnVector(array $values): bool
{
@@ -20,6 +22,8 @@ class Matrix
/**
* Helper function; NOT an implementation of any Excel Function.
*
* @param mixed[] $values
*/
public static function isRowVector(array $values): bool
{
@@ -30,7 +34,9 @@ class Matrix
/**
* TRANSPOSE.
*
* @param array|mixed $matrixData A matrix of values
* @param mixed $matrixData A matrix of values
*
* @return mixed[]
*/
public static function transpose($matrixData): array
{
@@ -38,8 +44,12 @@ class Matrix
if (!is_array($matrixData)) {
$matrixData = [[$matrixData]];
}
if (!is_array(end($matrixData))) {
$matrixData = [$matrixData];
}
$column = 0;
/** @var mixed[][] $matrixData */
foreach ($matrixData as $matrixRow) {
$row = 0;
foreach ($matrixRow as $matrixCell) {
@@ -82,6 +92,15 @@ class Matrix
$rowNum = $rowNum ?? 0;
$columnNum = $columnNum ?? 0;
if (is_scalar($matrix)) {
if ($rowNum === 0 || $rowNum === 1) {
if ($columnNum === 0 || $columnNum === 1) {
if ($columnNum === 1 || $rowNum === 1) {
return $matrix;
}
}
}
}
try {
$rowNum = LookupRefValidations::validatePositiveInt($rowNum);
@@ -106,7 +125,7 @@ class Matrix
}
$rowKeys = array_keys($matrix);
$columnKeys = @array_keys($matrix[$rowKeys[0]]);
$columnKeys = @array_keys($matrix[$rowKeys[0]]); //* @phpstan-ignore-line
if ($columnNum > count($columnKeys)) {
return ExcelError::REF();
@@ -124,10 +143,15 @@ class Matrix
);
}
$rowNum = $rowKeys[--$rowNum];
/** @var mixed[][] $matrix */
return $matrix[$rowNum][$columnNum];
}
/**
* @param mixed[] $matrix
* @param mixed[] $rowKeys
*/
private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum): mixed
{
if ($rowNum === 0) {

View File

@@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Validations;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Offset
@@ -24,28 +25,32 @@ class Offset
* @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.
* @param int $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
* @param int $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.
* @param ?int $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.
* @param ?int $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
* @return array<mixed>|string An array containing a cell or range of cells, or a string on error
*/
public static function OFFSET(?string $cellAddress = null, mixed $rows = 0, mixed $columns = 0, mixed $height = null, mixed $width = null, ?Cell $cell = null): string|array
public static function OFFSET(?string $cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $cell = null): string|array
{
/** @var int */
$rows = Functions::flattenSingleValue($rows);
/** @var int */
$columns = Functions::flattenSingleValue($columns);
/** @var int */
$height = Functions::flattenSingleValue($height);
/** @var int */
$width = Functions::flattenSingleValue($width);
if ($cellAddress === null || $cellAddress === '') {
@@ -55,6 +60,10 @@ class Offset
if (!is_object($cell)) {
return ExcelError::REF();
}
$sheet = $cell->getParent()?->getParent(); // worksheet
if ($sheet !== null) {
$cellAddress = Validations::definedNameToCoordinate($cellAddress, $sheet);
}
[$cellAddress, $worksheet] = self::extractWorksheet($cellAddress, $cell);
@@ -62,12 +71,11 @@ class Offset
if (strpos($cellAddress, ':')) {
[$startCell, $endCell] = explode(':', $cellAddress);
}
[$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
[$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
[$startCellColumn, $startCellRow] = Coordinate::indexesFromString($startCell);
[, $endCellRow, $endCellColumn] = Coordinate::indexesFromString($endCell);
$startCellRow += $rows;
$startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
$startCellColumn += $columns;
$startCellColumn += $columns - 1;
if (($startCellRow <= 0) || ($startCellColumn < 0)) {
return ExcelError::REF();
@@ -91,20 +99,21 @@ class Offset
return self::extractRequiredCells($worksheet, $cellAddress);
}
/** @return mixed[] */
private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress): array
{
return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
->extractCellRange($cellAddress, $worksheet, false);
}
/** @return array{string, ?Worksheet} */
private static function extractWorksheet(?string $cellAddress, Cell $cell): array
{
$cellAddress = self::assessCellAddress($cellAddress ?? '', $cell);
$sheetName = '';
if (str_contains($cellAddress, '!')) {
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
$sheetName = trim($sheetName, "'");
[$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true);
}
$worksheet = ($sheetName !== '')
@@ -123,7 +132,11 @@ class Offset
return $cellAddress;
}
private static function adjustEndCellColumnForWidth(string $endCellColumn, mixed $width, int $startCellColumn, mixed $columns): int
/**
* @param null|object|scalar $width
* @param scalar $columns
*/
private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns): int
{
$endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
if (($width !== null) && (!is_object($width))) {
@@ -135,7 +148,11 @@ class Offset
return $endCellColumn;
}
private static function adustEndCellRowForHeight(mixed $height, int $startCellRow, mixed $rows, mixed $endCellRow): int
/**
* @param null|object|scalar $height
* @param scalar $rows
*/
private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, int $endCellRow): int
{
if (($height !== null) && (!is_object($height))) {
$endCellRow = $startCellRow + (int) $height - 1;

View File

@@ -3,9 +3,11 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class RowColumnInformation
@@ -13,7 +15,7 @@ class RowColumnInformation
/**
* Test if cellAddress is null or whitespace string.
*
* @param null|array|string $cellAddress A reference to a range of cells
* @param null|mixed[]|string $cellAddress A reference to a range of cells
*/
private static function cellAddressNullOrWhitespace($cellAddress): bool
{
@@ -38,11 +40,11 @@ class RowColumnInformation
* Excel Function:
* =COLUMN([cellAddress])
*
* @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers
* @param null|mixed[]|string $cellAddress A reference to a range of cells for which you want the column numbers
*
* @return int|int[]
* @return int|int[]|string
*/
public static function COLUMN($cellAddress = null, ?Cell $cell = null): int|array
public static function COLUMN($cellAddress = null, ?Cell $cell = null): int|string|array
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return self::cellColumn($cell);
@@ -79,7 +81,11 @@ class RowColumnInformation
$cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress);
return Coordinate::columnIndexFromString($cellAddress);
try {
return Coordinate::columnIndexFromString($cellAddress);
} catch (SpreadsheetException) {
return ExcelError::NAME();
}
}
/**
@@ -90,7 +96,7 @@ class RowColumnInformation
* Excel Function:
* =COLUMNS(cellAddress)
*
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
* @param null|mixed[]|string $cellAddress An array or array formula, or a reference to a range of cells
* for which you want the number of columns
*
* @return int|string The number of columns in cellAddress, or a string if arguments are invalid
@@ -100,6 +106,9 @@ class RowColumnInformation
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return 1;
}
if (is_string($cellAddress) && ErrorValue::isError($cellAddress)) {
return $cellAddress;
}
if (!is_array($cellAddress)) {
return ExcelError::VALUE();
}
@@ -115,9 +124,18 @@ class RowColumnInformation
return $columns;
}
private static function cellRow(?Cell $cell): int
private static function cellRow(?Cell $cell): int|string
{
return ($cell !== null) ? $cell->getRow() : 1;
return ($cell !== null) ? self::convert0ToName($cell->getRow()) : 1;
}
private static function convert0ToName(int|string $result): int|string
{
if (is_int($result) && ($result <= 0 || $result > 1048576)) {
return ExcelError::NAME();
}
return $result;
}
/**
@@ -133,11 +151,11 @@ class RowColumnInformation
* Excel Function:
* =ROW([cellAddress])
*
* @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers
* @param null|mixed[][]|string $cellAddress A reference to a range of cells for which you want the row numbers
*
* @return int|mixed[]
* @return int|mixed[]|string
*/
public static function ROW($cellAddress = null, ?Cell $cell = null): int|array
public static function ROW($cellAddress = null, ?Cell $cell = null): int|string|array
{
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return self::cellRow($cell);
@@ -172,7 +190,7 @@ class RowColumnInformation
}
[$cellAddress] = explode(':', $cellAddress);
return (int) preg_replace('/\D/', '', $cellAddress);
return self::convert0ToName((int) preg_replace('/\D/', '', $cellAddress));
}
/**
@@ -183,7 +201,7 @@ class RowColumnInformation
* Excel Function:
* =ROWS(cellAddress)
*
* @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells
* @param null|mixed[]|string $cellAddress An array or array formula, or a reference to a range of cells
* for which you want the number of rows
*
* @return int|string The number of rows in cellAddress, or a string if arguments are invalid
@@ -193,6 +211,9 @@ class RowColumnInformation
if (self::cellAddressNullOrWhitespace($cellAddress)) {
return 1;
}
if (is_string($cellAddress) && ErrorValue::isError($cellAddress)) {
return $cellAddress;
}
if (!is_array($cellAddress)) {
return ExcelError::VALUE();
}

View File

@@ -36,6 +36,7 @@ class Sort extends LookupRefValidations
return $sortArray;
}
/** @var mixed[][] */
$sortArray = self::enumerateArrayKeys($sortArray);
$byColumn = (bool) $byColumn;
@@ -43,7 +44,7 @@ class Sort extends LookupRefValidations
try {
// If $sortIndex and $sortOrder are scalars, then convert them into arrays
if (is_scalar($sortIndex)) {
if (!is_array($sortIndex)) {
$sortIndex = [$sortIndex];
$sortOrder = is_scalar($sortOrder) ? [$sortOrder] : $sortOrder;
}
@@ -55,7 +56,11 @@ class Sort extends LookupRefValidations
}
// We want a simple, enumrated array of arrays where we can reference column by its index number.
$sortArray = array_values(array_map('array_values', $sortArray));
/** @var callable(mixed): mixed */
$temp = 'array_values';
/** @var array<int> $sortOrder */
$sortArray = array_values(array_map($temp, $sortArray));
/** @var int[] $sortIndex */
return ($byColumn === true)
? self::sortByColumn($sortArray, $sortIndex, $sortOrder)
@@ -104,6 +109,11 @@ class Sort extends LookupRefValidations
return self::processSortBy($sortArray, $sortBy, $sortOrder);
}
/**
* @param mixed[] $sortArray
*
* @return mixed[]
*/
private static function enumerateArrayKeys(array $sortArray): array
{
array_walk(
@@ -133,6 +143,7 @@ class Sort extends LookupRefValidations
$sortOrder = self::validateSortOrder($sortOrder);
}
/** @return mixed[] */
private static function validateSortVector(mixed $sortVector, int $sortArraySize): array
{
if (!is_array($sortVector)) {
@@ -158,6 +169,7 @@ class Sort extends LookupRefValidations
return $sortOrder;
}
/** @param mixed[] $sortIndex */
private static function validateArrayArgumentsForSort(array &$sortIndex, mixed &$sortOrder, int $sortArraySize): void
{
// It doesn't matter if they're row or column vectors, it works either way
@@ -184,6 +196,11 @@ class Sort extends LookupRefValidations
}
}
/**
* @param mixed[] $sortVector
*
* @return mixed[]
*/
private static function prepareSortVectorValues(array $sortVector): array
{
// Strings should be sorted case-insensitive; with booleans converted to locale-strings
@@ -202,14 +219,19 @@ class Sort extends LookupRefValidations
}
/**
* @param array[] $sortIndex
* @param mixed[] $sortArray
* @param mixed[] $sortIndex
* @param int[] $sortOrder
*
* @return mixed[]
*/
private static function processSortBy(array $sortArray, array $sortIndex, array $sortOrder): array
{
$sortArguments = [];
/** @var mixed[] */
$sortData = [];
foreach ($sortIndex as $index => $sortValues) {
/** @var mixed[] $sortValues */
$sortData[] = $sortValues;
$sortArguments[] = self::prepareSortVectorValues($sortValues);
$sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC;
@@ -221,8 +243,11 @@ class Sort extends LookupRefValidations
}
/**
* @param mixed[] $sortArray
* @param int[] $sortIndex
* @param int[] $sortOrder
*
* @return mixed[]
*/
private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array
{
@@ -232,8 +257,11 @@ class Sort extends LookupRefValidations
}
/**
* @param mixed[] $sortArray
* @param int[] $sortIndex
* @param int[] $sortOrder
*
* @return mixed[]
*/
private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array
{
@@ -244,8 +272,11 @@ class Sort extends LookupRefValidations
}
/**
* @param mixed[] $sortArray
* @param int[] $sortIndex
* @param int[] $sortOrder
*
* @return mixed[]
*/
private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array
{
@@ -263,6 +294,12 @@ class Sort extends LookupRefValidations
return $sortData;
}
/**
* @param mixed[] $sortData
* @param mixed[] $sortArguments
*
* @return mixed[]
*/
private static function executeVectorSortQuery(array $sortData, array $sortArguments): array
{
$sortData = Matrix::transpose($sortData);
@@ -287,6 +324,12 @@ class Sort extends LookupRefValidations
return $sortedData;
}
/**
* @param mixed[] $sortArray
* @param mixed[] $sortVector
*
* @return mixed[]
*/
private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array
{
// Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays

View File

@@ -33,17 +33,31 @@ class Unique
: self::uniqueByRow($lookupVector, $exactlyOnce);
}
/** @param mixed[] $lookupVector */
private static function uniqueByRow(array $lookupVector, bool $exactlyOnce): mixed
{
// When not $byColumn, we count whole rows or values, not individual values
// so implode each row into a single string value
array_walk(
$lookupVector,
//* @phpstan-ignore-next-line
function (array &$value): void {
$value = implode(chr(0x00), $value);
$valuex = '';
$separator = '';
$numericIndicator = "\x01";
foreach ($value as $cellValue) {
/** @var scalar $cellValue */
$valuex .= $separator . $cellValue;
$separator = "\x00";
if (is_int($cellValue) || is_float($cellValue)) {
$valuex .= $numericIndicator;
}
}
$value = $valuex;
}
);
/** @var string[] $lookupVector */
$result = self::countValuesCaseInsensitive($lookupVector);
if ($exactlyOnce === true) {
@@ -60,15 +74,24 @@ class Unique
array_walk(
$result,
function (string &$value): void {
$value = explode(chr(0x00), $value);
$value = explode("\x00", $value);
foreach ($value as &$stringValue) {
if (str_ends_with($stringValue, "\x01")) {
// x01 should only end a string which is otherwise a float or int,
// so phpstan is technically correct but what it fears should not happen.
$stringValue = 0 + substr($stringValue, 0, -1); //@phpstan-ignore-line
}
}
}
);
return (count($result) === 1) ? array_pop($result) : $result;
}
/** @param mixed[] $lookupVector */
private static function uniqueByColumn(array $lookupVector, bool $exactlyOnce): mixed
{
/** @var string[] */
$flattenedLookupVector = Functions::flattenArray($lookupVector);
if (count($lookupVector, COUNT_RECURSIVE) > count($flattenedLookupVector, COUNT_RECURSIVE) + 1) {
@@ -94,6 +117,11 @@ class Unique
return $result;
}
/**
* @param string[] $caseSensitiveLookupValues
*
* @return mixed[]
*/
private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array
{
$caseInsensitiveCounts = array_count_values(
@@ -121,6 +149,11 @@ class Unique
return $caseSensitiveCounts;
}
/**
* @param mixed[] $values
*
* @return mixed[]
*/
private static function exactlyOnceFilter(array $values): array
{
return array_filter(

View File

@@ -17,14 +17,14 @@ class VLookup extends LookupBase
* in the same row based on the index_number.
*
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param mixed $lookupArray The range of cells being searched
* @param mixed $indexNumber The column number in table_array from which the matching value must be returned.
* @param mixed[] $lookupArray The range of cells being searched
* @param array<mixed>|float|int|string $indexNumber The column number in table_array from which the matching value must be returned.
* The first column is 1.
* @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
*
* @return mixed The value of the found cell
*/
public static function lookup(mixed $lookupValue, mixed $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed
public static function lookup(mixed $lookupValue, $lookupArray, mixed $indexNumber, mixed $notExactMatch = true): mixed
{
if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
@@ -54,6 +54,7 @@ class VLookup extends LookupBase
uasort($lookupArray, $callable);
}
/** @var string[][] $lookupArray */
$rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
if ($rowNumber !== null) {
@@ -64,6 +65,10 @@ class VLookup extends LookupBase
return ExcelError::NA();
}
/**
* @param scalar[] $a
* @param scalar[] $b
*/
private static function vlookupSort(array $a, array $b): int
{
reset($a);
@@ -80,16 +85,17 @@ class VLookup extends LookupBase
/**
* @param mixed $lookupValue The value that you want to match in lookup_array
* @param string[][] $lookupArray
* @param int|string $column
*/
private static function vLookupSearch(mixed $lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int
{
$lookupLower = StringHelper::strToLower((string) $lookupValue);
$lookupLower = StringHelper::strToLower(StringHelper::convertToString($lookupValue));
$rowNumber = null;
foreach ($lookupArray as $rowKey => $rowData) {
$bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]);
$bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]);
$bothNumeric = self::numeric($lookupValue) && self::numeric($rowData[$column]);
$bothNotNumeric = !self::numeric($lookupValue) && !self::numeric($rowData[$column]);
$cellDataLower = StringHelper::strToLower((string) $rowData[$column]);
// break if we have passed possible keys
@@ -114,4 +120,9 @@ class VLookup extends LookupBase
return $rowNumber;
}
private static function numeric(mixed $value): bool
{
return is_int($value) || is_float($value);
}
}

View File

@@ -16,7 +16,7 @@ class Absolute
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|int|string rounded number
* @return array<mixed>|float|int|string rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -16,7 +16,7 @@ class Angle
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string Rounded number
* @return array<mixed>|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -42,7 +42,7 @@ class Angle
*
* @param mixed $number Should be numeric, or can be an array of numbers
*
* @return array|float|string Rounded number
* @return array<mixed>|float|string Rounded number
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -22,6 +22,8 @@ class Arabic
/**
* Recursively calculate the arabic value of a roman numeral.
*
* @param mixed[] $roman
*/
private static function calculateArabic(array $roman, int &$sum = 0, int $subtract = 0): int
{
@@ -55,7 +57,7 @@ class Arabic
*
* @param string|string[] $roman Should be a string, or can be an array of strings
*
* @return array|int|string the arabic numberal contrived from the roman numeral
* @return array<mixed>|int|string the arabic numberal contrived from the roman numeral
* If an array of numbers is passed as the argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -74,11 +76,14 @@ class Arabic
// Convert the roman numeral to an arabic number
$negativeNumber = $roman[0] === '-';
if ($negativeNumber) {
$roman = substr($roman, 1);
$roman = trim(substr($roman, 1));
if ($roman === '') {
return ExcelError::NAN();
}
}
try {
$arabic = self::calculateArabic(str_split($roman));
$arabic = self::calculateArabic(mb_str_split($roman, 1, 'UTF-8'));
} catch (Exception) {
return ExcelError::VALUE(); // Invalid character detected
}

View File

@@ -25,7 +25,7 @@ class Base
* @param mixed $minLength expect int or null
* Or can be an array of values
*
* @return array|string the text representation with the given radix (base)
* @return array<mixed>|string the text representation with the given radix (base)
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/

View File

@@ -22,12 +22,12 @@ class Ceiling
* Excel Function:
* CEILING(number[,significance])
*
* @param array|float $number the number you want the ceiling
* @param array<mixed>|float $number the number you want the ceiling
* Or can be an array of values
* @param array|float $significance the multiple to which you want to round
* @param array<mixed>|float $significance the multiple to which you want to round
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* @return array<mixed>|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -63,14 +63,14 @@ class Ceiling
* Or can be an array of values
* @param mixed $significance Significance
* Or can be an array of values
* @param array|int $mode direction to round negative numbers
* @param array<mixed>|int $mode direction to round negative numbers
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* @return array<mixed>|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
public static function math(mixed $number, mixed $significance = null, $mode = 0): array|string|float
public static function math(mixed $number, mixed $significance = null, $mode = 0, bool $checkSigns = false): array|string|float
{
if (is_array($number) || is_array($significance) || is_array($mode)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode);
@@ -87,6 +87,11 @@ class Ceiling
if (empty($significance * $number)) {
return 0.0;
}
if ($checkSigns) {
if (($number > 0 && $significance < 0) || ($number < 0 && $significance > 0)) {
return ExcelError::NAN();
}
}
if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) {
return floor($number / $significance) * $significance;
}
@@ -104,10 +109,10 @@ class Ceiling
*
* @param mixed $number the number you want to round
* Or can be an array of values
* @param array|float $significance the multiple to which you want to round
* @param array<mixed>|float $significance the multiple to which you want to round
* Or can be an array of values
*
* @return array|float|string Rounded Number, or a string containing an error
* @return array<mixed>|float|string Rounded Number, or a string containing an error
* If an array of numbers is passed as an argument, then the returned result will also be an array
* with the same dimensions
*/
@@ -132,6 +137,23 @@ class Ceiling
return ceil($result) * $significance * (($significance < 0) ? -1 : 1);
}
/**
* CEILING.ODS, pseudo-function - CEILING as implemented in ODS.
*
* ODS Function (theoretical):
* CEILING.ODS(number[,significance[,mode]])
*
* @param mixed $number Number to round
* @param mixed $significance Significance
* @param array<mixed>|int $mode direction to round negative numbers
*
* @return array<mixed>|float|string Rounded Number, or a string containing an error
*/
public static function mathOds(mixed $number, mixed $significance = null, $mode = 0): array|string|float
{
return self::math($number, $significance, $mode, true);
}
/**
* Let CEILINGMATH complexity pass Scrutinizer.
*/
@@ -148,7 +170,12 @@ class Ceiling
if (empty($number * $significance)) {
return 0.0;
}
if (Helpers::returnSign($number) == Helpers::returnSign($significance)) {
$signSig = Helpers::returnSign($significance);
$signNum = Helpers::returnSign($number);
if (
($signSig === 1 && ($signNum === 1 || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC))
|| ($signSig === -1 && $signNum === -1)
) {
return ceil($number / $significance) * $significance;
}

Some files were not shown because too many files have changed in this diff Show More