init
This commit is contained in:
commit
72a26edcff
22092 changed files with 2101903 additions and 0 deletions
38
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php
Normal file
38
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
class Constants
|
||||
{
|
||||
// Constants currently used by WeekNum; will eventually be used by WEEKDAY
|
||||
const STARTWEEK_SUNDAY = 1;
|
||||
const STARTWEEK_MONDAY = 2;
|
||||
const STARTWEEK_MONDAY_ALT = 11;
|
||||
const STARTWEEK_TUESDAY = 12;
|
||||
const STARTWEEK_WEDNESDAY = 13;
|
||||
const STARTWEEK_THURSDAY = 14;
|
||||
const STARTWEEK_FRIDAY = 15;
|
||||
const STARTWEEK_SATURDAY = 16;
|
||||
const STARTWEEK_SUNDAY_ALT = 17;
|
||||
const DOW_SUNDAY = 1;
|
||||
const DOW_MONDAY = 2;
|
||||
const DOW_TUESDAY = 3;
|
||||
const DOW_WEDNESDAY = 4;
|
||||
const DOW_THURSDAY = 5;
|
||||
const DOW_FRIDAY = 6;
|
||||
const DOW_SATURDAY = 7;
|
||||
const STARTWEEK_MONDAY_ISO = 21;
|
||||
|
||||
const METHODARR = [
|
||||
self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
|
||||
self::DOW_MONDAY,
|
||||
self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
|
||||
self::DOW_TUESDAY,
|
||||
self::DOW_WEDNESDAY,
|
||||
self::DOW_THURSDAY,
|
||||
self::DOW_FRIDAY,
|
||||
self::DOW_SATURDAY,
|
||||
self::DOW_SUNDAY,
|
||||
self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
|
||||
];
|
||||
}
|
||||
59
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php
Normal file
59
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
|
||||
class Current
|
||||
{
|
||||
/**
|
||||
* DATENOW.
|
||||
*
|
||||
* Returns the current date.
|
||||
* The NOW function is useful when you need to display the current date and time on a worksheet or
|
||||
* calculate a value based on the current date and time, and have that value updated each time you
|
||||
* open the worksheet.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TODAY()
|
||||
*
|
||||
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
||||
* depending on the value of the ReturnDateType flag
|
||||
*/
|
||||
public static function today(): mixed
|
||||
{
|
||||
$dti = new DateTimeImmutable();
|
||||
$dateArray = Helpers::dateParse($dti->format('c'));
|
||||
|
||||
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : ExcelError::VALUE();
|
||||
}
|
||||
|
||||
/**
|
||||
* DATETIMENOW.
|
||||
*
|
||||
* Returns the current date and time.
|
||||
* The NOW function is useful when you need to display the current date and time on a worksheet or
|
||||
* calculate a value based on the current date and time, and have that value updated each time you
|
||||
* open the worksheet.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* NOW()
|
||||
*
|
||||
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
||||
* depending on the value of the ReturnDateType flag
|
||||
*/
|
||||
public static function now(): mixed
|
||||
{
|
||||
$dti = new DateTimeImmutable();
|
||||
$dateArray = Helpers::dateParse($dti->format('c'));
|
||||
|
||||
return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE();
|
||||
}
|
||||
}
|
||||
166
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php
Normal file
166
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
|
||||
class Date
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DATE.
|
||||
*
|
||||
* The DATE function returns a value that represents a particular date.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* DATE(year,month,day)
|
||||
*
|
||||
* PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function.
|
||||
* A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
|
||||
* as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
|
||||
*
|
||||
* @param array|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
|
||||
* value to 1900 to calculate the year. For example, DATE(108,1,2)
|
||||
* returns January 2, 2008 (1900+108).
|
||||
* If year is between 1900 and 9999 (inclusive), Excel uses that
|
||||
* value as the year. For example, DATE(2008,1,2) returns January 2,
|
||||
* 2008.
|
||||
* If year is less than 0 or is 10000 or greater, Excel returns the
|
||||
* #NUM! error value.
|
||||
* @param array|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)
|
||||
* returns the serial number representing February 2, 2009.
|
||||
* If month is less than 1, month subtracts the magnitude of that
|
||||
* number of months, plus 1, from the first month in the year
|
||||
* specified. For example, DATE(2008,-3,2) returns the serial number
|
||||
* representing September 2, 2007.
|
||||
* @param array|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
|
||||
* example, DATE(2008,1,35) returns the serial number representing
|
||||
* February 4, 2008.
|
||||
* If day is less than 1, day subtracts the magnitude that number of
|
||||
* days, plus one, from the first day of the month specified. For
|
||||
* example, DATE(2008,1,-15) returns the serial number representing
|
||||
* December 16, 2007.
|
||||
*
|
||||
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
||||
* depending on the value of the ReturnDateType flag
|
||||
* 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): mixed
|
||||
{
|
||||
if (is_array($year) || is_array($month) || is_array($day)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day);
|
||||
}
|
||||
|
||||
$baseYear = SharedDateHelper::getExcelCalendar();
|
||||
|
||||
try {
|
||||
$year = self::getYear($year, $baseYear);
|
||||
$month = self::getMonth($month);
|
||||
$day = self::getDay($day);
|
||||
self::adjustYearMonth($year, $month, $baseYear);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day);
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert year from multiple formats to int.
|
||||
*/
|
||||
private static function getYear(mixed $year, int $baseYear): int
|
||||
{
|
||||
$year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0;
|
||||
if (!is_numeric($year)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
$year = (int) $year;
|
||||
|
||||
if ($year < ($baseYear - 1900)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
|
||||
$year += 1900;
|
||||
}
|
||||
|
||||
return (int) $year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert month from multiple formats to int.
|
||||
*/
|
||||
private static function getMonth(mixed $month): int
|
||||
{
|
||||
if (($month !== null) && (!is_numeric($month))) {
|
||||
$month = SharedDateHelper::monthStringToNumber($month);
|
||||
}
|
||||
|
||||
$month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0;
|
||||
if (!is_numeric($month)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) $month;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert day from multiple formats to int.
|
||||
*/
|
||||
private static function getDay(mixed $day): int
|
||||
{
|
||||
if (($day !== null) && (!is_numeric($day))) {
|
||||
$day = SharedDateHelper::dayStringToNumber($day);
|
||||
}
|
||||
|
||||
$day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0;
|
||||
if (!is_numeric($day)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) $day;
|
||||
}
|
||||
|
||||
private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void
|
||||
{
|
||||
if ($month < 1) {
|
||||
// Handle year/month adjustment if month < 1
|
||||
--$month;
|
||||
$year += ceil($month / 12) - 1;
|
||||
$month = 13 - abs($month % 12);
|
||||
} elseif ($month > 12) {
|
||||
// Handle year/month adjustment if month > 12
|
||||
$year += floor($month / 12);
|
||||
$month = ($month % 12);
|
||||
}
|
||||
|
||||
// Re-validate the year parameter after adjustments
|
||||
if (($year < $baseYear) || ($year >= 10000)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
}
|
||||
}
|
||||
154
lib/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php
Normal file
154
lib/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
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 DateParts
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DAYOFMONTH.
|
||||
*
|
||||
* Returns the day of the month, for a specified date. The day is given as an integer
|
||||
* ranging from 1 to 31.
|
||||
*
|
||||
* Excel Function:
|
||||
* DAY(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of date values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function day(mixed $dateValue): array|int|string
|
||||
{
|
||||
if (is_array($dateValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
$weirdResult = self::weirdCondition($dateValue);
|
||||
if ($weirdResult >= 0) {
|
||||
return $weirdResult;
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
SharedDateHelper::roundMicroseconds($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* MONTHOFYEAR.
|
||||
*
|
||||
* Returns the month of a date represented by a serial number.
|
||||
* The month is given as an integer, ranging from 1 (January) to 12 (December).
|
||||
*
|
||||
* Excel Function:
|
||||
* MONTH(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of date values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function month(mixed $dateValue): array|string|int
|
||||
{
|
||||
if (is_array($dateValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
SharedDateHelper::roundMicroseconds($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* YEAR.
|
||||
*
|
||||
* Returns the year corresponding to a date.
|
||||
* The year is returned as an integer in the range 1900-9999.
|
||||
*
|
||||
* Excel Function:
|
||||
* YEAR(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of date values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function year(mixed $dateValue): array|string|int
|
||||
{
|
||||
if (is_array($dateValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
|
||||
return 1900;
|
||||
}
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
SharedDateHelper::roundMicroseconds($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*/
|
||||
private static function weirdCondition(mixed $dateValue): int
|
||||
{
|
||||
// Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR)
|
||||
if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
|
||||
if (is_bool($dateValue)) {
|
||||
return (int) $dateValue;
|
||||
}
|
||||
if ($dateValue === null) {
|
||||
return 0;
|
||||
}
|
||||
if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
162
lib/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
Normal file
162
lib/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class DateValue
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DATEVALUE.
|
||||
*
|
||||
* Returns a value that represents a particular date.
|
||||
* Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp
|
||||
* value.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* DATEVALUE(dateValue)
|
||||
*
|
||||
* @param null|array|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
|
||||
* January 1, 1900, to December 31, 9999. Using the default date
|
||||
* system in Excel for the Macintosh, date_text must represent a date
|
||||
* from January 1, 1904, to December 31, 9999. DATEVALUE returns the
|
||||
* #VALUE! error value if date_text is out of this range.
|
||||
* Or can be an array of date values
|
||||
*
|
||||
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
||||
* depending on the value of the ReturnDateType flag
|
||||
* 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 fromString(null|array|string|int|bool|float $dateValue): mixed
|
||||
{
|
||||
if (is_array($dateValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
// try to parse as date iff there is at least one digit
|
||||
if (is_string($dateValue) && preg_match('/\\d/', $dateValue) !== 1) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
$dti = new DateTimeImmutable();
|
||||
$baseYear = SharedDateHelper::getExcelCalendar();
|
||||
$dateValue = trim((string) $dateValue, '"');
|
||||
// Strip any ordinals because they're allowed in Excel (English only)
|
||||
$dateValue = (string) preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue);
|
||||
// Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
|
||||
$dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue);
|
||||
|
||||
$yearFound = false;
|
||||
$t1 = explode(' ', $dateValue);
|
||||
$t = '';
|
||||
foreach ($t1 as &$t) {
|
||||
if ((is_numeric($t)) && ($t > 31)) {
|
||||
if ($yearFound) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
if ($t < 100) {
|
||||
$t += 1900;
|
||||
}
|
||||
$yearFound = true;
|
||||
}
|
||||
}
|
||||
if (count($t1) === 1) {
|
||||
// We've been fed a time value without any date
|
||||
return ((!str_contains((string) $t, ':'))) ? ExcelError::Value() : 0.0;
|
||||
}
|
||||
unset($t);
|
||||
|
||||
$dateValue = self::t1ToString($t1, $dti, $yearFound);
|
||||
|
||||
$PHPDateArray = self::setUpArray($dateValue, $dti);
|
||||
|
||||
return self::finalResults($PHPDateArray, $dti, $baseYear);
|
||||
}
|
||||
|
||||
private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string
|
||||
{
|
||||
if (count($t1) == 2) {
|
||||
// We only have two parts of the date: either day/month or month/year
|
||||
if ($yearFound) {
|
||||
array_unshift($t1, 1);
|
||||
} else {
|
||||
if (is_numeric($t1[1]) && $t1[1] > 29) {
|
||||
$t1[1] += 1900;
|
||||
array_unshift($t1, 1);
|
||||
} else {
|
||||
$t1[] = $dti->format('Y');
|
||||
}
|
||||
}
|
||||
}
|
||||
$dateValue = implode(' ', $t1);
|
||||
|
||||
return $dateValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse date.
|
||||
*/
|
||||
private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array
|
||||
{
|
||||
$PHPDateArray = Helpers::dateParse($dateValue);
|
||||
if (!Helpers::dateParseSucceeded($PHPDateArray)) {
|
||||
// If original count was 1, we've already returned.
|
||||
// If it was 2, we added another.
|
||||
// Therefore, neither of the first 2 stroks below can fail.
|
||||
$testVal1 = strtok($dateValue, '- ');
|
||||
$testVal2 = strtok('- ');
|
||||
$testVal3 = strtok('- ') ?: $dti->format('Y');
|
||||
Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3);
|
||||
$PHPDateArray = Helpers::dateParse($testVal1 . '-' . $testVal2 . '-' . $testVal3);
|
||||
if (!Helpers::dateParseSucceeded($PHPDateArray)) {
|
||||
$PHPDateArray = Helpers::dateParse($testVal2 . '-' . $testVal1 . '-' . $testVal3);
|
||||
}
|
||||
}
|
||||
|
||||
return $PHPDateArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final results.
|
||||
*
|
||||
* @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
|
||||
* depending on the value of the ReturnDateType flag
|
||||
*/
|
||||
private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear): mixed
|
||||
{
|
||||
$retValue = ExcelError::Value();
|
||||
if (Helpers::dateParseSucceeded($PHPDateArray)) {
|
||||
// Execute function
|
||||
Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
|
||||
if ($PHPDateArray['year'] < $baseYear) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
|
||||
Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
|
||||
$PHPDateArray['hour'] = 0;
|
||||
$PHPDateArray['minute'] = 0;
|
||||
$PHPDateArray['second'] = 0;
|
||||
$month = (int) $PHPDateArray['month'];
|
||||
$day = (int) $PHPDateArray['day'];
|
||||
$year = (int) $PHPDateArray['year'];
|
||||
if (!checkdate($month, $day, $year)) {
|
||||
return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE();
|
||||
}
|
||||
$retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true);
|
||||
}
|
||||
|
||||
return $retValue;
|
||||
}
|
||||
}
|
||||
62
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php
Normal file
62
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTimeInterface;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class Days
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DAYS.
|
||||
*
|
||||
* Returns the number of days between two dates
|
||||
*
|
||||
* Excel Function:
|
||||
* DAYS(endDate, startDate)
|
||||
*
|
||||
* @param array|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),
|
||||
* 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
|
||||
* 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 between(array|DateTimeInterface|float|int|string $endDate, array|DateTimeInterface|float|int|string $startDate): array|int|string
|
||||
{
|
||||
if (is_array($endDate) || is_array($startDate)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $endDate, $startDate);
|
||||
}
|
||||
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDate = Helpers::getDateValue($endDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
|
||||
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
|
||||
|
||||
$days = ExcelError::VALUE();
|
||||
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
|
||||
if ($diff !== false && !is_bool($diff->days)) {
|
||||
$days = $diff->days;
|
||||
if ($diff->invert) {
|
||||
$days = -$days;
|
||||
}
|
||||
}
|
||||
|
||||
return $days;
|
||||
}
|
||||
}
|
||||
118
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php
Normal file
118
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class Days360
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* DAYS360.
|
||||
*
|
||||
* Returns the number of days between two dates based on a 360-day year (twelve 30-day months),
|
||||
* which is used in some accounting calculations. Use this function to help compute payments if
|
||||
* your accounting system is based on twelve 30-day months.
|
||||
*
|
||||
* Excel Function:
|
||||
* DAYS360(startDate,endDate[,method])
|
||||
*
|
||||
* @param array|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|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 date values
|
||||
* @param array|mixed $method US or European Method as a bool
|
||||
* FALSE or omitted: U.S. (NASD) method. If the starting date is
|
||||
* the last day of a month, it becomes equal to the 30th of the
|
||||
* same month. If the ending date is the last day of a month and
|
||||
* the starting date is earlier than the 30th of a month, the
|
||||
* ending date becomes equal to the 1st of the next month;
|
||||
* otherwise the ending date becomes equal to the 30th of the
|
||||
* same month.
|
||||
* TRUE: European method. Starting dates and ending dates that
|
||||
* occur on the 31st of a month become equal to the 30th of the
|
||||
* same month.
|
||||
* Or can be an array of methods
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function between(mixed $startDate = 0, mixed $endDate = 0, mixed $method = false): array|string|int
|
||||
{
|
||||
if (is_array($startDate) || is_array($endDate) || is_array($method)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
|
||||
}
|
||||
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDate = Helpers::getDateValue($endDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
if (!is_bool($method)) {
|
||||
return ExcelError::VALUE();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
|
||||
$startDay = $PHPStartDateObject->format('j');
|
||||
$startMonth = $PHPStartDateObject->format('n');
|
||||
$startYear = $PHPStartDateObject->format('Y');
|
||||
|
||||
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
|
||||
$endDay = $PHPEndDateObject->format('j');
|
||||
$endMonth = $PHPEndDateObject->format('n');
|
||||
$endYear = $PHPEndDateObject->format('Y');
|
||||
|
||||
return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of days between two dates based on a 360 day calendar.
|
||||
*/
|
||||
private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int
|
||||
{
|
||||
$startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS);
|
||||
$endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS);
|
||||
|
||||
return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
|
||||
}
|
||||
|
||||
private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int
|
||||
{
|
||||
if ($startDay == 31) {
|
||||
--$startDay;
|
||||
} elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) {
|
||||
$startDay = 30;
|
||||
}
|
||||
|
||||
return $startDay;
|
||||
}
|
||||
|
||||
private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int
|
||||
{
|
||||
if ($endDay == 31) {
|
||||
if ($methodUS && $startDay != 30) {
|
||||
$endDay = 1;
|
||||
if ($endMonth == 12) {
|
||||
++$endYear;
|
||||
$endMonth = 1;
|
||||
} else {
|
||||
++$endMonth;
|
||||
}
|
||||
} else {
|
||||
$endDay = 30;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDay;
|
||||
}
|
||||
}
|
||||
153
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php
Normal file
153
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
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
|
||||
* Or can be an array of date values
|
||||
* @param array|string $unit Or can be an array of unit values
|
||||
*
|
||||
* @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(mixed $startDate, mixed $endDate, array|string $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);
|
||||
$difference = self::initialDiff($startDate, $endDate);
|
||||
$unit = strtoupper($unit);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
|
||||
$startDays = (int) $PHPStartDateObject->format('j');
|
||||
//$startMonths = (int) $PHPStartDateObject->format('n');
|
||||
$startYears = (int) $PHPStartDateObject->format('Y');
|
||||
|
||||
$PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
|
||||
$endDays = (int) $PHPEndDateObject->format('j');
|
||||
//$endMonths = (int) $PHPEndDateObject->format('n');
|
||||
$endYears = (int) $PHPEndDateObject->format('Y');
|
||||
|
||||
$PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject);
|
||||
|
||||
$retVal = false;
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject);
|
||||
$retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject);
|
||||
|
||||
return is_bool($retVal) ? ExcelError::VALUE() : $retVal;
|
||||
}
|
||||
|
||||
private static function initialDiff(float $startDate, float $endDate): float
|
||||
{
|
||||
// Validate parameters
|
||||
if ($startDate > $endDate) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $endDate - $startDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether it's time to set retVal.
|
||||
*/
|
||||
private static function replaceRetValue(bool|int $retVal, string $unit, string $compare): null|bool|int
|
||||
{
|
||||
if ($retVal !== false || $unit !== $compare) {
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function datedifD(float $difference): int
|
||||
{
|
||||
return (int) $difference;
|
||||
}
|
||||
|
||||
private static function datedifM(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m');
|
||||
}
|
||||
|
||||
private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
if ($endDays < $startDays) {
|
||||
$retVal = $endDays;
|
||||
$PHPEndDateObject->modify('-' . $endDays . ' days');
|
||||
$adjustDays = (int) $PHPEndDateObject->format('j');
|
||||
$retVal += ($adjustDays - $startDays);
|
||||
} else {
|
||||
$retVal = (int) $PHPDiffDateObject->format('%d');
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
private static function datedifY(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return (int) $PHPDiffDateObject->format('%y');
|
||||
}
|
||||
|
||||
private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int
|
||||
{
|
||||
$retVal = (int) $difference;
|
||||
if ($endYears > $startYears) {
|
||||
$isLeapStartYear = $PHPStartDateObject->format('L');
|
||||
$wasLeapEndYear = $PHPEndDateObject->format('L');
|
||||
|
||||
// Adjust end year to be as close as possible as start year
|
||||
while ($PHPEndDateObject >= $PHPStartDateObject) {
|
||||
$PHPEndDateObject->modify('-1 year');
|
||||
//$endYears = $PHPEndDateObject->format('Y');
|
||||
}
|
||||
$PHPEndDateObject->modify('+1 year');
|
||||
|
||||
// Get the result
|
||||
$retVal = (int) $PHPEndDateObject->diff($PHPStartDateObject)->days;
|
||||
|
||||
// Adjust for leap years cases
|
||||
$isLeapEndYear = $PHPEndDateObject->format('L');
|
||||
$limit = new DateTime($PHPEndDateObject->format('Y-02-29'));
|
||||
if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) {
|
||||
--$retVal;
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $retVal;
|
||||
}
|
||||
|
||||
private static function datedifYM(DateInterval $PHPDiffDateObject): int
|
||||
{
|
||||
return (int) $PHPDiffDateObject->format('%m');
|
||||
}
|
||||
}
|
||||
282
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
Normal file
282
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Identify if a year is a leap year or not.
|
||||
*
|
||||
* @param int|string $year The year to test
|
||||
*
|
||||
* @return bool TRUE if the year is a leap year, otherwise FALSE
|
||||
*/
|
||||
public static function isLeapYear(int|string $year): bool
|
||||
{
|
||||
return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* getDateValue.
|
||||
*
|
||||
* @return float Excel date/time serial value
|
||||
*/
|
||||
public static function getDateValue(mixed $dateValue, bool $allowBool = true): float
|
||||
{
|
||||
if (is_object($dateValue)) {
|
||||
$retval = SharedDateHelper::PHPToExcel($dateValue);
|
||||
if (is_bool($retval)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
self::nullFalseTrueToNumber($dateValue, $allowBool);
|
||||
if (!is_numeric($dateValue)) {
|
||||
$saveReturnDateType = Functions::getReturnDateType();
|
||||
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||
$dateValue = DateValue::fromString($dateValue);
|
||||
Functions::setReturnDateType($saveReturnDateType);
|
||||
if (!is_numeric($dateValue)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
}
|
||||
if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return (float) $dateValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* getTimeValue.
|
||||
*
|
||||
* @return mixed Excel date/time serial value, or string if error
|
||||
*/
|
||||
public static function getTimeValue(string $timeValue): mixed
|
||||
{
|
||||
$saveReturnDateType = Functions::getReturnDateType();
|
||||
Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
|
||||
$timeValue = TimeValue::fromString($timeValue);
|
||||
Functions::setReturnDateType($saveReturnDateType);
|
||||
|
||||
return $timeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust date by given months.
|
||||
*/
|
||||
public static function adjustDateByMonths(mixed $dateValue = 0, float $adjustmentMonths = 0): DateTime
|
||||
{
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
$oMonth = (int) $PHPDateObject->format('m');
|
||||
$oYear = (int) $PHPDateObject->format('Y');
|
||||
|
||||
$adjustmentMonthsString = (string) $adjustmentMonths;
|
||||
if ($adjustmentMonths > 0) {
|
||||
$adjustmentMonthsString = '+' . $adjustmentMonths;
|
||||
}
|
||||
if ($adjustmentMonths != 0) {
|
||||
$PHPDateObject->modify($adjustmentMonthsString . ' months');
|
||||
}
|
||||
$nMonth = (int) $PHPDateObject->format('m');
|
||||
$nYear = (int) $PHPDateObject->format('Y');
|
||||
|
||||
$monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
|
||||
if ($monthDiff != $adjustmentMonths) {
|
||||
$adjustDays = (int) $PHPDateObject->format('d');
|
||||
$adjustDaysString = '-' . $adjustDays . ' days';
|
||||
$PHPDateObject->modify($adjustDaysString);
|
||||
}
|
||||
|
||||
return $PHPDateObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Help reduce perceived complexity of some tests.
|
||||
*/
|
||||
public static function replaceIfEmpty(mixed &$value, mixed $altValue): void
|
||||
{
|
||||
$value = $value ?: $altValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust year in ambiguous situations.
|
||||
*/
|
||||
public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
|
||||
{
|
||||
if (!is_numeric($testVal1) || $testVal1 < 31) {
|
||||
if (!is_numeric($testVal2) || $testVal2 < 12) {
|
||||
if (is_numeric($testVal3) && $testVal3 < 12) {
|
||||
$testVal3 += 2000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*/
|
||||
public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false): DateTime|float|int
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
|
||||
return new DateTime(
|
||||
$dateArray['year']
|
||||
. '-' . $dateArray['month']
|
||||
. '-' . $dateArray['day']
|
||||
. ' ' . $dateArray['hour']
|
||||
. ':' . $dateArray['minute']
|
||||
. ':' . $dateArray['second']
|
||||
);
|
||||
}
|
||||
$excelDateValue
|
||||
= SharedDateHelper::formattedPHPToExcel(
|
||||
$dateArray['year'],
|
||||
$dateArray['month'],
|
||||
$dateArray['day'],
|
||||
$dateArray['hour'],
|
||||
$dateArray['minute'],
|
||||
$dateArray['second']
|
||||
);
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return $noFrac ? floor($excelDateValue) : $excelDateValue;
|
||||
}
|
||||
// RETURNDATE_UNIX_TIMESTAMP)
|
||||
|
||||
return SharedDateHelper::excelToTimestamp($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*/
|
||||
public static function returnIn3FormatsFloat(float $excelDateValue): float|int|DateTime
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return $excelDateValue;
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
return SharedDateHelper::excelToTimestamp($excelDateValue);
|
||||
}
|
||||
// RETURNDATE_PHP_DATETIME_OBJECT
|
||||
|
||||
return SharedDateHelper::excelToDateTimeObject($excelDateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return result in one of three formats.
|
||||
*/
|
||||
public static function returnIn3FormatsObject(DateTime $PHPDateObject): DateTime|float|int
|
||||
{
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
|
||||
return $PHPDateObject;
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
return (float) SharedDateHelper::PHPToExcel($PHPDateObject);
|
||||
}
|
||||
// RETURNDATE_UNIX_TIMESTAMP
|
||||
$stamp = SharedDateHelper::PHPToExcel($PHPDateObject);
|
||||
$stamp = is_bool($stamp) ? ((int) $stamp) : $stamp;
|
||||
|
||||
return SharedDateHelper::excelToTimestamp($stamp);
|
||||
}
|
||||
|
||||
private static function baseDate(): int
|
||||
{
|
||||
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
|
||||
return 0;
|
||||
}
|
||||
if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null/false/true argument treated as 0/0/1.
|
||||
*/
|
||||
public static function nullFalseTrueToNumber(mixed &$number, bool $allowBool = true): void
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
$nullVal = self::baseDate();
|
||||
if ($number === null) {
|
||||
$number = $nullVal;
|
||||
} elseif ($allowBool && is_bool($number)) {
|
||||
$number = $nullVal + (int) $number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null argument treated as 0.
|
||||
*/
|
||||
public static function validateNumericNull(mixed $number): int|float
|
||||
{
|
||||
$number = Functions::flattenSingleValue($number);
|
||||
if ($number === null) {
|
||||
return 0;
|
||||
}
|
||||
if (is_int($number)) {
|
||||
return $number;
|
||||
}
|
||||
if (is_numeric($number)) {
|
||||
return (float) $number;
|
||||
}
|
||||
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
/**
|
||||
* Many functions accept null/false/true argument treated as 0/0/1.
|
||||
*/
|
||||
public static function validateNotNegative(mixed $number): float
|
||||
{
|
||||
if (!is_numeric($number)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
if ($number >= 0) {
|
||||
return (float) $number;
|
||||
}
|
||||
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
|
||||
{
|
||||
$isoDate = $PHPDateObject->format('c');
|
||||
if ($isoDate < '1900-03-01') {
|
||||
$PHPDateObject->modify($mod);
|
||||
}
|
||||
}
|
||||
|
||||
public static function dateParse(string $string): array
|
||||
{
|
||||
return self::forceArray(date_parse($string));
|
||||
}
|
||||
|
||||
public static function dateParseSucceeded(array $dateArray): bool
|
||||
{
|
||||
return $dateArray['error_count'] === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Despite documentation, date_parse probably never returns false.
|
||||
* Just in case, this routine helps guarantee it.
|
||||
*
|
||||
* @param array|false $dateArray
|
||||
*/
|
||||
private static function forceArray(array|bool $dateArray): array
|
||||
{
|
||||
return is_array($dateArray) ? $dateArray : ['error_count' => 1];
|
||||
}
|
||||
}
|
||||
104
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php
Normal file
104
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
|
||||
class Month
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* EDATE.
|
||||
*
|
||||
* Returns the serial number that represents the date that is the indicated number of months
|
||||
* before or after a specified date (the start_date).
|
||||
* Use EDATE to calculate maturity dates or due dates that fall on the same day of the month
|
||||
* as the date of issue.
|
||||
*
|
||||
* Excel Function:
|
||||
* EDATE(dateValue,adjustmentMonths)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* 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 array|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
|
||||
*/
|
||||
public static function adjust(mixed $dateValue, array|string|bool|float|int $adjustmentMonths): DateTime|float|int|string|array
|
||||
{
|
||||
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);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$dateValue = floor($dateValue);
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths);
|
||||
|
||||
return Helpers::returnIn3FormatsObject($PHPDateObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* EOMONTH.
|
||||
*
|
||||
* Returns the date value for the last day of the month that is the indicated number of months
|
||||
* before or after start_date.
|
||||
* Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
|
||||
*
|
||||
* Excel Function:
|
||||
* EOMONTH(dateValue,adjustmentMonths)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* 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 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(mixed $dateValue, array|float|int|bool|string $adjustmentMonths): mixed
|
||||
{
|
||||
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);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$dateValue = floor($dateValue);
|
||||
$adjustmentMonths = floor($adjustmentMonths);
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1);
|
||||
$adjustDays = (int) $PHPDateObject->format('d');
|
||||
$adjustDaysString = '-' . $adjustDays . ' days';
|
||||
$PHPDateObject->modify($adjustDaysString);
|
||||
|
||||
return Helpers::returnIn3FormatsObject($PHPDateObject);
|
||||
}
|
||||
}
|
||||
119
lib/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php
Normal file
119
lib/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
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.
|
||||
*
|
||||
* Returns the number of whole working days between start_date and end_date. Working days
|
||||
* exclude weekends and any dates identified in holidays.
|
||||
* Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days
|
||||
* worked during a specific term.
|
||||
*
|
||||
* Excel Function:
|
||||
* NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]])
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
public static function count(mixed $startDate, mixed $endDate, mixed ...$dateArgs): array|string|int
|
||||
{
|
||||
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);
|
||||
$eDate = Helpers::getDateValue($endDate);
|
||||
$startDate = min($sDate, $eDate);
|
||||
$endDate = max($sDate, $eDate);
|
||||
// Get the optional days
|
||||
$dateArgs = Functions::flattenArray($dateArgs);
|
||||
// Test any extra holiday parameters
|
||||
$holidayArray = [];
|
||||
foreach ($dateArgs as $holidayDate) {
|
||||
$holidayArray[] = Helpers::getDateValue($holidayDate);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$startDow = self::calcStartDow($startDate);
|
||||
$endDow = self::calcEndDow($endDate);
|
||||
$wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5;
|
||||
$partWeekDays = self::calcPartWeekDays($startDow, $endDow);
|
||||
|
||||
// Test any extra holiday parameters
|
||||
$holidayCountedArray = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
|
||||
if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) {
|
||||
--$partWeekDays;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate);
|
||||
}
|
||||
|
||||
private static function calcStartDow(float $startDate): int
|
||||
{
|
||||
$startDow = 6 - (int) Week::day($startDate, 2);
|
||||
if ($startDow < 0) {
|
||||
$startDow = 5;
|
||||
}
|
||||
|
||||
return $startDow;
|
||||
}
|
||||
|
||||
private static function calcEndDow(float $endDate): int
|
||||
{
|
||||
$endDow = (int) Week::day($endDate, 2);
|
||||
if ($endDow >= 6) {
|
||||
$endDow = 0;
|
||||
}
|
||||
|
||||
return $endDow;
|
||||
}
|
||||
|
||||
private static function calcPartWeekDays(int $startDow, int $endDow): int
|
||||
{
|
||||
$partWeekDays = $endDow + $startDow;
|
||||
if ($partWeekDays > 5) {
|
||||
$partWeekDays -= 5;
|
||||
}
|
||||
|
||||
return $partWeekDays;
|
||||
}
|
||||
|
||||
private static function applySign(int $result, float $sDate, float $eDate): int
|
||||
{
|
||||
return ($sDate > $eDate) ? -$result : $result;
|
||||
}
|
||||
}
|
||||
130
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php
Normal file
130
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
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\Date as SharedDateHelper;
|
||||
|
||||
class Time
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* TIME.
|
||||
*
|
||||
* The TIME function returns a value that represents a particular time.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TIME(hour,minute,second)
|
||||
*
|
||||
* @param null|array|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.
|
||||
* 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.
|
||||
* 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,
|
||||
* 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 fromHMS(array|int|float|bool|null|string $hour, array|int|float|bool|null|string $minute, array|int|float|bool|null|string $second): array|string|float|int|DateTime
|
||||
{
|
||||
if (is_array($hour) || is_array($minute) || is_array($second)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $hour, $minute, $second);
|
||||
}
|
||||
|
||||
try {
|
||||
$hour = self::toIntWithNullBool($hour);
|
||||
$minute = self::toIntWithNullBool($minute);
|
||||
$second = self::toIntWithNullBool($second);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
self::adjustSecond($second, $minute);
|
||||
self::adjustMinute($minute, $hour);
|
||||
|
||||
if ($hour > 23) {
|
||||
$hour = $hour % 24;
|
||||
} elseif ($hour < 0) {
|
||||
return ExcelError::NAN();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
$calendar = SharedDateHelper::getExcelCalendar();
|
||||
$date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900);
|
||||
|
||||
return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
|
||||
}
|
||||
if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600
|
||||
}
|
||||
// RETURNDATE_PHP_DATETIME_OBJECT
|
||||
// Hour has already been normalized (0-23) above
|
||||
$phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second);
|
||||
|
||||
return $phpDateObject;
|
||||
}
|
||||
|
||||
private static function adjustSecond(int &$second, int &$minute): void
|
||||
{
|
||||
if ($second < 0) {
|
||||
$minute += floor($second / 60);
|
||||
$second = 60 - abs($second % 60);
|
||||
if ($second == 60) {
|
||||
$second = 0;
|
||||
}
|
||||
} elseif ($second >= 60) {
|
||||
$minute += floor($second / 60);
|
||||
$second = $second % 60;
|
||||
}
|
||||
}
|
||||
|
||||
private static function adjustMinute(int &$minute, int &$hour): void
|
||||
{
|
||||
if ($minute < 0) {
|
||||
$hour += floor($minute / 60);
|
||||
$minute = 60 - abs($minute % 60);
|
||||
if ($minute == 60) {
|
||||
$minute = 0;
|
||||
}
|
||||
} elseif ($minute >= 60) {
|
||||
$hour += floor($minute / 60);
|
||||
$minute = $minute % 60;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value expect int
|
||||
*/
|
||||
private static function toIntWithNullBool(mixed $value): int
|
||||
{
|
||||
$value = $value ?? 0;
|
||||
if (is_bool($value)) {
|
||||
$value = (int) $value;
|
||||
}
|
||||
if (!is_numeric($value)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
135
lib/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php
Normal file
135
lib/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class TimeParts
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* HOUROFDAY.
|
||||
*
|
||||
* Returns the hour of a time value.
|
||||
* The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.).
|
||||
*
|
||||
* Excel Function:
|
||||
* HOUR(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
* Or can be an array of date/time values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function hour(mixed $timeValue): array|string|int
|
||||
{
|
||||
if (is_array($timeValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
|
||||
}
|
||||
|
||||
try {
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
|
||||
SharedDateHelper::roundMicroseconds($timeValue);
|
||||
|
||||
return (int) $timeValue->format('H');
|
||||
}
|
||||
|
||||
/**
|
||||
* MINUTE.
|
||||
*
|
||||
* Returns the minutes of a time value.
|
||||
* The minute is given as an integer, ranging from 0 to 59.
|
||||
*
|
||||
* Excel Function:
|
||||
* MINUTE(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
* Or can be an array of date/time values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function minute(mixed $timeValue): array|string|int
|
||||
{
|
||||
if (is_array($timeValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
|
||||
}
|
||||
|
||||
try {
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
|
||||
SharedDateHelper::roundMicroseconds($timeValue);
|
||||
|
||||
return (int) $timeValue->format('i');
|
||||
}
|
||||
|
||||
/**
|
||||
* SECOND.
|
||||
*
|
||||
* Returns the seconds of a time value.
|
||||
* The minute is given as an integer, ranging from 0 to 59.
|
||||
*
|
||||
* Excel Function:
|
||||
* SECOND(timeValue)
|
||||
*
|
||||
* @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard time string
|
||||
* Or can be an array of date/time values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function second(mixed $timeValue): array|string|int
|
||||
{
|
||||
if (is_array($timeValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
|
||||
}
|
||||
|
||||
try {
|
||||
Helpers::nullFalseTrueToNumber($timeValue);
|
||||
if (!is_numeric($timeValue)) {
|
||||
$timeValue = Helpers::getTimeValue($timeValue);
|
||||
}
|
||||
Helpers::validateNotNegative($timeValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$timeValue = fmod($timeValue, 1);
|
||||
$timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
|
||||
SharedDateHelper::roundMicroseconds($timeValue);
|
||||
|
||||
return (int) $timeValue->format('s');
|
||||
}
|
||||
}
|
||||
80
lib/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
Normal file
80
lib/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use Datetime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class TimeValue
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* TIMEVALUE.
|
||||
*
|
||||
* Returns a value that represents a particular time.
|
||||
* Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp
|
||||
* value.
|
||||
*
|
||||
* NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
|
||||
* format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
|
||||
*
|
||||
* Excel Function:
|
||||
* TIMEVALUE(timeValue)
|
||||
*
|
||||
* @param null|array|bool|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,
|
||||
* 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 fromString(null|array|string|int|bool $timeValue): array|string|Datetime|int|float
|
||||
{
|
||||
if (is_array($timeValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
|
||||
}
|
||||
|
||||
// try to parse as time iff there is at least one digit
|
||||
if (is_string($timeValue) && preg_match('/\\d/', $timeValue) !== 1) {
|
||||
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] = ($arraySplit[0] % 24);
|
||||
$timeValue = implode(':', $arraySplit);
|
||||
}
|
||||
|
||||
$PHPDateArray = Helpers::dateParse($timeValue);
|
||||
$retValue = ExcelError::VALUE();
|
||||
if (Helpers::dateParseSucceeded($PHPDateArray)) {
|
||||
$hour = $PHPDateArray['hour'];
|
||||
$minute = $PHPDateArray['minute'];
|
||||
$second = $PHPDateArray['second'];
|
||||
// OpenOffice-specific code removed - it works just like Excel
|
||||
$excelDateValue = SharedDateHelper::formattedPHPToExcel(1900, 1, 1, $hour, $minute, $second) - 1;
|
||||
|
||||
$retType = Functions::getReturnDateType();
|
||||
if ($retType === Functions::RETURNDATE_EXCEL) {
|
||||
$retValue = (float) $excelDateValue;
|
||||
} elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
|
||||
$retValue = (int) SharedDateHelper::excelToTimestamp($excelDateValue + 25569) - 3600;
|
||||
} else {
|
||||
$retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']);
|
||||
}
|
||||
}
|
||||
|
||||
return $retValue;
|
||||
}
|
||||
}
|
||||
274
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php
Normal file
274
lib/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
|
||||
|
||||
class Week
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* WEEKNUM.
|
||||
*
|
||||
* Returns the week of the year for a specified date.
|
||||
* The WEEKNUM function considers the week containing January 1 to be the first week of the year.
|
||||
* However, there is a European standard that defines the first week as the one with the majority
|
||||
* of days (four or more) falling in the new year. This means that for years in which there are
|
||||
* three days or less in the first week of January, the WEEKNUM function returns week numbers
|
||||
* that are incorrect according to the European standard.
|
||||
*
|
||||
* Excel Function:
|
||||
* WEEKNUM(dateValue[,style])
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of date values
|
||||
* @param array|int $method Week begins on Sunday or Monday
|
||||
* 1 or omitted Week begins on Sunday.
|
||||
* 2 Week begins on Monday.
|
||||
* 11 Week begins on Monday.
|
||||
* 12 Week begins on Tuesday.
|
||||
* 13 Week begins on Wednesday.
|
||||
* 14 Week begins on Thursday.
|
||||
* 15 Week begins on Friday.
|
||||
* 16 Week begins on Saturday.
|
||||
* 17 Week begins on Sunday.
|
||||
* 21 ISO (Jan. 4 is week 1, begins on Monday).
|
||||
* Or can be an array of methods
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function number(mixed $dateValue, array|int|string|null $method = Constants::STARTWEEK_SUNDAY): array|int|string
|
||||
{
|
||||
if (is_array($dateValue) || is_array($method)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $method);
|
||||
}
|
||||
|
||||
$origDateValueNull = empty($dateValue);
|
||||
|
||||
try {
|
||||
$method = self::validateMethod($method);
|
||||
if ($dateValue === null) { // boolean not allowed
|
||||
$dateValue = (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 || $method === Constants::DOW_SUNDAY) ? 0 : 1;
|
||||
}
|
||||
$dateValue = self::validateDateValue($dateValue);
|
||||
if (!$dateValue && self::buggyWeekNum1900($method)) {
|
||||
// This seems to be an additional Excel bug.
|
||||
return 0;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
if ($method == Constants::STARTWEEK_MONDAY_ISO) {
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('W');
|
||||
}
|
||||
if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) {
|
||||
return 0;
|
||||
}
|
||||
Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches
|
||||
$dayOfYear = (int) $PHPDateObject->format('z');
|
||||
$PHPDateObject->modify('-' . $dayOfYear . ' days');
|
||||
$firstDayOfFirstWeek = (int) $PHPDateObject->format('w');
|
||||
$daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
|
||||
$daysInFirstWeek += 7 * !$daysInFirstWeek;
|
||||
$endFirstWeek = $daysInFirstWeek - 1;
|
||||
$weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
|
||||
|
||||
return (int) $weekOfYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* ISOWEEKNUM.
|
||||
*
|
||||
* Returns the ISO 8601 week number of the year for a specified date.
|
||||
*
|
||||
* Excel Function:
|
||||
* ISOWEEKNUM(dateValue)
|
||||
*
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of date values
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function isoWeekNumber(mixed $dateValue): array|int|string
|
||||
{
|
||||
if (is_array($dateValue)) {
|
||||
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
|
||||
}
|
||||
|
||||
if (self::apparentBug($dateValue)) {
|
||||
return 52;
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
|
||||
return (int) $PHPDateObject->format('W');
|
||||
}
|
||||
|
||||
/**
|
||||
* WEEKDAY.
|
||||
*
|
||||
* Returns the day of the week for a specified date. The day is given as an integer
|
||||
* ranging from 0 to 7 (dependent on the requested style).
|
||||
*
|
||||
* Excel Function:
|
||||
* WEEKDAY(dateValue[,style])
|
||||
*
|
||||
* @param null|array|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
|
||||
* 1 or omitted Numbers 1 (Sunday) through 7 (Saturday).
|
||||
* 2 Numbers 1 (Monday) through 7 (Sunday).
|
||||
* 3 Numbers 0 (Monday) through 6 (Sunday).
|
||||
* Or can be an array of styles
|
||||
*
|
||||
* @return array|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
|
||||
*/
|
||||
public static function day(null|array|float|int|string|bool $dateValue, mixed $style = 1): array|string|int
|
||||
{
|
||||
if (is_array($dateValue) || is_array($style)) {
|
||||
return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style);
|
||||
}
|
||||
|
||||
try {
|
||||
$dateValue = Helpers::getDateValue($dateValue);
|
||||
$style = self::validateStyle($style);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// Execute function
|
||||
$PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
|
||||
Helpers::silly1900($PHPDateObject);
|
||||
$DoW = (int) $PHPDateObject->format('w');
|
||||
|
||||
switch ($style) {
|
||||
case 1:
|
||||
++$DoW;
|
||||
|
||||
break;
|
||||
case 2:
|
||||
$DoW = self::dow0Becomes7($DoW);
|
||||
|
||||
break;
|
||||
case 3:
|
||||
$DoW = self::dow0Becomes7($DoW) - 1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $DoW;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $style expect int
|
||||
*/
|
||||
private static function validateStyle(mixed $style): int
|
||||
{
|
||||
if (!is_numeric($style)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
$style = (int) $style;
|
||||
if (($style < 1) || ($style > 3)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
private static function dow0Becomes7(int $DoW): int
|
||||
{
|
||||
return ($DoW === 0) ? 7 : $DoW;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
*/
|
||||
private static function apparentBug(mixed $dateValue): bool
|
||||
{
|
||||
if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
|
||||
if (is_bool($dateValue)) {
|
||||
return true;
|
||||
}
|
||||
if (is_numeric($dateValue) && !((int) $dateValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate dateValue parameter.
|
||||
*/
|
||||
private static function validateDateValue(mixed $dateValue): float
|
||||
{
|
||||
if (is_bool($dateValue)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
return Helpers::getDateValue($dateValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate method parameter.
|
||||
*/
|
||||
private static function validateMethod(mixed $method): int
|
||||
{
|
||||
if ($method === null) {
|
||||
$method = Constants::STARTWEEK_SUNDAY;
|
||||
}
|
||||
|
||||
if (!is_numeric($method)) {
|
||||
throw new Exception(ExcelError::VALUE());
|
||||
}
|
||||
|
||||
$method = (int) $method;
|
||||
if (!array_key_exists($method, Constants::METHODARR)) {
|
||||
throw new Exception(ExcelError::NAN());
|
||||
}
|
||||
$method = Constants::METHODARR[$method];
|
||||
|
||||
return $method;
|
||||
}
|
||||
|
||||
private static function buggyWeekNum1900(int $method): bool
|
||||
{
|
||||
return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900;
|
||||
}
|
||||
|
||||
private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool
|
||||
{
|
||||
// This appears to be another Excel bug.
|
||||
|
||||
return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904
|
||||
&& !$origNull && $dateObject->format('Y-m-d') === '1904-01-01';
|
||||
}
|
||||
}
|
||||
198
lib/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php
Normal file
198
lib/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
|
||||
class WorkDay
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* WORKDAY.
|
||||
*
|
||||
* Returns the date that is the indicated number of working days before or after a date (the
|
||||
* starting date). Working days exclude weekends and any dates identified as holidays.
|
||||
* Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
|
||||
* delivery times, or the number of days of work performed.
|
||||
*
|
||||
* Excel Function:
|
||||
* WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
|
||||
*
|
||||
* @param array|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
|
||||
* 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|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 for the $startDate or $endDays,arguments, then the returned result
|
||||
* will also be an array with matching dimensions
|
||||
*/
|
||||
public static function date(mixed $startDate, array|int|string $endDays, mixed ...$dateArgs): mixed
|
||||
{
|
||||
if (is_array($startDate) || is_array($endDays)) {
|
||||
return self::evaluateArrayArgumentsSubset(
|
||||
[self::class, __FUNCTION__],
|
||||
2,
|
||||
$startDate,
|
||||
$endDays,
|
||||
...$dateArgs
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieve the mandatory start date and days that are referenced in the function definition
|
||||
try {
|
||||
$startDate = Helpers::getDateValue($startDate);
|
||||
$endDays = Helpers::validateNumericNull($endDays);
|
||||
$holidayArray = array_map([Helpers::class, 'getDateValue'], Functions::flattenArray($dateArgs));
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
$startDate = (float) floor($startDate);
|
||||
$endDays = (int) floor($endDays);
|
||||
// If endDays is 0, we always return startDate
|
||||
if ($endDays == 0) {
|
||||
return $startDate;
|
||||
}
|
||||
if ($endDays < 0) {
|
||||
return self::decrementing($startDate, $endDays, $holidayArray);
|
||||
}
|
||||
|
||||
return self::incrementing($startDate, $endDays, $holidayArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use incrementing logic to determine Workday.
|
||||
*/
|
||||
private static function incrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
|
||||
{
|
||||
// Adjust the start date if it falls over a weekend
|
||||
$startDoW = self::getWeekDay($startDate, 3);
|
||||
if ($startDoW >= 5) {
|
||||
$startDate += 7 - $startDoW;
|
||||
--$endDays;
|
||||
}
|
||||
|
||||
// Add endDays
|
||||
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
||||
$endDays = $endDays % 5;
|
||||
while ($endDays > 0) {
|
||||
++$endDate;
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDow = self::getWeekDay($endDate, 3);
|
||||
if ($endDow >= 5) {
|
||||
$endDate += 7 - $endDow;
|
||||
}
|
||||
--$endDays;
|
||||
}
|
||||
|
||||
// Test any extra holiday parameters
|
||||
if (!empty($holidayArray)) {
|
||||
$endDate = self::incrementingArray($startDate, $endDate, $holidayArray);
|
||||
}
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($endDate);
|
||||
}
|
||||
|
||||
private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
||||
{
|
||||
$holidayCountedArray = $holidayDates = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (self::getWeekDay($holidayDate, 3) < 5) {
|
||||
$holidayDates[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
sort($holidayDates, SORT_NUMERIC);
|
||||
foreach ($holidayDates as $holidayDate) {
|
||||
if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
|
||||
if (!in_array($holidayDate, $holidayCountedArray)) {
|
||||
++$endDate;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDoW = self::getWeekDay($endDate, 3);
|
||||
if ($endDoW >= 5) {
|
||||
$endDate += 7 - $endDoW;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use decrementing logic to determine Workday.
|
||||
*/
|
||||
private static function decrementing(float $startDate, int $endDays, array $holidayArray): float|int|DateTime
|
||||
{
|
||||
// Adjust the start date if it falls over a weekend
|
||||
$startDoW = self::getWeekDay($startDate, 3);
|
||||
if ($startDoW >= 5) {
|
||||
$startDate += -$startDoW + 4;
|
||||
++$endDays;
|
||||
}
|
||||
|
||||
// Add endDays
|
||||
$endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
|
||||
$endDays = $endDays % 5;
|
||||
while ($endDays < 0) {
|
||||
--$endDate;
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDow = self::getWeekDay($endDate, 3);
|
||||
if ($endDow >= 5) {
|
||||
$endDate += 4 - $endDow;
|
||||
}
|
||||
++$endDays;
|
||||
}
|
||||
|
||||
// Test any extra holiday parameters
|
||||
if (!empty($holidayArray)) {
|
||||
$endDate = self::decrementingArray($startDate, $endDate, $holidayArray);
|
||||
}
|
||||
|
||||
return Helpers::returnIn3FormatsFloat($endDate);
|
||||
}
|
||||
|
||||
private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
|
||||
{
|
||||
$holidayCountedArray = $holidayDates = [];
|
||||
foreach ($holidayArray as $holidayDate) {
|
||||
if (self::getWeekDay($holidayDate, 3) < 5) {
|
||||
$holidayDates[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
rsort($holidayDates, SORT_NUMERIC);
|
||||
foreach ($holidayDates as $holidayDate) {
|
||||
if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
|
||||
if (!in_array($holidayDate, $holidayCountedArray)) {
|
||||
--$endDate;
|
||||
$holidayCountedArray[] = $holidayDate;
|
||||
}
|
||||
}
|
||||
// Adjust the calculated end date if it falls over a weekend
|
||||
$endDoW = self::getWeekDay($endDate, 3);
|
||||
/** int $endDoW */
|
||||
if ($endDoW >= 5) {
|
||||
$endDate += -$endDoW + 4;
|
||||
}
|
||||
}
|
||||
|
||||
return $endDate;
|
||||
}
|
||||
|
||||
private static function getWeekDay(float $date, int $wd): int
|
||||
{
|
||||
$result = Functions::scalar(Week::day($date, $wd));
|
||||
|
||||
return is_int($result) ? $result : -1;
|
||||
}
|
||||
}
|
||||
124
lib/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
Normal file
124
lib/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
|
||||
|
||||
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\Date as SharedDateHelper;
|
||||
|
||||
class YearFrac
|
||||
{
|
||||
use ArrayEnabled;
|
||||
|
||||
/**
|
||||
* YEARFRAC.
|
||||
*
|
||||
* Calculates the fraction of the year represented by the number of whole days between two dates
|
||||
* (the start_date and the end_date).
|
||||
* Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or
|
||||
* obligations to assign to a specific term.
|
||||
*
|
||||
* Excel Function:
|
||||
* YEARFRAC(startDate,endDate[,method])
|
||||
* See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
|
||||
* for description of algorithm used in Excel
|
||||
*
|
||||
* @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
|
||||
* PHP DateTime object, or a standard date string
|
||||
* Or can be an array of values
|
||||
* @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
|
||||
* 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 array|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
|
||||
*/
|
||||
public static function fraction(mixed $startDate, mixed $endDate, array|int|string|null $method = 0): array|string|int|float
|
||||
{
|
||||
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);
|
||||
$eDate = Helpers::getDateValue($endDate);
|
||||
$sDate = self::excelBug($sDate, $startDate, $endDate, $method);
|
||||
$eDate = self::excelBug($eDate, $endDate, $startDate, $method);
|
||||
$startDate = min($sDate, $eDate);
|
||||
$endDate = max($sDate, $eDate);
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return match ($method) {
|
||||
0 => Functions::scalar(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,
|
||||
default => ExcelError::NAN(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel 1900 calendar treats date argument of null as 1900-01-00. Really.
|
||||
*/
|
||||
private static function excelBug(float $sDate, mixed $startDate, mixed $endDate, int $method): float
|
||||
{
|
||||
if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
|
||||
if ($endDate === null && $startDate !== null) {
|
||||
if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) {
|
||||
$sDate += 2;
|
||||
} else {
|
||||
++$sDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sDate;
|
||||
}
|
||||
|
||||
private static function method1(float $startDate, float $endDate): float
|
||||
{
|
||||
$days = Functions::scalar(Difference::interval($startDate, $endDate));
|
||||
$startYear = (int) DateParts::year($startDate);
|
||||
$endYear = (int) DateParts::year($endDate);
|
||||
$years = $endYear - $startYear + 1;
|
||||
$startMonth = (int) DateParts::month($startDate);
|
||||
$startDay = (int) DateParts::day($startDate);
|
||||
$endMonth = (int) DateParts::month($endDate);
|
||||
$endDay = (int) DateParts::day($endDate);
|
||||
$startMonthDay = 100 * $startMonth + $startDay;
|
||||
$endMonthDay = 100 * $endMonth + $endDay;
|
||||
if ($years == 1) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear);
|
||||
} elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
|
||||
if (Helpers::isLeapYear($startYear)) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229);
|
||||
} elseif (Helpers::isLeapYear($endYear)) {
|
||||
$tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229);
|
||||
} else {
|
||||
$tmpCalcAnnualBasis = 365;
|
||||
}
|
||||
} else {
|
||||
$tmpCalcAnnualBasis = 0;
|
||||
for ($year = $startYear; $year <= $endYear; ++$year) {
|
||||
$tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year);
|
||||
}
|
||||
$tmpCalcAnnualBasis /= $years;
|
||||
}
|
||||
|
||||
return $days / $tmpCalcAnnualBasis;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue