Last of the work on array-enabling Date/Time functions; all completed in this category

This commit is contained in:
MarkBaker 2022-02-10 18:15:09 +01:00
parent ca81991728
commit 1e59b9113f
14 changed files with 242 additions and 44 deletions

View File

@ -575,21 +575,6 @@ parameters:
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
-
message: "#^Binary operation \"/\" between int\\|string and \\(float\\|int\\) results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
-
message: "#^Binary operation \"/\" between int\\|string and 360 results in an error\\.$#"
count: 3
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
-
message: "#^Binary operation \"/\" between int\\|string and 365 results in an error\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Engine\\\\Logger\\:\\:writeDebugLog\\(\\) has parameter \\$args with no type specified\\.$#"
count: 1

View File

@ -278,9 +278,9 @@ class DateTime
* or a standard date string
* @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* @param string $unit
* @param array|string $unit
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
*/
public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D')
{
@ -300,12 +300,12 @@ class DateTime
* @See DateTimeExcel\Days::between()
* Use the between method in the DateTimeExcel\Days class instead
*
* @param DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
* @param DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
* PHP date timestamp (integer), PHP DateTime object, or a standard date string
*
* @return int|string Number of days between start date and end date or an error
* @return array|int|string Number of days between start date and end date or an error
*/
public static function DAYS($endDate = 0, $startDate = 0)
{
@ -331,7 +331,7 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param bool $method US or European Method
* @param array|bool $method US or European Method
* FALSE or omitted: U.S. (NASD) method. If the starting date is
* the last day of a month, it becomes equal to the 30th of the
* same month. If the ending date is the last day of a month and
@ -343,7 +343,7 @@ class DateTime
* occur on the 31st of a month become equal to the 30th of the
* same month.
*
* @return int|string Number of days between start date and end date
* @return array|int|string Number of days between start date and end date
*/
public static function DAYS360($startDate = 0, $endDate = 0, $method = false)
{
@ -373,14 +373,14 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $method Method used for the calculation
* @param array|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
*
* @return float|string fraction of the year, or a string containing an error
* @return array|float|string fraction of the year, or a string containing an error
*/
public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0)
{
@ -409,7 +409,7 @@ class DateTime
* PHP DateTime object, or a standard date string
* @param mixed $dateArgs
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
*/
public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs)
{

View File

@ -46,7 +46,7 @@ class Days360
*/
public static function between($startDate = 0, $endDate = 0, $method = false)
{
if (is_array($endDate) || is_array($startDate) || is_array($method)) {
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}

View File

@ -4,25 +4,37 @@ namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use DateInterval;
use DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class Difference
{
use ArrayEnabled;
/**
* DATEDIF.
*
* @param mixed $startDate 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 mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
* or a standard date string
* @param string $unit
* Or can be an array of date values
* @param array|string $unit
* Or can be an array of unit values
*
* @return int|string Interval between the dates
* @return array|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
*/
public static function interval($startDate, $endDate, $unit = 'D')
{
if (is_array($startDate) || is_array($endDate) || is_array($unit)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $unit);
}
try {
$startDate = Helpers::getDateValue($startDate);
$endDate = Helpers::getDateValue($endDate);

View File

@ -2,10 +2,13 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
class Month
{
use ArrayEnabled;
/**
* EDATE.
*
@ -19,15 +22,23 @@ class Month
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $adjustmentMonths The number of months before or after start_date.
* Or can be an array of date values
* @param array|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 mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed 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
*/
public static function adjust($dateValue, $adjustmentMonths)
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
@ -54,15 +65,23 @@ class Month
*
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $adjustmentMonths The number of months before or after start_date.
* Or can be an array of date values
* @param array|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 mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* @return array|mixed 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
*/
public static function lastDay($dateValue, $adjustmentMonths)
{
if (is_array($dateValue) || is_array($adjustmentMonths)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
}
try {
$dateValue = Helpers::getDateValue($dateValue, false);
$adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);

View File

@ -2,11 +2,14 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
class NetworkDays
{
use ArrayEnabled;
/**
* NETWORKDAYS.
*
@ -20,14 +23,26 @@ class NetworkDays
*
* @param 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 mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param mixed $dateArgs
* Or can be an array of date values
* @param mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return int|string Interval between the dates
* @return array|int|string Interval between the dates
*/
public static function count($startDate, $endDate, ...$dateArgs)
{
if (is_array($startDate) || is_array($endDate)) {
return self::evaluateArrayArgumentsSubset(
[self::class, __FUNCTION__],
2,
$startDate,
$endDate,
...$dateArgs
);
}
try {
// Retrieve the mandatory start and end date that are referenced in the function definition
$sDate = Helpers::getDateValue($startDate);

View File

@ -28,7 +28,7 @@ class WorkDay
* 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 mixed $dateArgs
* @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
*
* @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
* depending on the value of the ReturnDateType flag
@ -194,8 +194,8 @@ class WorkDay
private static function getWeekDay(float $date, int $wd): int
{
$result = Week::day($date, $wd);
$result = Functions::scalar(Week::day($date, $wd));
return is_string($result) ? -1 : $result;
return is_int($result) ? $result : -1;
}
}

View File

@ -2,12 +2,15 @@
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
class YearFrac
{
use ArrayEnabled;
/**
* YEARFRAC.
*
@ -23,19 +26,28 @@ class YearFrac
*
* @param 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 methods
* @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
* PHP DateTime object, or a standard date string
* @param int $method Method used for the calculation
* Or can be an array of methods
* @param array|int $method Method used for the calculation
* 0 or omitted US (NASD) 30/360
* 1 Actual/actual
* 2 Actual/360
* 3 Actual/365
* 4 European 30/360
* Or can be an array of methods
*
* @return float|string fraction of the year, or a string containing an error
* @return array|float|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
*/
public static function fraction($startDate, $endDate, $method = 0)
{
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
}
try {
$method = (int) Helpers::validateNumericNull($method);
$sDate = Helpers::getDateValue($startDate);
@ -50,15 +62,15 @@ class YearFrac
switch ($method) {
case 0:
return Days360::between($startDate, $endDate) / 360;
return Functions::scalar(Days360::between($startDate, $endDate)) / 360;
case 1:
return self::method1($startDate, $endDate);
case 2:
return Difference::interval($startDate, $endDate) / 360;
return Functions::scalar(Difference::interval($startDate, $endDate)) / 360;
case 3:
return Difference::interval($startDate, $endDate) / 365;
return Functions::scalar(Difference::interval($startDate, $endDate)) / 365;
case 4:
return Days360::between($startDate, $endDate, true) / 360;
return Functions::scalar(Days360::between($startDate, $endDate, true)) / 360;
}
return Functions::NAN();
@ -87,7 +99,7 @@ class YearFrac
private static function method1(float $startDate, float $endDate): float
{
$days = Difference::interval($startDate, $endDate);
$days = Functions::scalar(Difference::interval($startDate, $endDate));
$startYear = (int) DateParts::year($startDate);
$endYear = (int) DateParts::year($endDate);
$years = $endYear - $startYear + 1;

View File

@ -608,6 +608,24 @@ class Functions
return $arrayValues;
}
/**
* @param mixed $value
*
* @return null|mixed
*/
public static function scalar($value)
{
if (!is_array($value)) {
return $value;
}
do {
$value = array_pop($value);
} while (is_array($value));
return $value;
}
/**
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
*

View File

@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class DateDifTest extends AllSetupTeardown
{
/**
@ -22,4 +24,30 @@ class DateDifTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/DateTime/DATEDIF.php';
}
/**
* @dataProvider providerDateDifArray
*/
public function testDateDifArray(array $expectedResult, string $startDate, string $endDate, ?string $methods): void
{
$calculation = Calculation::getInstance();
if ($methods === null) {
$formula = "=DATEDIF({$startDate}, {$endDate})";
} else {
$formula = "=DATEDIF({$startDate}, {$endDate}, {$methods})";
}
$result = $calculation->_calculateFormulaValue($formula);
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
}
public function providerDateDifArray(): array
{
return [
'row vector #1' => [[[364, 202, '#NUM!']], '{"2022-01-01", "2022-06-12", "2023-07-22"}', '"2022-12-31"', null],
'column vector #1' => [[[364], [362], [359]], '{"2022-01-01"; "2022-01-03"; "2022-01-06"}', '"2022-12-31"', null],
'matrix #1' => [[[365, 266], [139, 1]], '{"2022-01-01", "2022-04-10"; "2022-08-15", "2022-12-31"}', '"2023-01-01"', null],
'column vector with methods' => [[[364, 11], [242, 7], [173, 5]], '{"2022-01-01"; "2022-05-03"; "2022-07-11"}', '"2022-12-31"', '{"D", "M"}'],
];
}
}

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Month;
class EDateTest extends AllSetupTeardown
@ -46,4 +47,28 @@ class EDateTest extends AllSetupTeardown
// ... with the correct value
self::assertEquals($result->format('d-M-Y'), '26-Dec-2011');
}
/**
* @dataProvider providerEDateArray
*/
public function testEDateArray(array $expectedResult, string $dateValues, string $methods): void
{
$calculation = Calculation::getInstance();
$formula = "=EDATE({$dateValues}, {$methods})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
}
public function providerEDateArray(): array
{
return [
'row vector #1' => [[[44593, 44632, 45337]], '{"2022-01-01", "2022-02-12", "2024-01-15"}', '1'],
'column vector #1' => [[[44593], [44632], [45337]], '{"2022-01-01"; "2022-02-12"; "2024-01-15"}', '1'],
'matrix #1' => [[[44593, 44632], [44652, 45343]], '{"2022-01-01", "2022-02-12"; "2022-03-01", "2024-01-21"}', '1'],
'row vector #2' => [[[44573, 44604, 44632]], '"2022-02-12"', '{-1, 0, 1}'],
'column vector #2' => [[[44573], [44604], [44632]], '"2022-02-12"', '{-1; 0; 1}'],
'matrix #2' => [[[44573, 44604], [44632, 45334]], '"2022-02-12"', '{-1, 0; 1, 24}'],
];
}
}

View File

@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Month;
class EoMonthTest extends AllSetupTeardown
@ -45,4 +46,28 @@ class EoMonthTest extends AllSetupTeardown
// ... with the correct value
self::assertSame($result->format('d-M-Y'), '31-Dec-2011');
}
/**
* @dataProvider providerEoMonthArray
*/
public function testEoMonthArray(array $expectedResult, string $dateValues, string $methods): void
{
$calculation = Calculation::getInstance();
$formula = "=EOMONTH({$dateValues}, {$methods})";
$result = $calculation->_calculateFormulaValue($formula);
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
}
public function providerEoMonthArray(): array
{
return [
'row vector #1' => [[[44620, 44651, 45351]], '{"2022-01-01", "2022-02-12", "2024-01-15"}', '1'],
'column vector #1' => [[[44620], [44651], [45351]], '{"2022-01-01"; "2022-02-12"; "2024-01-15"}', '1'],
'matrix #1' => [[[44620, 44651], [44681, 45351]], '{"2022-01-01", "2022-02-12"; "2022-03-01", "2024-01-21"}', '1'],
'row vector #2' => [[[44592, 44620, 44651]], '"2022-02-12"', '{-1, 0, 1}'],
'column vector #2' => [[[44592], [44620], [44651]], '"2022-02-12"', '{-1; 0; 1}'],
'matrix #2' => [[[44592, 44620], [44651, 45351]], '"2022-02-12"', '{-1, 0; 1, 24}'],
];
}
}

View File

@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class NetworkDaysTest extends AllSetupTeardown
{
/**
@ -49,4 +51,33 @@ class NetworkDaysTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/DateTime/NETWORKDAYS.php';
}
/**
* @dataProvider providerNetWorkDaysArray
*/
public function testNetWorkDaysArray(array $expectedResult, string $startDate, string $endDays, ?string $holidays): void
{
$calculation = Calculation::getInstance();
if ($holidays === null) {
$formula = "=NETWORKDAYS({$startDate}, {$endDays})";
} else {
$formula = "=NETWORKDAYS({$startDate}, {$endDays}, {$holidays})";
}
$result = $calculation->_calculateFormulaValue($formula);
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
}
public function providerNetWorkDaysArray(): array
{
return [
'row vector #1' => [[[234, 233, 232]], '{"2022-02-01", "2022-02-02", "2022-02-03"}', '"2022-12-25"', null],
'column vector #1' => [[[234], [233], [232]], '{"2022-02-01"; "2022-02-02"; "2022-02-03"}', '"2022-12-25"', null],
'matrix #1' => [[[234, 233], [232, 231]], '{"2022-02-01", "2022-02-02"; "2022-02-03", "2022-02-04"}', '"2022-12-25"', null],
'row vector #2' => [[[234, -27]], '"2022-02-01"', '{"2022-12-25", "2021-12-25"}', null],
'column vector #2' => [[[234], [-27]], '"2022-02-01"', '{"2022-12-25"; "2021-12-25"}', null],
'row vector with Holiday' => [[[233, -27]], '"2022-02-01"', '{"2022-12-25", "2021-12-25"}', '{"2022-02-02"}'],
'row vector with Holidays' => [[[232, -27]], '"2022-02-01"', '{"2022-12-25", "2021-12-25"}', '{"2022-02-02", "2022-02-03"}'],
];
}
}

View File

@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class YearFracTest extends AllSetupTeardown
{
/**
@ -41,4 +43,30 @@ class YearFracTest extends AllSetupTeardown
{
return require 'tests/data/Calculation/DateTime/YEARFRAC.php';
}
/**
* @dataProvider providerYearFracArray
*/
public function testYearFracArray(array $expectedResult, string $startDate, string $endDate, ?string $methods): void
{
$calculation = Calculation::getInstance();
if ($methods === null) {
$formula = "=YEARFRAC({$startDate}, {$endDate})";
} else {
$formula = "=YEARFRAC({$startDate}, {$endDate}, {$methods})";
}
$result = $calculation->_calculateFormulaValue($formula);
self::assertEqualsWithDelta($expectedResult, $result, 1.0e-14);
}
public function providerYearFracArray(): array
{
return [
'row vector #1' => [[[1.0, 0.55277777777778, 0.56111111111111]], '{"2022-01-01", "2022-06-12", "2023-07-22"}', '"2022-12-31"', null],
'column vector #1' => [[[1.0], [0.99444444444445], [0.98611111111111]], '{"2022-01-01"; "2022-01-03"; "2022-01-06"}', '"2022-12-31"', null],
'matrix #1' => [[[0.002777777777778, 0.027777777777778], [0.625, 1.0]], '{"2022-01-01", "2022-01-10"; "2022-08-15", "2022-12-31"}', '"2021-12-31"', null],
'column vector with methods' => [[[0.99726027397260, 0.99722222222222], [0.99178082191781, 0.99166666666667], [0.98356164383562, 0.98333333333333]], '{"2022-01-01"; "2022-01-03"; "2022-01-06"}', '"2022-12-31"', '{1, 4}'],
];
}
}