From 9beacd21be4725bc2a73aebd3b74df75b7658d09 Mon Sep 17 00:00:00 2001 From: oleibman Date: Sun, 21 Mar 2021 01:12:05 -0700 Subject: [PATCH] Complete Breakup Of Calculation/DateTime Functions (#1937) * Complete Breakup Of Calculation/DateTime Functions In conjunction with parallel breakups happening in other areas of Calculation, this change breaks up all the DateTime functions into their own classes. All methods remaining in DateTime itself have a doc block deprecation notice, and consist only of stub code to call the replacement methods. Coverage of DateTime itself and all the replacement methods is 100%. There is only one substantive change to the code (see next paragraph). Among the non-substantive changes, it now adopts the same parsing technique (throwing and catching exceptions) already in use in Engineering and MathTrig. Boolean parameters are allowed in lieu of numbers when Excel allows them. Most of the code changes involve refactoring due to the need to avoid Scrutinizer "complexity" failures in what it will consider to be new methods. Issue #1936 was opened just as I was staging this. It is now fixed. One existing WORKDAY test was wrong (noted in a comment in the test data file), and a bunch of new tests are added. I found it confusing to use DateTime as a node of the the class name since most of the methods invoke native DateTime methods. So, everything is moved to directory DateTimeExcel, and that is what is used in the class names. There are several follow-up activities that I am planning to undertake if this PR is merged. - ODS supports dates well before 1900. There are exactly 2 assertions for this functionality. More are needed (and some functions might have to change to accept this). - WEEKDAY has some poorly documented extra options for "style" which are not yet implemented. - Most tests have been changed to use a formula as entered on a spreadsheet rather than a direct call to the method which implements the formula. There are 3 exceptions at this time. WORKDAY and NETWORKDAYS, which include arrays as part of their parameters, are more complicated than most. YEARFRAC was just too large to deal with now. - There are direct calls to the now-deprecated methods in both source code and tests, mostly in Financial code, but possibly in others as well. These need to be changed. - Some constants, none "officially" documented, remain in the original class. These should be either deleted or marked deprecated. I wasn't sure if deprecation was even possible (or desirable), and did not want that to be something which would cause Scrutinizer to fail the change. * Deprecate Now-unused Constants, Fix Yearfrac bug, Change 3 Tests Add new DateTime/Constants class, initially populated with constants used in Weeknum. MS has another inconsistency with how it handles null cells in Yearfrac. Change PhpSpreadsheet to behave compatibly with this bug. I have modified YearFrac, WorkDay, and NetworkDays tests to be more to my liking. Many tests added to YearFrac because of the bug above. Only minor modifications to the existing tests for the others. --- .../Calculation/Calculation.php | 46 +- src/PhpSpreadsheet/Calculation/DateTime.php | 1291 +++-------------- .../Calculation/DateTimeExcel/Constants.php | 37 + .../Calculation/DateTimeExcel/DateDif.php | 146 ++ .../Calculation/DateTimeExcel/DateValue.php | 151 ++ .../Calculation/DateTimeExcel/Datefunc.php | 168 +++ .../Calculation/DateTimeExcel/Day.php | 61 + .../Calculation/DateTimeExcel/Days.php | 51 + .../Calculation/DateTimeExcel/Days360.php | 106 ++ .../Calculation/DateTimeExcel/EDate.php | 45 + .../Calculation/DateTimeExcel/EoMonth.php | 47 + .../Calculation/DateTimeExcel/Helpers.php | 291 ++++ .../Calculation/DateTimeExcel/Hour.php | 44 + .../Calculation/DateTimeExcel/IsoWeekNum.php | 55 + .../Calculation/DateTimeExcel/Minute.php | 44 + .../Calculation/DateTimeExcel/Month.php | 40 + .../Calculation/DateTimeExcel/NetworkDays.php | 102 ++ .../Calculation/DateTimeExcel/Now.php | 34 + .../Calculation/DateTimeExcel/Second.php | 44 + .../Calculation/DateTimeExcel/Time.php | 116 ++ .../Calculation/DateTimeExcel/TimeValue.php | 61 + .../Calculation/DateTimeExcel/Today.php | 34 + .../Calculation/DateTimeExcel/WeekDay.php | 80 + .../Calculation/DateTimeExcel/WeekNum.php | 130 ++ .../Calculation/DateTimeExcel/WorkDay.php | 182 +++ .../Calculation/DateTimeExcel/Year.php | 40 + .../Calculation/DateTimeExcel/YearFrac.php | 120 ++ .../Functions/DateTime/AllSetupTeardown.php | 71 + .../Functions/DateTime/DateDifTest.php | 26 +- .../Functions/DateTime/DateTest.php | 56 +- .../Functions/DateTime/DateValueTest.php | 53 +- .../Functions/DateTime/DayTest.php | 65 +- .../Functions/DateTime/Days360Test.php | 27 +- .../Functions/DateTime/DaysTest.php | 43 +- .../Functions/DateTime/EDateTest.php | 33 +- .../Functions/DateTime/EoMonthTest.php | 36 +- .../Functions/DateTime/HourTest.php | 24 +- .../Functions/DateTime/IsoWeekNumTest.php | 44 +- .../Functions/DateTime/MinuteTest.php | 24 +- .../Functions/DateTime/MonthTest.php | 24 +- .../Functions/DateTime/MovedFunctionsTest.php | 63 + .../Functions/DateTime/NetworkDaysTest.php | 50 +- .../Functions/DateTime/NowTest.php | 8 +- .../Functions/DateTime/SecondTest.php | 24 +- .../Functions/DateTime/TimeTest.php | 52 +- .../Functions/DateTime/TimeValueTest.php | 28 +- .../Functions/DateTime/TodayTest.php | 34 + .../Functions/DateTime/WeekDayTest.php | 35 +- .../Functions/DateTime/WeekNumTest.php | 51 +- .../Functions/DateTime/WorkDayTest.php | 50 +- .../Functions/DateTime/YearFracTest.php | 42 +- .../Functions/DateTime/YearTest.php | 24 +- tests/data/Calculation/DateTime/DATE.php | 393 +---- tests/data/Calculation/DateTime/DATEDIF.php | 528 ++----- tests/data/Calculation/DateTime/DATEVALUE.php | 379 +---- tests/data/Calculation/DateTime/DAY.php | 81 +- .../Calculation/DateTime/DAYOpenOffice.php | 19 + tests/data/Calculation/DateTime/DAYS.php | 97 +- tests/data/Calculation/DateTime/DAYS360.php | 174 +-- tests/data/Calculation/DateTime/EDATE.php | 80 +- tests/data/Calculation/DateTime/EOMONTH.php | 92 +- tests/data/Calculation/DateTime/HOUR.php | 65 +- .../data/Calculation/DateTime/ISOWEEKNUM.php | 69 +- .../Calculation/DateTime/ISOWEEKNUM1904.php | 33 + tests/data/Calculation/DateTime/MINUTE.php | 65 +- tests/data/Calculation/DateTime/MONTH.php | 68 +- .../data/Calculation/DateTime/NETWORKDAYS.php | 19 +- tests/data/Calculation/DateTime/SECOND.php | 65 +- tests/data/Calculation/DateTime/TIME.php | 112 +- tests/data/Calculation/DateTime/TIMEVALUE.php | 70 +- tests/data/Calculation/DateTime/WEEKDAY.php | 156 +- tests/data/Calculation/DateTime/WEEKNUM.php | 275 +--- .../data/Calculation/DateTime/WEEKNUM1904.php | 92 ++ tests/data/Calculation/DateTime/WORKDAY.php | 28 +- tests/data/Calculation/DateTime/YEAR.php | 62 +- tests/data/Calculation/DateTime/YEARFRAC.php | 50 + 76 files changed, 3874 insertions(+), 3751 deletions(-) create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php create mode 100644 src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php create mode 100644 tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php create mode 100644 tests/data/Calculation/DateTime/DAYOpenOffice.php create mode 100644 tests/data/Calculation/DateTime/ISOWEEKNUM1904.php create mode 100644 tests/data/Calculation/DateTime/WEEKNUM1904.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 36f5efe4..dbea0850 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -755,17 +755,17 @@ class Calculation ], 'DATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATE'], + 'functionCall' => [DateTimeExcel\Datefunc::class, 'funcDate'], 'argumentCount' => '3', ], 'DATEDIF' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEDIF'], + 'functionCall' => [DateTimeExcel\DateDif::class, 'funcDateDif'], 'argumentCount' => '2,3', ], 'DATEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEVALUE'], + 'functionCall' => [DateTimeExcel\DateValue::class, 'funcDateValue'], 'argumentCount' => '1', ], 'DAVERAGE' => [ @@ -775,17 +775,17 @@ class Calculation ], 'DAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYOFMONTH'], + 'functionCall' => [DateTimeExcel\Day::class, 'funcDay'], 'argumentCount' => '1', ], 'DAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS'], + 'functionCall' => [DateTimeExcel\Days::class, 'funcDays'], 'argumentCount' => '2', ], 'DAYS360' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS360'], + 'functionCall' => [DateTimeExcel\Days360::class, 'funcDays360'], 'argumentCount' => '2,3', ], 'DB' => [ @@ -920,7 +920,7 @@ class Calculation ], 'EDATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EDATE'], + 'functionCall' => [DateTimeExcel\EDate::class, 'funcEDate'], 'argumentCount' => '2', ], 'EFFECT' => [ @@ -935,7 +935,7 @@ class Calculation ], 'EOMONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EOMONTH'], + 'functionCall' => [DateTimeExcel\EoMonth::class, 'funcEoMonth'], 'argumentCount' => '2', ], 'ERF' => [ @@ -1237,7 +1237,7 @@ class Calculation ], 'HOUR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'HOUROFDAY'], + 'functionCall' => [DateTimeExcel\Hour::class, 'funcHour'], 'argumentCount' => '1', ], 'HYPERLINK' => [ @@ -1501,7 +1501,7 @@ class Calculation ], 'ISOWEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'ISOWEEKNUM'], + 'functionCall' => [DateTimeExcel\IsoWeekNum::class, 'funcIsoWeekNum'], 'argumentCount' => '1', ], 'ISPMT' => [ @@ -1681,7 +1681,7 @@ class Calculation ], 'MINUTE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MINUTE'], + 'functionCall' => [DateTimeExcel\Minute::class, 'funcMinute'], 'argumentCount' => '1', ], 'MINVERSE' => [ @@ -1721,7 +1721,7 @@ class Calculation ], 'MONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MONTHOFYEAR'], + 'functionCall' => [DateTimeExcel\Month::class, 'funcMonth'], 'argumentCount' => '1', ], 'MROUND' => [ @@ -1761,7 +1761,7 @@ class Calculation ], 'NETWORKDAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'NETWORKDAYS'], + 'functionCall' => [DateTimeExcel\NetworkDays::class, 'funcNetworkDays'], 'argumentCount' => '2-3', ], 'NETWORKDAYS.INTL' => [ @@ -1821,7 +1821,7 @@ class Calculation ], 'NOW' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATETIMENOW'], + 'functionCall' => [DateTimeExcel\Now::class, 'funcNow'], 'argumentCount' => '0', ], 'NPER' => [ @@ -2175,7 +2175,7 @@ class Calculation ], 'SECOND' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'SECOND'], + 'functionCall' => [DateTimeExcel\Second::class, 'funcSecond'], 'argumentCount' => '1', ], 'SEQUENCE' => [ @@ -2421,12 +2421,12 @@ class Calculation ], 'TIME' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIME'], + 'functionCall' => [DateTimeExcel\Time::class, 'funcTime'], 'argumentCount' => '3', ], 'TIMEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIMEVALUE'], + 'functionCall' => [DateTimeExcel\TimeValue::class, 'funcTimeValue'], 'argumentCount' => '1', ], 'TINV' => [ @@ -2446,7 +2446,7 @@ class Calculation ], 'TODAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATENOW'], + 'functionCall' => [DateTimeExcel\Today::class, 'funcToday'], 'argumentCount' => '0', ], 'TRANSPOSE' => [ @@ -2571,12 +2571,12 @@ class Calculation ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKDAY'], + 'functionCall' => [DateTimeExcel\WeekDay::class, 'funcWeekDay'], 'argumentCount' => '1,2', ], 'WEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKNUM'], + 'functionCall' => [DateTimeExcel\WeekNum::class, 'funcWeekNum'], 'argumentCount' => '1,2', ], 'WEIBULL' => [ @@ -2591,7 +2591,7 @@ class Calculation ], 'WORKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WORKDAY'], + 'functionCall' => [DateTimeExcel\WorkDay::class, 'funcWorkDay'], 'argumentCount' => '2-3', ], 'WORKDAY.INTL' => [ @@ -2626,12 +2626,12 @@ class Calculation ], 'YEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEAR'], + 'functionCall' => [DateTimeExcel\Year::class, 'funcYear'], 'argumentCount' => '1', ], 'YEARFRAC' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEARFRAC'], + 'functionCall' => [DateTimeExcel\YearFrac::class, 'funcYearFrac'], 'argumentCount' => '2,3', ], 'YIELD' => [ diff --git a/src/PhpSpreadsheet/Calculation/DateTime.php b/src/PhpSpreadsheet/Calculation/DateTime.php index 64d72c2b..744d9589 100644 --- a/src/PhpSpreadsheet/Calculation/DateTime.php +++ b/src/PhpSpreadsheet/Calculation/DateTime.php @@ -2,127 +2,36 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use DateTimeImmutable; use DateTimeInterface; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class DateTime { /** * Identify if a year is a leap year or not. * + * @Deprecated 2.0.0 Use the method isLeapYear in the DateTimeExcel\Helpers class instead + * * @param int|string $year The year to test * * @return bool TRUE if the year is a leap year, otherwise FALSE */ public static function isLeapYear($year) { - return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); - } - - /** - * Return the number of days between two dates based on a 360 day calendar. - * - * @param int $startDay Day of month of the start date - * @param int $startMonth Month of the start date - * @param int $startYear Year of the start date - * @param int $endDay Day of month of the start date - * @param int $endMonth Month of the start date - * @param int $endYear Year of the start date - * @param bool $methodUS Whether to use the US method or the European method of calculation - * - * @return int Number of days between the start date and the end date - */ - private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) - { - if ($startDay == 31) { - --$startDay; - } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) { - $startDay = 30; - } - if ($endDay == 31) { - if ($methodUS && $startDay != 30) { - $endDay = 1; - if ($endMonth == 12) { - ++$endYear; - $endMonth = 1; - } else { - ++$endMonth; - } - } else { - $endDay = 30; - } - } - - return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + return DateTimeExcel\Helpers::isLeapYear($year); } /** * getDateValue. * + * @Deprecated 2.0.0 Use the method getDateValueNoThrow in the DateTimeExcel\Helpers class instead + * * @param mixed $dateValue * * @return mixed Excel date/time serial value, or string if error */ public static function getDateValue($dateValue) { - if (!is_numeric($dateValue)) { - if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) { - $dateValue = Date::PHPToExcel($dateValue); - } else { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $dateValue = self::DATEVALUE($dateValue); - Functions::setReturnDateType($saveReturnDateType); - } - } - - return $dateValue; - } - - /** - * getTimeValue. - * - * @param string $timeValue - * - * @return mixed Excel date/time serial value, or string if error - */ - private static function getTimeValue($timeValue) - { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $timeValue = self::TIMEVALUE($timeValue); - Functions::setReturnDateType($saveReturnDateType); - - return $timeValue; - } - - private static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) - { - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $oMonth = (int) $PHPDateObject->format('m'); - $oYear = (int) $PHPDateObject->format('Y'); - - $adjustmentMonthsString = (string) $adjustmentMonths; - if ($adjustmentMonths > 0) { - $adjustmentMonthsString = '+' . $adjustmentMonths; - } - if ($adjustmentMonths != 0) { - $PHPDateObject->modify($adjustmentMonthsString . ' months'); - } - $nMonth = (int) $PHPDateObject->format('m'); - $nYear = (int) $PHPDateObject->format('Y'); - - $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); - if ($monthDiff != $adjustmentMonths) { - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - } - - return $PHPDateObject; + return DateTimeExcel\Helpers::getDateValueNoThrow($dateValue); } /** @@ -136,6 +45,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcNow method in the DateTimeExcel\Now class instead + * * Excel Function: * NOW() * @@ -144,10 +55,7 @@ class DateTime */ public static function DATETIMENOW() { - $dti = new DateTimeImmutable(); - $dateArray = date_parse($dti->format('c')); - - return is_array($dateArray) ? self::returnIn3FormatsArray($dateArray) : Functions::VALUE(); + return DateTimeExcel\Now::funcNow(); } /** @@ -161,6 +69,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcToday method in the DateTimeExcel\Today class instead + * * Excel Function: * TODAY() * @@ -169,10 +79,7 @@ class DateTime */ public static function DATENOW() { - $dti = new DateTimeImmutable(); - $dateArray = date_parse($dti->format('c')); - - return is_array($dateArray) ? self::returnIn3FormatsArray($dateArray, true) : Functions::VALUE(); + return DateTimeExcel\Today::funcToday(); } /** @@ -183,6 +90,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcDate method in the DateTimeExcel\Date class instead + * * Excel Function: * DATE(year,month,day) * @@ -226,65 +135,7 @@ class DateTime */ public static function DATE($year = 0, $month = 1, $day = 1) { - $year = Functions::flattenSingleValue($year); - $month = Functions::flattenSingleValue($month); - $day = Functions::flattenSingleValue($day); - - if (($month !== null) && (!is_numeric($month))) { - $month = Date::monthStringToNumber($month); - } - - if (($day !== null) && (!is_numeric($day))) { - $day = Date::dayStringToNumber($day); - } - - $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; - $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; - $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; - if ( - (!is_numeric($year)) || - (!is_numeric($month)) || - (!is_numeric($day)) - ) { - return Functions::VALUE(); - } - $year = (int) $year; - $month = (int) $month; - $day = (int) $day; - - $baseYear = Date::getExcelCalendar(); - // Validate parameters - if ($year < ($baseYear - 1900)) { - return Functions::NAN(); - } - if ((($baseYear - 1900) != 0) && ($year < $baseYear) && ($year >= 1900)) { - return Functions::NAN(); - } - - if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { - $year += 1900; - } - - 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)) { - return Functions::NAN(); - } - - // Execute function - $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); - - return self::returnIn3FormatsFloat($excelDateValue); + return DateTimeExcel\Datefunc::funcDate($year, $month, $day); } /** @@ -295,6 +146,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcTime method in the DateTimeExcel\Time class instead + * * Excel Function: * TIME(hour,minute,second) * @@ -315,73 +168,7 @@ class DateTime */ public static function TIME($hour = 0, $minute = 0, $second = 0) { - $hour = Functions::flattenSingleValue($hour); - $minute = Functions::flattenSingleValue($minute); - $second = Functions::flattenSingleValue($second); - - if ($hour == '') { - $hour = 0; - } - if ($minute == '') { - $minute = 0; - } - if ($second == '') { - $second = 0; - } - - if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) { - return Functions::VALUE(); - } - $hour = (int) $hour; - $minute = (int) $minute; - $second = (int) $second; - - 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; - } - 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; - } - - if ($hour > 23) { - $hour = $hour % 24; - } elseif ($hour < 0) { - return Functions::NAN(); - } - - // Execute function - $retType = Functions::getReturnDateType(); - if ($retType === Functions::RETURNDATE_EXCEL) { - $date = 0; - $calendar = Date::getExcelCalendar(); - if ($calendar != Date::CALENDAR_WINDOWS_1900) { - $date = 1; - } - - return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); - } - if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { - return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 - } - // RETURNDATE_PHP_DATETIME_OBJECT - // Hour has already been normalized (0-23) above - $phpDateObject = new \DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); - - return $phpDateObject; + return DateTimeExcel\Time::funcTime($hour, $minute, $second); } /** @@ -394,6 +181,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcDateValue method in the DateTimeExcel\DateValue class instead + * * Excel Function: * DATEVALUE(dateValue) * @@ -411,186 +200,7 @@ class DateTime */ public static function DATEVALUE($dateValue = 1) { - $dti = new DateTimeImmutable(); - $baseYear = Date::getExcelCalendar(); - $dateValue = trim(Functions::flattenSingleValue($dateValue), '"'); - // Strip any ordinals because they're allowed in Excel (English only) - $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue); - // Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany) - $dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue); - - $yearFound = false; - $t1 = explode(' ', $dateValue); - $t = ''; - foreach ($t1 as &$t) { - if ((is_numeric($t)) && ($t > 31)) { - if ($yearFound) { - return Functions::VALUE(); - } - if ($t < 100) { - $t += 1900; - } - $yearFound = true; - } - } - if (count($t1) === 1) { - // We've been fed a time value without any date - return ((strpos($t, ':') === false)) ? Functions::Value() : 0.0; - } - 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'); - } - } - } - unset($t); - $dateValue = implode(' ', $t1); - - $PHPDateArray = date_parse($dateValue); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - // If original count was 1, we've already returned. - // If it was 2, we added another. - // Therefore, neither of the first 2 stroks below can fail. - $testVal1 = strtok($dateValue, '- '); - $testVal2 = strtok('- '); - $testVal3 = strtok('- ') ?: $dti->format('Y'); - self::adjustYear($testVal1, $testVal2, $testVal3); - $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - return Functions::VALUE(); - } - } - } - - $retValue = Functions::Value(); - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - // Execute function - self::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); - if ($PHPDateArray['year'] < $baseYear) { - return Functions::VALUE(); - } - self::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); - self::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) ? self::returnIn3FormatsFloat(60.0) : Functions::VALUE(); - } - $retValue = is_array($PHPDateArray) ? self::returnIn3FormatsArray($PHPDateArray, true) : Functions::VALUE(); - } - - return $retValue; - } - - /** - * Help reduce perceived complexity of some tests. - * - * @param mixed $value - * @param mixed $altValue - */ - private static function replaceIfEmpty(&$value, $altValue): void - { - $value = $value ?: $altValue; - } - - /** - * Adjust year in ambiguous situations. - */ - private static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void - { - if (!is_numeric($testVal1) || $testVal1 < 31) { - if (!is_numeric($testVal2) || $testVal2 < 12) { - if (is_numeric($testVal3) && $testVal3 < 12) { - $testVal3 += 2000; - } - } - } - } - - /** - * Return result in one of three formats. - * - * @return mixed - */ - private static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) - { - $retType = Functions::getReturnDateType(); - if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { - return new \DateTime( - $dateArray['year'] - . '-' . $dateArray['month'] - . '-' . $dateArray['day'] - . ' ' . $dateArray['hour'] - . ':' . $dateArray['minute'] - . ':' . $dateArray['second'] - ); - } - $excelDateValue = - Date::formattedPHPToExcel( - $dateArray['year'], - $dateArray['month'], - $dateArray['day'], - $dateArray['hour'], - $dateArray['minute'], - $dateArray['second'] - ); - if ($retType === Functions::RETURNDATE_EXCEL) { - return $noFrac ? floor($excelDateValue) : (float) $excelDateValue; - } - // RETURNDATE_UNIX_TIMESTAMP) - - return (int) Date::excelToTimestamp($excelDateValue); - } - - /** - * Return result in one of three formats. - * - * @return mixed - */ - private static function returnIn3FormatsFloat(float $excelDateValue) - { - $retType = Functions::getReturnDateType(); - if ($retType === Functions::RETURNDATE_EXCEL) { - return $excelDateValue; - } - if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { - return (int) Date::excelToTimestamp($excelDateValue); - } - // RETURNDATE_PHP_DATETIME_OBJECT - - return Date::excelToDateTimeObject($excelDateValue); - } - - /** - * Return result in one of three formats. - * - * @return mixed - */ - private static function returnIn3FormatsObject(\DateTime $PHPDateObject) - { - $retType = Functions::getReturnDateType(); - if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { - return $PHPDateObject; - } - if ($retType === Functions::RETURNDATE_EXCEL) { - return (float) Date::PHPToExcel($PHPDateObject); - } - // RETURNDATE_UNIX_TIMESTAMP - - return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); + return DateTimeExcel\DateValue::funcDateValue($dateValue); } /** @@ -603,6 +213,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcTimeValue method in the DateTimeExcel\TimeValue class instead + * * Excel Function: * TIMEVALUE(timeValue) * @@ -616,37 +228,14 @@ class DateTime */ public static function TIMEVALUE($timeValue) { - $timeValue = trim(Functions::flattenSingleValue($timeValue), '"'); - $timeValue = str_replace(['/', '.'], '-', $timeValue); - - $arraySplit = preg_split('/[\/:\-\s]/', $timeValue); - if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { - $arraySplit[0] = ($arraySplit[0] % 24); - $timeValue = implode(':', $arraySplit); - } - - $PHPDateArray = date_parse($timeValue); - $retValue = Functions::VALUE(); - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - // OpenOffice-specific code removed - it works just like Excel - $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; - - $retType = Functions::getReturnDateType(); - if ($retType === Functions::RETURNDATE_EXCEL) { - $retValue = (float) $excelDateValue; - } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { - $retValue = (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; - } else { - $retValue = new \DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); - } - } - - return $retValue; + return DateTimeExcel\TimeValue::funcTimeValue($timeValue); } /** * DATEDIF. * + * @Deprecated 2.0.0 Use the funcDateDif method in the DateTimeExcel\DateDif class instead + * * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object @@ -657,95 +246,7 @@ class DateTime */ public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $unit = strtoupper(Functions::flattenSingleValue($unit)); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - // Validate parameters - if ($startDate > $endDate) { - return Functions::NAN(); - } - - // Execute function - $difference = $endDate - $startDate; - - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDays = $PHPStartDateObject->format('j'); - $startMonths = $PHPStartDateObject->format('n'); - $startYears = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDays = $PHPEndDateObject->format('j'); - $endMonths = $PHPEndDateObject->format('n'); - $endYears = $PHPEndDateObject->format('Y'); - - $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); - - switch ($unit) { - case 'D': - $retVal = (int) $difference; - - break; - case 'M': - $retVal = (int) 12 * $PHPDiffDateObject->format('%y') + $PHPDiffDateObject->format('%m'); - - break; - case 'Y': - $retVal = (int) $PHPDiffDateObject->format('%y'); - - break; - case 'MD': - if ($endDays < $startDays) { - $retVal = $endDays; - $PHPEndDateObject->modify('-' . $endDays . ' days'); - $adjustDays = $PHPEndDateObject->format('j'); - $retVal += ($adjustDays - $startDays); - } else { - $retVal = (int) $PHPDiffDateObject->format('%d'); - } - - break; - case 'YM': - $retVal = (int) $PHPDiffDateObject->format('%m'); - - break; - case 'YD': - $retVal = (int) $difference; - if ($endYears > $startYears) { - $isLeapStartYear = $PHPStartDateObject->format('L'); - $wasLeapEndYear = $PHPEndDateObject->format('L'); - - // Adjust end year to be as close as possible as start year - while ($PHPEndDateObject >= $PHPStartDateObject) { - $PHPEndDateObject->modify('-1 year'); - $endYears = $PHPEndDateObject->format('Y'); - } - $PHPEndDateObject->modify('+1 year'); - - // Get the result - $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; - - // Adjust for leap years cases - $isLeapEndYear = $PHPEndDateObject->format('L'); - $limit = new \DateTime($PHPEndDateObject->format('Y-02-29')); - if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { - --$retVal; - } - } - - break; - default: - $retVal = Functions::VALUE(); - } - - return $retVal; + return DateTimeExcel\DateDif::funcDateDif($startDate, $endDate, $unit); } /** @@ -753,43 +254,21 @@ class DateTime * * Returns the number of days between two dates * + * @Deprecated 2.0.0 Use the funcDays method in the DateTimeExcel\Days class instead + * * Excel Function: * DAYS(endDate, startDate) * - * @param DateTimeImmutable|float|int|string $endDate Excel date serial value (float), + * @param DateTimeInterface|float|int|string $endDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string - * @param DateTimeImmutable|float|int|string $startDate Excel date serial value (float), + * @param DateTimeInterface|float|int|string $startDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string * * @return int|string Number of days between start date and end date or an error */ public static function DAYS($endDate = 0, $startDate = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - $startDate = self::getDateValue($startDate); - if (is_string($startDate)) { - return Functions::VALUE(); - } - - $endDate = self::getDateValue($endDate); - if (is_string($endDate)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - - $diff = $PHPStartDateObject->diff($PHPEndDateObject); - $days = $diff->days; - - if ($diff->invert) { - $days = -$days; - } - - return $days; + return DateTimeExcel\Days::funcDays($endDate, $startDate); } /** @@ -799,6 +278,8 @@ class DateTime * 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. * + * @Deprecated 2.0.0 Use the funcDays360 method in the DateTimeExcel\Days360 class instead + * * Excel Function: * DAYS360(startDate,endDate[,method]) * @@ -822,32 +303,7 @@ class DateTime */ public static function DAYS360($startDate = 0, $endDate = 0, $method = false) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - if (!is_bool($method)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDay = $PHPStartDateObject->format('j'); - $startMonth = $PHPStartDateObject->format('n'); - $startYear = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDay = $PHPEndDateObject->format('j'); - $endMonth = $PHPEndDateObject->format('n'); - $endYear = $PHPEndDateObject->format('Y'); - - return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method); + return DateTimeExcel\Days360::funcDays360($startDate, $endDate, $method); } /** @@ -858,6 +314,8 @@ class DateTime * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or * obligations to assign to a specific term. * + * @Deprecated 2.0.0 Use the funcYearFrac method in the DateTimeExcel\YearFrac class instead + * * Excel Function: * YEARFRAC(startDate,endDate[,method]) * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html @@ -878,78 +336,7 @@ class DateTime */ public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $method = Functions::flattenSingleValue($method); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - if ($startDate > $endDate) { - $temp = $startDate; - $startDate = $endDate; - $endDate = $temp; - } - - if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) { - switch ($method) { - case 0: - return self::DAYS360($startDate, $endDate) / 360; - case 1: - $days = self::DATEDIF($startDate, $endDate); - $startYear = self::YEAR($startDate); - $endYear = self::YEAR($endDate); - $years = $endYear - $startYear + 1; - $startMonth = self::MONTHOFYEAR($startDate); - $startDay = self::DAYOFMONTH($startDate); - $endMonth = self::MONTHOFYEAR($endDate); - $endDay = self::DAYOFMONTH($endDate); - $startMonthDay = 100 * $startMonth + $startDay; - $endMonthDay = 100 * $endMonth + $endDay; - if ($years == 1) { - if (self::isLeapYear($endYear)) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { - if (self::isLeapYear($startYear)) { - if ($startMonthDay <= 229) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } elseif (self::isLeapYear($endYear)) { - if ($endMonthDay >= 229) { - $tmpCalcAnnualBasis = 366; - } else { - $tmpCalcAnnualBasis = 365; - } - } else { - $tmpCalcAnnualBasis = 365; - } - } else { - $tmpCalcAnnualBasis = 0; - for ($year = $startYear; $year <= $endYear; ++$year) { - $tmpCalcAnnualBasis += self::isLeapYear($year) ? 366 : 365; - } - $tmpCalcAnnualBasis /= $years; - } - - return $days / $tmpCalcAnnualBasis; - case 2: - return self::DATEDIF($startDate, $endDate) / 360; - case 3: - return self::DATEDIF($startDate, $endDate) / 365; - case 4: - return self::DAYS360($startDate, $endDate, true) / 360; - } - } - - return Functions::VALUE(); + return DateTimeExcel\YearFrac::funcYearFrac($startDate, $endDate, $method); } /** @@ -960,6 +347,8 @@ class DateTime * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days * worked during a specific term. * + * @Deprecated 2.0.0 Use the funcNetworkDays method in the DateTimeExcel\NetworkDays class instead + * * Excel Function: * NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]]) * @@ -972,62 +361,7 @@ class DateTime */ public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) { - // Retrieve the mandatory start and end date that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - // Validate the start and end dates - if (is_string($startDate = $sDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - if (is_string($endDate = $eDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - $endDate = (float) floor($endDate); - - if ($sDate > $eDate) { - $startDate = $eDate; - $endDate = $sDate; - } - - // Execute function - $startDoW = 6 - self::WEEKDAY($startDate, 2); - if ($startDoW < 0) { - $startDoW = 5; - } - $endDoW = self::WEEKDAY($endDate, 2); - if ($endDoW >= 6) { - $endDoW = 0; - } - - $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5; - $partWeekDays = $endDoW + $startDoW; - if ($partWeekDays > 5) { - $partWeekDays -= 5; - } - - // Test any extra holiday parameters - $holidayCountedArray = []; - foreach ($dateArgs as $holidayDate) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { - if ((self::WEEKDAY($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { - --$partWeekDays; - $holidayCountedArray[] = $holidayDate; - } - } - } - - if ($sDate > $eDate) { - return 0 - ($wholeWeekDays + $partWeekDays); - } - - return $wholeWeekDays + $partWeekDays; + return DateTimeExcel\NetworkDays::funcNetworkDays($startDate, $endDate, ...$dateArgs); } /** @@ -1038,6 +372,8 @@ class DateTime * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected * delivery times, or the number of days of work performed. * + * @Deprecated 2.0.0 Use the funcWorkDay method in the DateTimeExcel\WorkDay class instead + * * Excel Function: * WORKDAY(startDate,endDays[,holidays[,holiday[,...]]]) * @@ -1052,84 +388,7 @@ class DateTime */ public static function WORKDAY($startDate, $endDays, ...$dateArgs) { - // Retrieve the mandatory start date and days that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDays = Functions::flattenSingleValue($endDays); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - if ((is_string($startDate = self::getDateValue($startDate))) || (!is_numeric($endDays))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - $endDays = (int) floor($endDays); - // If endDays is 0, we always return startDate - if ($endDays == 0) { - return $startDate; - } - - $decrementing = $endDays < 0; - - // Adjust the start date if it falls over a weekend - - $startDoW = self::WEEKDAY($startDate, 3); - if (self::WEEKDAY($startDate, 3) >= 5) { - $startDate += ($decrementing) ? -$startDoW + 4 : 7 - $startDoW; - ($decrementing) ? $endDays++ : $endDays--; - } - - // Add endDays - $endDate = (float) $startDate + ((int) ($endDays / 5) * 7) + ($endDays % 5); - - // Adjust the calculated end date if it falls over a weekend - $endDoW = self::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - - // Test any extra holiday parameters - if (!empty($dateArgs)) { - $holidayCountedArray = $holidayDates = []; - foreach ($dateArgs as $holidayDate) { - if (($holidayDate !== null) && (trim($holidayDate) > '')) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (self::WEEKDAY($holidayDate, 3) < 5) { - $holidayDates[] = $holidayDate; - } - } - } - if ($decrementing) { - rsort($holidayDates, SORT_NUMERIC); - } else { - sort($holidayDates, SORT_NUMERIC); - } - foreach ($holidayDates as $holidayDate) { - if ($decrementing) { - if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { - if (!in_array($holidayDate, $holidayCountedArray)) { - --$endDate; - $holidayCountedArray[] = $holidayDate; - } - } - } else { - 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::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - } - } - - return self::returnIn3FormatsFloat($endDate); + return DateTimeExcel\WorkDay::funcWorkDay($startDate, $endDays, ...$dateArgs); } /** @@ -1138,6 +397,8 @@ class DateTime * Returns the day of the month, for a specified date. The day is given as an integer * ranging from 1 to 31. * + * @Deprecated 2.0.0 Use the funcDay method in the DateTimeExcel\Day class instead + * * Excel Function: * DAY(dateValue) * @@ -1148,27 +409,7 @@ class DateTime */ public static function DAYOFMONTH($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null || is_bool($dateValue)) { - return (int) $dateValue; - } - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($dateValue < 0.0) { - return Functions::NAN(); - } elseif ($dateValue < 1.0) { - return 0; - } - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('j'); + return DateTimeExcel\Day::funcDay($dateValue); } /** @@ -1177,6 +418,8 @@ class DateTime * 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). * + * @Deprecated 2.0.0 Use the funcWeekDay method in the DateTimeExcel\WeekDay class instead + * * Excel Function: * WEEKDAY(dateValue[,style]) * @@ -1191,70 +434,169 @@ class DateTime */ public static function WEEKDAY($dateValue = 1, $style = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - self::nullFalseTrueToNumber($dateValue); - $style = Functions::flattenSingleValue($style); - - if (!is_numeric($style)) { - return Functions::VALUE(); - } elseif (($style < 1) || ($style > 3)) { - return Functions::NAN(); - } - $style = floor($style); - - $dateValue = self::getDateValue($dateValue); - if (is_string($dateValue)) { - return Functions::VALUE(); - } - if ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - self::silly1900($PHPDateObject); - $DoW = (int) $PHPDateObject->format('w'); - - switch ($style) { - case 1: - ++$DoW; - - break; - case 2: - if ($DoW === 0) { - $DoW = 7; - } - - break; - case 3: - if ($DoW === 0) { - $DoW = 7; - } - --$DoW; - - break; - } - - return $DoW; + return DateTimeExcel\WeekDay::funcWeekDay($dateValue, $style); } + /** + * STARTWEEK_SUNDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY instead + */ const STARTWEEK_SUNDAY = 1; + + /** + * STARTWEEK_MONDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY instead + */ const STARTWEEK_MONDAY = 2; + + /** + * STARTWEEK_MONDAY_ALT. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ALT instead + */ const STARTWEEK_MONDAY_ALT = 11; + + /** + * STARTWEEK_TUESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_TUESDAY instead + */ const STARTWEEK_TUESDAY = 12; + + /** + * STARTWEEK_WEDNESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_WEDNESDAY instead + */ const STARTWEEK_WEDNESDAY = 13; + + /** + * STARTWEEK_THURSDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_THURSDAY instead + */ const STARTWEEK_THURSDAY = 14; + + /** + * STARTWEEK_FRIDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_FRIDAY instead + */ const STARTWEEK_FRIDAY = 15; + + /** + * STARTWEEK_SATURDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SATURDAY instead + */ const STARTWEEK_SATURDAY = 16; + + /** + * STARTWEEK_SUNDAY_ALT. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY_ALT instead + */ const STARTWEEK_SUNDAY_ALT = 17; + + /** + * DOW_SUNDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_SUNDAY instead + */ const DOW_SUNDAY = 1; + + /** + * DOW_MONDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_MONDAY instead + */ const DOW_MONDAY = 2; + + /** + * DOW_TUESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_TUESDAY instead + */ const DOW_TUESDAY = 3; + + /** + * DOW_WEDNESDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_WEDNESDAY instead + */ const DOW_WEDNESDAY = 4; + + /** + * DOW_THURSDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_THURSDAY instead + */ const DOW_THURSDAY = 5; + + /** + * DOW_FRIDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_FRIDAY instead + */ const DOW_FRIDAY = 6; + + /** + * DOW_SATURDAY. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\DOW_SATURDAY instead + */ const DOW_SATURDAY = 7; + + /** + * STARTWEEK_MONDAY_ISO. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ISO instead + */ const STARTWEEK_MONDAY_ISO = 21; + + /** + * METHODARR. + * + * @Deprecated 2.0.0 + * + * @see Use DateTimeExcel\Constants\METHODARR instead + */ const METHODARR = [ self::STARTWEEK_SUNDAY => self::DOW_SUNDAY, self::DOW_MONDAY, @@ -1278,6 +620,8 @@ class DateTime * three days or less in the first week of January, the WEEKNUM function returns week numbers * that are incorrect according to the European standard. * + * @Deprecated 2.0.0 Use the funcWeekNum method in the DateTimeExcel\WeekNum class instead + * * Excel Function: * WEEKNUM(dateValue[,style]) * @@ -1299,67 +643,7 @@ class DateTime */ public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY) { - $origDateValueNull = $dateValue === null; - $dateValue = Functions::flattenSingleValue($dateValue); - $method = Functions::flattenSingleValue($method); - if (!is_numeric($method)) { - return Functions::VALUE(); - } - - $method = (int) $method; - if (!array_key_exists($method, self::METHODARR)) { - return Functions::NaN(); - } - $method = self::METHODARR[$method]; - if ($dateValue === null) { // boolean not allowed - // This seems to be an additional Excel bug. - if (self::buggyWeekNum1900($method)) { - return 0; - } - //$dateValue = 1; - $dateValue = (Date::getExcelCalendar() === DATE::CALENDAR_MAC_1904) ? 0 : 1; - } - - $dateValue = self::getDateValue($dateValue); - if (is_string($dateValue)) { - return Functions::VALUE(); - } - if ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - if ($method == self::STARTWEEK_MONDAY_ISO) { - self::silly1900($PHPDateObject); - - return (int) $PHPDateObject->format('W'); - } - if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { - return 0; - } - self::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches - $dayOfYear = $PHPDateObject->format('z'); - $PHPDateObject->modify('-' . $dayOfYear . ' days'); - $firstDayOfFirstWeek = $PHPDateObject->format('w'); - $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; - $daysInFirstWeek += 7 * !$daysInFirstWeek; - $endFirstWeek = $daysInFirstWeek - 1; - $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); - - return (int) $weekOfYear; - } - - private static function buggyWeekNum1900(int $method): bool - { - return $method === self::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_WINDOWS_1900; - } - - private static function buggyWeekNum1904(int $method, bool $origNull, \DateTime $dateObject): bool - { - // This appears to be another Excel bug. - - return $method === self::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_MAC_1904 && !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; + return DateTimeExcel\WeekNum::funcWeekNum($dateValue, $method); } /** @@ -1367,6 +651,8 @@ class DateTime * * Returns the ISO 8601 week number of the year for a specified date. * + * @Deprecated 2.0.0 Use the funcIsoWeeknum method in the DateTimeExcel\Isoweeknum class instead + * * Excel Function: * ISOWEEKNUM(dateValue) * @@ -1377,22 +663,7 @@ class DateTime */ public static function ISOWEEKNUM($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - self::nullFalseTrueToNumber($dateValue); - - $dateValue = self::getDateValue($dateValue); - if (!is_numeric($dateValue)) { - return Functions::VALUE(); - } - if ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - self::silly1900($PHPDateObject); - - return (int) $PHPDateObject->format('W'); + return DateTimeExcel\IsoweekNum::funcIsoWeekNum($dateValue); } /** @@ -1401,6 +672,8 @@ class DateTime * 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). * + * @Deprecated 2.0.0 Use the funcMonth method in the DateTimeExcel\Month class instead + * * Excel Function: * MONTH(dateValue) * @@ -1411,21 +684,7 @@ class DateTime */ public static function MONTHOFYEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if (empty($dateValue)) { - $dateValue = 1; - } - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('n'); + return DateTimeExcel\Month::funcMonth($dateValue); } /** @@ -1434,6 +693,8 @@ class DateTime * Returns the year corresponding to a date. * The year is returned as an integer in the range 1900-9999. * + * @Deprecated 2.0.0 Use the funcYear method in the DateTimeExcel\Year class instead + * * Excel Function: * YEAR(dateValue) * @@ -1444,20 +705,7 @@ class DateTime */ public static function YEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('Y'); + return DateTimeExcel\Year::funcYear($dateValue); } /** @@ -1466,6 +714,8 @@ class DateTime * 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.). * + * @Deprecated 2.0.0 Use the funcHour method in the DateTimeExcel\Hour class instead + * * Excel Function: * HOUR(timeValue) * @@ -1476,24 +726,7 @@ class DateTime */ public static function HOUROFDAY($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - // Gnumeric test removed - it operates like Excel - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('G', $timeValue); + return DateTimeExcel\Hour::funcHour($timeValue); } /** @@ -1502,6 +735,8 @@ class DateTime * Returns the minutes of a time value. * The minute is given as an integer, ranging from 0 to 59. * + * @Deprecated 2.0.0 Use the funcMinute method in the DateTimeExcel\Minute class instead + * * Excel Function: * MINUTE(timeValue) * @@ -1512,24 +747,7 @@ class DateTime */ public static function MINUTE($timeValue = 0) { - $timeValue = $timeTester = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - // Gnumeric test removed - it operates like Excel - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('i', $timeValue); + return DateTimeExcel\Minute::funcMinute($timeValue); } /** @@ -1538,6 +756,8 @@ class DateTime * Returns the seconds of a time value. * The second is given as an integer in the range 0 (zero) to 59. * + * @Deprecated 2.0.0 Use the funcSecond method in the DateTimeExcel\Second class instead + * * Excel Function: * SECOND(timeValue) * @@ -1548,24 +768,7 @@ class DateTime */ public static function SECOND($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - // Gnumeric test removed - it operates like Excel - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('s', $timeValue); + return DateTimeExcel\Second::funcSecond($timeValue); } /** @@ -1576,6 +779,8 @@ class DateTime * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month * as the date of issue. * + * @Deprecated 2.0.0 Use the funcEDate method in the DateTimeExcel\EDate class instead + * * Excel Function: * EDATE(dateValue,adjustmentMonths) * @@ -1590,22 +795,7 @@ class DateTime */ public static function EDATE($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths); - - return self::returnIn3FormatsObject($PHPDateObject); + return DateTimeExcel\EDate::funcEDate($dateValue, $adjustmentMonths); } /** @@ -1615,6 +805,8 @@ class DateTime * before or after start_date. * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. * + * @Deprecated 2.0.0 Use the funcEoMonth method in the DateTimeExcel\EoMonth class instead + * * Excel Function: * EOMONTH(dateValue,adjustmentMonths) * @@ -1629,49 +821,6 @@ class DateTime */ public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths + 1); - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - - return self::returnIn3FormatsObject($PHPDateObject); - } - - /** - * Many functions accept null/false/true argument treated as 0/0/1. - * - * @param mixed $number - */ - private static function nullFalseTrueToNumber(&$number): void - { - $number = Functions::flattenSingleValue($number); - $baseYear = Date::getExcelCalendar(); - $nullVal = $baseYear === DATE::CALENDAR_MAC_1904 ? 0 : 1; - if ($number === null) { - $number = $nullVal; - } elseif (is_bool($number)) { - $number = $nullVal + (int) $number; - } - } - - private static function silly1900(\DateTime $PHPDateObject, string $mod = '-1 day'): void - { - $isoDate = $PHPDateObject->format('c'); - if ($isoDate < '1900-03-01') { - $PHPDateObject->modify($mod); - } + return DateTimeExcel\EoMonth::funcEoMonth($dateValue, $adjustmentMonths); } } diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php new file mode 100644 index 00000000..da1b81c1 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php @@ -0,0 +1,37 @@ + 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, + ]; +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php new file mode 100644 index 00000000..ace22cbf --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateDif.php @@ -0,0 +1,146 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $startDays = (int) $PHPStartDateObject->format('j'); + $startMonths = (int) $PHPStartDateObject->format('n'); + $startYears = (int) $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + $endDays = (int) $PHPEndDateObject->format('j'); + $endMonths = (int) $PHPEndDateObject->format('n'); + $endYears = (int) $PHPEndDateObject->format('Y'); + + $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); + + $retVal = false; + $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference); + $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject); + + return is_bool($retVal) ? Functions::VALUE() : $retVal; + } + + private static function initialDiff(float $startDate, float $endDate): float + { + // Validate parameters + if ($startDate > $endDate) { + throw new Exception(Functions::NAN()); + } + + return $endDate - $startDate; + } + + /** + * Decide whether it's time to set retVal. + * + * @param bool|int $retVal + * + * @return null|bool|int + */ + private static function replaceRetValue($retVal, string $unit, string $compare) + { + if ($retVal !== false || $unit !== $compare) { + return $retVal; + } + + return null; + } + + private static function datedifD(float $difference): int + { + return (int) $difference; + } + + private static function datedifM(DateInterval $PHPDiffDateObject): int + { + return (int) 12 * $PHPDiffDateObject->format('%y') + $PHPDiffDateObject->format('%m'); + } + + private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int + { + if ($endDays < $startDays) { + $retVal = $endDays; + $PHPEndDateObject->modify('-' . $endDays . ' days'); + $adjustDays = (int) $PHPEndDateObject->format('j'); + $retVal += ($adjustDays - $startDays); + } else { + $retVal = (int) $PHPDiffDateObject->format('%d'); + } + + return $retVal; + } + + private static function datedifY(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%y'); + } + + private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int + { + $retVal = (int) $difference; + if ($endYears > $startYears) { + $isLeapStartYear = $PHPStartDateObject->format('L'); + $wasLeapEndYear = $PHPEndDateObject->format('L'); + + // Adjust end year to be as close as possible as start year + while ($PHPEndDateObject >= $PHPStartDateObject) { + $PHPEndDateObject->modify('-1 year'); + $endYears = $PHPEndDateObject->format('Y'); + } + $PHPEndDateObject->modify('+1 year'); + + // Get the result + $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; + + // Adjust for leap years cases + $isLeapEndYear = $PHPEndDateObject->format('L'); + $limit = new DateTime($PHPEndDateObject->format('Y-02-29')); + if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { + --$retVal; + } + } + + return (int) $retVal; + } + + private static function datedifYM(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%m'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php new file mode 100644 index 00000000..3c15d06a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php @@ -0,0 +1,151 @@ + 31)) { + if ($yearFound) { + return Functions::VALUE(); + } + if ($t < 100) { + $t += 1900; + } + $yearFound = true; + } + } + if (count($t1) === 1) { + // We've been fed a time value without any date + return ((strpos($t, ':') === false)) ? Functions::Value() : 0.0; + } + unset($t); + + $dateValue = self::t1ToString($t1, $dti, $yearFound); + + $PHPDateArray = self::setUpArray($dateValue, $dti); + + return self::finalResults($PHPDateArray, $dti, $baseYear); + } + + private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string + { + if (count($t1) == 2) { + // We only have two parts of the date: either day/month or month/year + if ($yearFound) { + array_unshift($t1, 1); + } else { + if (is_numeric($t1[1]) && $t1[1] > 29) { + $t1[1] += 1900; + array_unshift($t1, 1); + } else { + $t1[] = $dti->format('Y'); + } + } + } + $dateValue = implode(' ', $t1); + + return $dateValue; + } + + /** + * Parse date. + * + * @return array|bool + */ + private static function setUpArray(string $dateValue, DateTimeImmutable $dti) + { + $PHPDateArray = date_parse($dateValue); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + // If original count was 1, we've already returned. + // If it was 2, we added another. + // Therefore, neither of the first 2 stroks below can fail. + $testVal1 = strtok($dateValue, '- '); + $testVal2 = strtok('- '); + $testVal3 = strtok('- ') ?: $dti->format('Y'); + Helpers::adjustYear($testVal1, $testVal2, $testVal3); + $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); + if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { + $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); + } + } + + return $PHPDateArray; + } + + /** + * Final results. + * + * @param array|false $PHPDateArray + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + private static function finalResults($PHPDateArray, DateTimeImmutable $dti, int $baseYear) + { + $retValue = Functions::Value(); + if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { + // Execute function + Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); + if ($PHPDateArray['year'] < $baseYear) { + return Functions::VALUE(); + } + Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); + Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d')); + $PHPDateArray['hour'] = 0; + $PHPDateArray['minute'] = 0; + $PHPDateArray['second'] = 0; + $month = (int) $PHPDateArray['month']; + $day = (int) $PHPDateArray['day']; + $year = (int) $PHPDateArray['year']; + if (!checkdate($month, $day, $year)) { + return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : Functions::VALUE(); + } + $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true); + } + + return $retValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php new file mode 100644 index 00000000..ec8be2df --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Datefunc.php @@ -0,0 +1,168 @@ +getMessage(); + } + + // Execute function + $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); + + return Helpers::returnIn3FormatsFloat($excelDateValue); + } + + /** + * Convert year from multiple formats to int. + * + * @param mixed $year + */ + private static function getYear($year, int $baseYear): int + { + $year = Functions::flattenSingleValue($year); + $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; + if (!is_numeric($year)) { + throw new Exception(Functions::VALUE()); + } + $year = (int) $year; + + if ($year < ($baseYear - 1900)) { + throw new Exception(Functions::NAN()); + } + if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { + throw new Exception(Functions::NAN()); + } + + if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { + $year += 1900; + } + + return $year; + } + + /** + * Convert month from multiple formats to int. + * + * @param mixed $month + */ + private static function getMonth($month): int + { + $month = Functions::flattenSingleValue($month); + + if (($month !== null) && (!is_numeric($month))) { + $month = Date::monthStringToNumber($month); + } + + $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; + if (!is_numeric($month)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $month; + } + + /** + * Convert day from multiple formats to int. + * + * @param mixed $day + */ + private static function getDay($day): int + { + $day = Functions::flattenSingleValue($day); + + if (($day !== null) && (!is_numeric($day))) { + $day = Date::dayStringToNumber($day); + } + + $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; + if (!is_numeric($day)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $day; + } + + private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void + { + if ($month < 1) { + // Handle year/month adjustment if month < 1 + --$month; + $year += ceil($month / 12) - 1; + $month = 13 - abs($month % 12); + } elseif ($month > 12) { + // Handle year/month adjustment if month > 12 + $year += floor($month / 12); + $month = ($month % 12); + } + + // Re-validate the year parameter after adjustments + if (($year < $baseYear) || ($year >= 10000)) { + throw new Exception(Functions::NAN()); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php new file mode 100644 index 00000000..6ab27184 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Day.php @@ -0,0 +1,61 @@ += 0) { + return $weirdResult; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('j'); + } + + private static function weirdCondition($dateValue): int + { + // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR) + if (Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { + if (is_bool($dateValue)) { + return (int) $dateValue; + } + if ($dateValue === null) { + return 0; + } + if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) { + return 0; + } + } + + return -1; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php new file mode 100644 index 00000000..2c814e8e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php @@ -0,0 +1,51 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + + $days = Functions::VALUE(); + $diff = $PHPStartDateObject->diff($PHPEndDateObject); + if ($diff !== false && !is_bool($diff->days)) { + $days = $diff->days; + if ($diff->invert) { + $days = -$days; + } + } + + return $days; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php new file mode 100644 index 00000000..068ea2bc --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php @@ -0,0 +1,106 @@ +getMessage(); + } + + if (!is_bool($method)) { + return Functions::VALUE(); + } + + // Execute function + $PHPStartDateObject = Date::excelToDateTimeObject($startDate); + $startDay = $PHPStartDateObject->format('j'); + $startMonth = $PHPStartDateObject->format('n'); + $startYear = $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = Date::excelToDateTimeObject($endDate); + $endDay = $PHPEndDateObject->format('j'); + $endMonth = $PHPEndDateObject->format('n'); + $endYear = $PHPEndDateObject->format('Y'); + + return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method); + } + + /** + * Return the number of days between two dates based on a 360 day calendar. + */ + private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int + { + $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS); + $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS); + + return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + } + + private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int + { + if ($startDay == 31) { + --$startDay; + } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) { + $startDay = 30; + } + + return $startDay; + } + + private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int + { + if ($endDay == 31) { + if ($methodUS && $startDay != 30) { + $endDay = 1; + if ($endMonth == 12) { + ++$endYear; + $endMonth = 1; + } else { + ++$endMonth; + } + } else { + $endDay = 30; + } + } + + return $endDay; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php new file mode 100644 index 00000000..43af694f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EDate.php @@ -0,0 +1,45 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php new file mode 100644 index 00000000..6b39a609 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/EoMonth.php @@ -0,0 +1,47 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1); + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php new file mode 100644 index 00000000..48300642 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php @@ -0,0 +1,291 @@ +getMessage(); + } + } + + /** + * getTimeValue. + * + * @param string $timeValue + * + * @return mixed Excel date/time serial value, or string if error + */ + public static function getTimeValue($timeValue) + { + $saveReturnDateType = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + $timeValue = TimeValue::funcTimeValue($timeValue); + Functions::setReturnDateType($saveReturnDateType); + + return $timeValue; + } + + public static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) + { + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + $oMonth = (int) $PHPDateObject->format('m'); + $oYear = (int) $PHPDateObject->format('Y'); + + $adjustmentMonthsString = (string) $adjustmentMonths; + if ($adjustmentMonths > 0) { + $adjustmentMonthsString = '+' . $adjustmentMonths; + } + if ($adjustmentMonths != 0) { + $PHPDateObject->modify($adjustmentMonthsString . ' months'); + } + $nMonth = (int) $PHPDateObject->format('m'); + $nYear = (int) $PHPDateObject->format('Y'); + + $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); + if ($monthDiff != $adjustmentMonths) { + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + } + + return $PHPDateObject; + } + + /** + * Help reduce perceived complexity of some tests. + * + * @param mixed $value + * @param mixed $altValue + */ + public static function replaceIfEmpty(&$value, $altValue): void + { + $value = $value ?: $altValue; + } + + /** + * Adjust year in ambiguous situations. + */ + public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void + { + if (!is_numeric($testVal1) || $testVal1 < 31) { + if (!is_numeric($testVal2) || $testVal2 < 12) { + if (is_numeric($testVal3) && $testVal3 < 12) { + $testVal3 += 2000; + } + } + } + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return new DateTime( + $dateArray['year'] + . '-' . $dateArray['month'] + . '-' . $dateArray['day'] + . ' ' . $dateArray['hour'] + . ':' . $dateArray['minute'] + . ':' . $dateArray['second'] + ); + } + $excelDateValue = + Date::formattedPHPToExcel( + $dateArray['year'], + $dateArray['month'], + $dateArray['day'], + $dateArray['hour'], + $dateArray['minute'], + $dateArray['second'] + ); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $noFrac ? floor($excelDateValue) : (float) $excelDateValue; + } + // RETURNDATE_UNIX_TIMESTAMP) + + return (int) Date::excelToTimestamp($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsFloat(float $excelDateValue) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $excelDateValue; + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) Date::excelToTimestamp($excelDateValue); + } + // RETURNDATE_PHP_DATETIME_OBJECT + + return Date::excelToDateTimeObject($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsObject(DateTime $PHPDateObject) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return $PHPDateObject; + } + if ($retType === Functions::RETURNDATE_EXCEL) { + return (float) Date::PHPToExcel($PHPDateObject); + } + // RETURNDATE_UNIX_TIMESTAMP + + return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); + } + + private static function baseDate(): int + { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + return 0; + } + if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) { + return 0; + } + + return 1; + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void + { + $number = Functions::flattenSingleValue($number); + $nullVal = self::baseDate(); + if ($number === null) { + $number = $nullVal; + } elseif ($allowBool && is_bool($number)) { + $number = $nullVal + (int) $number; + } + } + + /** + * Many functions accept null argument treated as 0. + * + * @param mixed $number + * + * @return float|int + */ + public static function validateNumericNull($number) + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + return 0; + } + if (is_numeric($number)) { + return $number; + } + + throw new Exception(Functions::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + * + * @return float + */ + public static function validateNotNegative($number) + { + if (!is_numeric($number)) { + throw new Exception(Functions::VALUE()); + } + if ($number >= 0) { + return (float) $number; + } + + throw new Exception(Functions::NAN()); + } + + public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void + { + $isoDate = $PHPDateObject->format('c'); + if ($isoDate < '1900-03-01') { + $PHPDateObject->modify($mod); + } + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php new file mode 100644 index 00000000..98d4570d --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Hour.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('H'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php new file mode 100644 index 00000000..41959d9a --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/IsoWeekNum.php @@ -0,0 +1,55 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + + private static function apparentBug($dateValue): bool + { + if (Date::getExcelCalendar() !== DATE::CALENDAR_MAC_1904) { + if (is_bool($dateValue)) { + return true; + } + if (is_numeric($dateValue) && !((int) $dateValue)) { + return true; + } + } + + return false; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php new file mode 100644 index 00000000..a1747ec9 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Minute.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('i'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php new file mode 100644 index 00000000..a9fb8ece --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php @@ -0,0 +1,40 @@ +getMessage(); + } + if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) { + return 1; + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('n'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php new file mode 100644 index 00000000..c700c834 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php @@ -0,0 +1,102 @@ +getMessage(); + } + + // Execute function + $startDow = self::calcStartDow($startDate); + $endDow = self::calcEndDow($endDate); + $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5; + $partWeekDays = self::calcPartWeekDays($startDow, $endDow); + + // Test any extra holiday parameters + $holidayCountedArray = []; + foreach ($holidayArray as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if ((WeekDay::funcWeekDay($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { + --$partWeekDays; + $holidayCountedArray[] = $holidayDate; + } + } + } + + return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate); + } + + private static function calcStartDow(float $startDate): int + { + $startDow = 6 - (int) WeekDay::funcWeekDay($startDate, 2); + if ($startDow < 0) { + $startDow = 5; + } + + return $startDow; + } + + private static function calcEndDow(float $endDate): int + { + $endDow = (int) WeekDay::funcWeekDay($endDate, 2); + if ($endDow >= 6) { + $endDow = 0; + } + + return $endDow; + } + + private static function calcPartWeekDays(int $startDow, int $endDow): int + { + $partWeekDays = $endDow + $startDow; + if ($partWeekDays > 5) { + $partWeekDays -= 5; + } + + return $partWeekDays; + } + + private static function applySign(int $result, float $sDate, float $eDate) + { + return ($sDate > $eDate) ? -$result : $result; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php new file mode 100644 index 00000000..6e6bd171 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Now.php @@ -0,0 +1,34 @@ +format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php new file mode 100644 index 00000000..c4749993 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Second.php @@ -0,0 +1,44 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = Date::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('s'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php new file mode 100644 index 00000000..450f9d50 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php @@ -0,0 +1,116 @@ +getMessage(); + } + + self::adjustSecond($second, $minute); + self::adjustMinute($minute, $hour); + + if ($hour > 23) { + $hour = $hour % 24; + } elseif ($hour < 0) { + return Functions::NAN(); + } + + // Execute function + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $calendar = Date::getExcelCalendar(); + $date = (int) ($calendar !== Date::CALENDAR_WINDOWS_1900); + + return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 + } + // RETURNDATE_PHP_DATETIME_OBJECT + // Hour has already been normalized (0-23) above + $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); + + return $phpDateObject; + } + + private static function adjustSecond(int &$second, int &$minute): void + { + if ($second < 0) { + $minute += floor($second / 60); + $second = 60 - abs($second % 60); + if ($second == 60) { + $second = 0; + } + } elseif ($second >= 60) { + $minute += floor($second / 60); + $second = $second % 60; + } + } + + private static function adjustMinute(int &$minute, int &$hour): void + { + if ($minute < 0) { + $hour += floor($minute / 60); + $minute = 60 - abs($minute % 60); + if ($minute == 60) { + $minute = 0; + } + } elseif ($minute >= 60) { + $hour += floor($minute / 60); + $minute = $minute % 60; + } + } + + private static function toIntWithNullBool($value): int + { + $value = Functions::flattenSingleValue($value); + $value = $value ?? 0; + if (is_bool($value)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + throw new Exception(Functions::VALUE()); + } + + return (int) $value; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php new file mode 100644 index 00000000..2366b1d6 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -0,0 +1,61 @@ + 24) { + $arraySplit[0] = ($arraySplit[0] % 24); + $timeValue = implode(':', $arraySplit); + } + + $PHPDateArray = date_parse($timeValue); + $retValue = Functions::VALUE(); + if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { + // OpenOffice-specific code removed - it works just like Excel + $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; + + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $retValue = (float) $excelDateValue; + } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + $retValue = (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; + } else { + $retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); + } + } + + return $retValue; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php new file mode 100644 index 00000000..5e459410 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Today.php @@ -0,0 +1,34 @@ +format('c')); + + return is_array($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE(); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php new file mode 100644 index 00000000..15811ee5 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekDay.php @@ -0,0 +1,80 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + $DoW = (int) $PHPDateObject->format('w'); + + switch ($style) { + case 1: + ++$DoW; + + break; + case 2: + $DoW = self::dow0Becomes7($DoW); + + break; + case 3: + $DoW = self::dow0Becomes7($DoW) - 1; + + break; + } + + return $DoW; + } + + private static function validateStyle($style): int + { + $style = Functions::flattenSingleValue($style); + + if (!is_numeric($style)) { + throw new Exception(Functions::VALUE()); + } + $style = (int) $style; + if (($style < 1) || ($style > 3)) { + throw new Exception(Functions::NAN()); + } + + return $style; + } + + private static function dow0Becomes7(int $DoW): int + { + return ($DoW === 0) ? 7 : $DoW; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php new file mode 100644 index 00000000..1dd15edb --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WeekNum.php @@ -0,0 +1,130 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + if ($method == Constants::STARTWEEK_MONDAY_ISO) { + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { + return 0; + } + Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches + $dayOfYear = $PHPDateObject->format('z'); + $PHPDateObject->modify('-' . $dayOfYear . ' days'); + $firstDayOfFirstWeek = $PHPDateObject->format('w'); + $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; + $daysInFirstWeek += 7 * !$daysInFirstWeek; + $endFirstWeek = $daysInFirstWeek - 1; + $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); + + return (int) $weekOfYear; + } + + /** + * Validate dateValue parameter. + * + * @param mixed $dateValue + */ + private static function validateDateValue($dateValue): float + { + if (is_bool($dateValue)) { + throw new Exception(Functions::VALUE()); + } + + return Helpers::getDateValue($dateValue); + } + + /** + * Validate method parameter. + * + * @param mixed $method + */ + private static function validateMethod($method): int + { + if ($method === null) { + $method = Constants::STARTWEEK_SUNDAY; + } + $method = Functions::flattenSingleValue($method); + if (!is_numeric($method)) { + throw new Exception(Functions::VALUE()); + } + + $method = (int) $method; + if (!array_key_exists($method, Constants::METHODARR)) { + throw new Exception(Functions::NAN()); + } + $method = Constants::METHODARR[$method]; + + return $method; + } + + private static function buggyWeekNum1900(int $method): bool + { + return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_WINDOWS_1900; + } + + private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool + { + // This appears to be another Excel bug. + + return $method === Constants::DOW_SUNDAY && Date::getExcelCalendar() === Date::CALENDAR_MAC_1904 && !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php new file mode 100644 index 00000000..f812624e --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php @@ -0,0 +1,182 @@ +getMessage(); + } + + $startDate = (float) floor($startDate); + $endDays = (int) floor($endDays); + // If endDays is 0, we always return startDate + if ($endDays == 0) { + return $startDate; + } + if ($endDays < 0) { + return self::decrementing($startDate, $endDays, $holidayArray); + } + + return self::incrementing($startDate, $endDays, $holidayArray); + } + + /** + * Use incrementing logic to determine Workday. + * + * @return mixed + */ + private static function incrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = WeekDay::funcWeekDay($startDate, 3); + if (WeekDay::funcWeekDay($startDate, 3) >= 5) { + $startDate += 7 - $startDoW; + --$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays > 0) { + ++$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = WeekDay::funcWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 7 - $endDow; + } + --$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::incrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (WeekDay::funcWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + sort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + ++$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = WeekDay::funcWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += 7 - $endDoW; + } + } + + return $endDate; + } + + /** + * Use decrementing logic to determine Workday. + * + * @return mixed + */ + private static function decrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + + $startDoW = WeekDay::funcWeekDay($startDate, 3); + if (WeekDay::funcWeekDay($startDate, 3) >= 5) { + $startDate += -$startDoW + 4; + ++$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays < 0) { + --$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = WeekDay::funcWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 4 - $endDow; + } + ++$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::decrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (WeekDay::funcWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + rsort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + --$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = WeekDay::funcWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += -$endDoW + 4; + } + } + + return $endDate; + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php new file mode 100644 index 00000000..5fcac739 --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Year.php @@ -0,0 +1,40 @@ +getMessage(); + } + + if ($dateValue < 1 && Date::getExcelCalendar() === DATE::CALENDAR_WINDOWS_1900) { + return 1900; + } + // Execute function + $PHPDateObject = Date::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('Y'); + } +} diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php new file mode 100644 index 00000000..a99b1c7f --- /dev/null +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php @@ -0,0 +1,120 @@ +getMessage(); + } + + switch ($method) { + case 0: + return Days360::funcDays360($startDate, $endDate) / 360; + case 1: + return self::method1($startDate, $endDate); + case 2: + return DateDif::funcDateDif($startDate, $endDate) / 360; + case 3: + return DateDif::funcDateDif($startDate, $endDate) / 365; + case 4: + return Days360::funcDays360($startDate, $endDate, true) / 360; + } + + return Functions::NAN(); + } + + /** + * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. + * + * @param mixed $startDate + * @param mixed $endDate + */ + private static function excelBug(float $sDate, $startDate, $endDate, int $method): float + { + if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && Date::getExcelCalendar() !== Date::CALENDAR_MAC_1904) { + if ($endDate === null && $startDate !== null) { + if (Month::funcMonth($sDate) == 12 && Day::funcDay($sDate) === 31 && $method === 0) { + $sDate += 2; + } else { + ++$sDate; + } + } + } + + return $sDate; + } + + private static function method1(float $startDate, float $endDate): float + { + $days = DateDif::funcDateDif($startDate, $endDate); + $startYear = Year::funcYear($startDate); + $endYear = Year::funcYear($endDate); + $years = $endYear - $startYear + 1; + $startMonth = Month::funcMonth($startDate); + $startDay = Day::funcDay($startDate); + $endMonth = Month::funcMonth($endDate); + $endDay = Day::funcDay($endDate); + $startMonthDay = 100 * $startMonth + $startDay; + $endMonthDay = 100 * $endMonth + $endDay; + if ($years == 1) { + $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear); + } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { + if (Helpers::isLeapYear($startYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229); + } elseif (Helpers::isLeapYear($endYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229); + } else { + $tmpCalcAnnualBasis = 365; + } + } else { + $tmpCalcAnnualBasis = 0; + for ($year = $startYear; $year <= $endYear; ++$year) { + $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year); + } + $tmpCalcAnnualBasis /= $years; + } + + return $days / $tmpCalcAnnualBasis; + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php new file mode 100644 index 00000000..c56c7431 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/AllSetupTeardown.php @@ -0,0 +1,71 @@ +compatibilityMode = Functions::getCompatibilityMode(); + $this->excelCalendar = Date::getExcelCalendar(); + $this->returnDateType = Functions::getReturnDateType(); + $this->spreadsheet = new Spreadsheet(); + $this->sheet = $this->spreadsheet->getActiveSheet(); + } + + protected function tearDown(): void + { + Date::setExcelCalendar($this->excelCalendar); + Functions::setCompatibilityMode($this->compatibilityMode); + Functions::setReturnDateType($this->returnDateType); + $this->spreadsheet->disconnectWorksheets(); + $this->spreadsheet = null; + $this->sheet = null; + } + + protected static function setMac1904(): void + { + Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + } + + protected static function setUnixReturn(): void + { + Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + } + + protected static function setObjectReturn(): void + { + Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + } + + protected static function setOpenOffice(): void + { + Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); + } + + /** + * @param mixed $expectedResult + */ + protected function mightHaveException($expectedResult): void + { + if ($expectedResult === 'exception') { + $this->expectException(CalcException::class); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php index db8e29a1..6c394087 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateDifTest.php @@ -2,32 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class DateDifTest extends TestCase +class DateDifTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDATEDIF * * @param mixed $expectedResult - * @param $startDate - * @param $endDate - * @param $unit */ - public function testDATEDIF($expectedResult, $startDate, $endDate, $unit): void + public function testDATEDIF($expectedResult, string $formula): void { - $result = DateTime::DATEDIF($startDate, $endDate, $unit); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DATEDIF($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerDATEDIF() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php index aad59729..354e6f3b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateTest.php @@ -2,42 +2,22 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Datefunc; -class DateTest extends TestCase +class DateTest extends AllSetupTeardown { - private $returnDateType; - - private $excelCalendar; - - protected function setUp(): void - { - $this->returnDateType = Functions::getReturnDateType(); - $this->excelCalendar = Date::getExcelCalendar(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - } - - protected function tearDown(): void - { - Functions::setReturnDateType($this->returnDateType); - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerDATE * * @param mixed $expectedResult - * @param $year - * @param $month - * @param $day */ - public function testDATE($expectedResult, $year, $month, $day): void + public function testDATE($expectedResult, string $formula): void { - $result = DateTime::DATE($year, $month, $day); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DATE($formula)"); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerDATE() @@ -47,18 +27,17 @@ class DateTest extends TestCase public function testDATEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::DATE(2012, 1, 31); + $result = Datefunc::funcDate(2012, 1, 31); // 32-bit safe self::assertEquals(1327968000, $result); - self::assertEqualsWithDelta(1327968000, $result, 1E-8); } public function testDATEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::DATE(2012, 1, 31); + $result = Datefunc::funcDate(2012, 1, 31); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -69,17 +48,12 @@ class DateTest extends TestCase public function testDATEwith1904Calendar(): void { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); + self::setMac1904(); - $result = DateTime::DATE(1918, 11, 11); + $result = Datefunc::funcDate(1918, 11, 11); self::assertEquals($result, 5428); - } - public function testDATEwith1904CalendarError(): void - { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - - $result = DateTime::DATE(1901, 1, 31); + $result = Datefunc::funcDate(1901, 1, 31); self::assertEquals($result, '#NUM!'); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php index 72e036f9..fc432bbe 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DateValueTest.php @@ -4,50 +4,33 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; use DateTimeImmutable; use DateTimeInterface; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\DateValue; -class DateValueTest extends TestCase +class DateValueTest extends AllSetupTeardown { - private $returnDateType; - - private $excelCalendar; - - protected function setUp(): void - { - $this->returnDateType = Functions::getReturnDateType(); - $this->excelCalendar = Date::getExcelCalendar(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - } - - protected function tearDown(): void - { - Functions::setReturnDateType($this->returnDateType); - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerDATEVALUE * * @param mixed $expectedResult - * @param $dateValue */ - public function testDATEVALUE($expectedResult, $dateValue): void + public function testDATEVALUE($expectedResult, string $dateValue): void { + $this->sheet->getCell('B1')->setValue('1954-07-20'); // Loop to avoid extraordinarily rare edge case where first calculation // and second do not take place on same day. + $row = 0; do { + ++$row; $dtStart = new DateTimeImmutable(); $startDay = $dtStart->format('d'); if (is_string($expectedResult)) { $replYMD = str_replace('Y', date('Y'), $expectedResult); if ($replYMD !== $expectedResult) { - $expectedResult = DateTime::DATEVALUE($replYMD); + $expectedResult = DateValue::funcDateValue($replYMD); } } - $result = DateTime::DATEVALUE($dateValue); + $this->sheet->getCell("A$row")->setValue("=DATEVALUE($dateValue)"); + $result = $this->sheet->getCell("A$row")->getCalculatedValue(); $dtEnd = new DateTimeImmutable(); $endDay = $dtEnd->format('d'); } while ($startDay !== $endDay); @@ -61,18 +44,18 @@ class DateValueTest extends TestCase public function testDATEVALUEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::DATEVALUE('2012-1-31'); + $result = DateValue::funcDateValue('2012-1-31'); self::assertEquals(1327968000, $result); self::assertEqualsWithDelta(1327968000, $result, 1E-8); } public function testDATEVALUEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::DATEVALUE('2012-1-31'); + $result = DateValue::funcDateValue('2012-1-31'); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -83,10 +66,10 @@ class DateValueTest extends TestCase public function testDATEVALUEwith1904Calendar(): void { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - self::assertEquals(5428, DateTime::DATEVALUE('1918-11-11')); - self::assertEquals(0, DateTime::DATEVALUE('1904-01-01')); - self::assertEquals('#VALUE!', DateTime::DATEVALUE('1903-12-31')); - self::assertEquals('#VALUE!', DateTime::DATEVALUE('1900-02-29')); + self::setMac1904(); + self::assertEquals(5428, DateValue::funcDateValue('1918-11-11')); + self::assertEquals(0, DateValue::funcDateValue('1904-01-01')); + self::assertEquals('#VALUE!', DateValue::funcDateValue('1903-12-31')); + self::assertEquals('#VALUE!', DateValue::funcDateValue('1900-02-29')); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php index 482e068d..e50475cf 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DayTest.php @@ -2,56 +2,43 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class DayTest extends TestCase +class DayTest extends AllSetupTeardown { - private $compatibilityMode; - - private $returnDateType; - - private $excelCalendar; - - protected function setUp(): void - { - $this->compatibilityMode = Functions::getCompatibilityMode(); - $this->returnDateType = Functions::getReturnDateType(); - $this->excelCalendar = Date::getExcelCalendar(); - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - - protected function tearDown(): void - { - Functions::setCompatibilityMode($this->compatibilityMode); - Functions::setReturnDateType($this->returnDateType); - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerDAY * * @param mixed $expectedResultExcel - * @param mixed $expectedResultOpenOffice - * @param $dateTimeValue */ - public function testDAY($expectedResultExcel, $expectedResultOpenOffice, $dateTimeValue): void + public function testDAY($expectedResultExcel, string $dateTimeValue): void { - $resultExcel = DateTime::DAYOFMONTH($dateTimeValue); - self::assertEqualsWithDelta($expectedResultExcel, $resultExcel, 1E-8); - - Functions::setCompatibilityMode(Functions::COMPATIBILITY_OPENOFFICE); - - $resultOpenOffice = DateTime::DAYOFMONTH($dateTimeValue); - self::assertEqualsWithDelta($expectedResultOpenOffice, $resultOpenOffice, 1E-8); + $this->mightHaveException($expectedResultExcel); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=DAY($dateTimeValue)"); + self::assertSame($expectedResultExcel, $sheet->getCell('A1')->getCalculatedValue()); } public function providerDAY() { return require 'tests/data/Calculation/DateTime/DAY.php'; } + + /** + * @dataProvider providerDAYOpenOffice + * + * @param mixed $expectedResultOpenOffice + */ + public function testDAYOpenOffice($expectedResultOpenOffice, string $dateTimeValue): void + { + self::setOpenOffice(); + $this->mightHaveException($expectedResultOpenOffice); + $sheet = $this->sheet; + $sheet->getCell('A2')->setValue("=DAY($dateTimeValue)"); + self::assertSame($expectedResultOpenOffice, $sheet->getCell('A2')->getCalculatedValue()); + } + + public function providerDAYOpenOffice() + { + return require 'tests/data/Calculation/DateTime/DAYOpenOffice.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php index 47449e0d..5d6ba29e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/Days360Test.php @@ -2,32 +2,21 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class Days360Test extends TestCase +class Days360Test extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDAYS360 * * @param mixed $expectedResult - * @param $startDate - * @param $endDate - * @param $method */ - public function testDAYS360($expectedResult, $startDate, $endDate, $method): void + public function testDAYS360($expectedResult, string $formula): void { - $result = DateTime::DAYS360($startDate, $endDate, $method); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('2000-02-29'); + $sheet->getCell('C1')->setValue('2000-03-31'); + $sheet->getCell('A1')->setValue("=DAYS360($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerDAYS360() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php index fe31dfcc..8b3ea392 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/DaysTest.php @@ -2,35 +2,44 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use DateTime; +use DateTimeImmutable; +use Exception; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Days; -class DaysTest extends TestCase +class DaysTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerDAYS * * @param mixed $expectedResult - * @param $endDate - * @param $startDate */ - public function testDAYS($expectedResult, $endDate, $startDate): void + public function testDAYS($expectedResult, string $formula): void { - $result = DateTime::DAYS($endDate, $startDate); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('C1')->setValue('1954-11-30'); + $sheet->getCell('A1')->setValue("=DAYS($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerDAYS() { return require 'tests/data/Calculation/DateTime/DAYS.php'; } + + public function testObject(): void + { + $obj1 = new DateTime('2000-3-31'); + $obj2 = new DateTimeImmutable('2000-2-29'); + self::assertSame(31, Days::funcDays($obj1, $obj2)); + } + + public function testNonDateObject(): void + { + $obj1 = new Exception(); + $obj2 = new DateTimeImmutable('2000-2-29'); + self::assertSame('#VALUE!', Days::funcDays($obj1, $obj2)); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php index a887ba5b..384e1aec 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EDateTest.php @@ -2,31 +2,22 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\EDate; -class EDateTest extends TestCase +class EDateTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerEDATE * * @param mixed $expectedResult - * @param $dateValue - * @param $adjustmentMonths */ - public function testEDATE($expectedResult, $dateValue, $adjustmentMonths): void + public function testEDATE($expectedResult, string $formula): void { - $result = DateTime::EDATE($dateValue, $adjustmentMonths); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=EDATE($formula)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerEDATE() @@ -36,18 +27,18 @@ class EDateTest extends TestCase public function testEDATEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::EDATE('2012-1-26', -1); + $result = EDate::funcEDate('2012-1-26', -1); self::assertEquals(1324857600, $result); self::assertEqualsWithDelta(1324857600, $result, 1E-8); } public function testEDATEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::EDATE('2012-1-26', -1); + $result = EDate::funcEDate('2012-1-26', -1); // Must return an object... self::assertIsObject($result); // ... of the correct type diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php index f9c54039..1af81c9f 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/EoMonthTest.php @@ -2,31 +2,22 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\EoMonth; -class EoMonthTest extends TestCase +class EoMonthTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerEOMONTH * * @param mixed $expectedResult - * @param $dateValue - * @param $adjustmentMonths */ - public function testEOMONTH($expectedResult, $dateValue, $adjustmentMonths): void + public function testEOMONTH($expectedResult, string $formula): void { - $result = DateTime::EOMONTH($dateValue, $adjustmentMonths); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=EOMONTH($formula)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertEquals($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerEOMONTH() @@ -36,23 +27,22 @@ class EoMonthTest extends TestCase public function testEOMONTHtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::EOMONTH('2012-1-26', -1); + $result = EoMonth::funcEomonth('2012-1-26', -1); self::assertEquals(1325289600, $result); - self::assertEqualsWithDelta(1325289600, $result, 1E-8); } public function testEOMONTHtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::EOMONTH('2012-1-26', -1); + $result = EoMonth::funcEomonth('2012-1-26', -1); // Must return an object... self::assertIsObject($result); // ... of the correct type self::assertTrue(is_a($result, 'DateTimeInterface')); // ... with the correct value - self::assertEquals($result->format('d-M-Y'), '31-Dec-2011'); + self::assertSame($result->format('d-M-Y'), '31-Dec-2011'); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php index 2d0cd5d1..99544b5a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/HourTest.php @@ -2,30 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class HourTest extends TestCase +class HourTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerHOUR * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testHOUR($expectedResult, $dateTimeValue): void + public function testHOUR($expectedResult, string $dateTimeValue): void { - $result = DateTime::HOUROFDAY($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=HOUR($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerHOUR() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php index 1ef0080a..b27ca7d5 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/IsoWeekNumTest.php @@ -2,34 +2,46 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class IsoWeekNumTest extends TestCase +class IsoWeekNumTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerISOWEEKNUM * * @param mixed $expectedResult - * @param mixed $dateValue + * @param string $dateValue */ public function testISOWEEKNUM($expectedResult, $dateValue): void { - $result = DateTime::ISOWEEKNUM($dateValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=ISOWEEKNUM($dateValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerISOWEEKNUM() { return require 'tests/data/Calculation/DateTime/ISOWEEKNUM.php'; } + + /** + * @dataProvider providerISOWEEKNUM1904 + * + * @param mixed $expectedResult + * @param string $dateValue + */ + public function testISOWEEKNUM1904($expectedResult, $dateValue): void + { + $this->mightHaveException($expectedResult); + self::setMac1904(); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=ISOWEEKNUM($dateValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); + } + + public function providerISOWEEKNUM1904() + { + return require 'tests/data/Calculation/DateTime/ISOWEEKNUM1904.php'; + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php index 8472c6de..cbc2a1a4 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MinuteTest.php @@ -2,30 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class MinuteTest extends TestCase +class MinuteTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerMINUTE * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testMINUTE($expectedResult, $dateTimeValue): void + public function testMINUTE($expectedResult, string $dateTimeValue): void { - $result = DateTime::MINUTE($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=MINUTE($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerMINUTE() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php index 62513702..a9f70229 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MonthTest.php @@ -2,30 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class MonthTest extends TestCase +class MonthTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerMONTH * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testMONTH($expectedResult, $dateTimeValue): void + public function testMONTH($expectedResult, string $dateTimeValue): void { - $result = DateTime::MONTHOFYEAR($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=MONTH($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerMONTH() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php new file mode 100644 index 00000000..d14f7d7d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/MovedFunctionsTest.php @@ -0,0 +1,63 @@ +format('s'); + $nowResult = DateTime::DATETIMENOW(); + $todayResult = DateTime::DATENOW(); + $dtEnd = new DateTimeImmutable(); + $endSecond = $dtEnd->format('s'); + } while ($startSecond !== $endSecond); + self::assertSame(DateTime::DAYOFMONTH($nowResult), DateTime::DAYOFMONTH($todayResult)); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php index e366c44e..568c661c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NetworkDaysTest.php @@ -2,29 +2,47 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class NetworkDaysTest extends TestCase +class NetworkDaysTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerNETWORKDAYS * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 */ - public function testNETWORKDAYS($expectedResult, ...$args): void + public function testNETWORKDAYS($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', ?array $arg3 = null): void { - $result = DateTime::NETWORKDAYS(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + $dateArray = []; + if (is_array($arg3)) { + if (array_key_exists(0, $arg3) && is_array($arg3[0])) { + $dateArray = $arg3[0]; + } else { + $dateArray = $arg3; + } + } + $dateIndex = 0; + foreach ($dateArray as $date) { + ++$dateIndex; + $sheet->getCell("C$dateIndex")->setValue($date); + } + $arrayArg = $dateIndex ? ", C1:C$dateIndex" : ''; + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=NETWORKDAYS()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=NETWORKDAYS(A1)'); + } else { + $sheet->getCell('B1')->setValue("=NETWORKDAYS(A1, A2$arrayArg)"); + } + self::assertEquals($expectedResult, $sheet->getCell('B1')->getCalculatedValue()); } public function providerNETWORKDAYS() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php index f139f703..e0f68c24 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/NowTest.php @@ -3,15 +3,12 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; use DateTimeImmutable; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PHPUnit\Framework\TestCase; -class NowTest extends TestCase +class NowTest extends AllSetupTeardown { public function testNow(): void { - $spreadsheet = new Spreadsheet(); - $sheet = $spreadsheet->getActiveSheet(); + $sheet = $this->sheet; // Loop to avoid rare edge case where first calculation // and second do not take place in same second. do { @@ -21,7 +18,6 @@ class NowTest extends TestCase $dtEnd = new DateTimeImmutable(); $endSecond = $dtEnd->format('s'); } while ($startSecond !== $endSecond); - //echo("\n"); var_dump($sheet->getCell('A1')->getCalculatedValue()); echo ("\n"); $sheet->setCellValue('B1', '=YEAR(A1)'); $sheet->setCellValue('C1', '=MONTH(A1)'); $sheet->setCellValue('D1', '=DAY(A1)'); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php index bc2b0752..03cef8bc 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/SecondTest.php @@ -2,30 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class SecondTest extends TestCase +class SecondTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerSECOND * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testSECOND($expectedResult, $dateTimeValue): void + public function testSECOND($expectedResult, string $dateTimeValue): void { - $result = DateTime::SECOND($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=SECOND($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23 2:23:46'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerSECOND() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php index 3ef58374..f33b5aac 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeTest.php @@ -2,39 +2,24 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Time; -class TimeTest extends TestCase +class TimeTest extends AllSetupTeardown { - private $returnDateType; - - private $calendar; - - protected function setUp(): void - { - $this->returnDateType = Functions::getReturnDateType(); - $this->calendar = Date::getExcelCalendar(); - } - - protected function tearDown(): void - { - Functions::setReturnDateType($this->returnDateType); - Date::setExcelCalendar($this->calendar); - } - /** * @dataProvider providerTIME * * @param mixed $expectedResult */ - public function testTIME($expectedResult, ...$args): void + public function testTIME($expectedResult, string $formula): void { - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $result = DateTime::TIME(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('15'); + $sheet->getCell('B2')->setValue('32'); + $sheet->getCell('B3')->setValue('50'); + $sheet->getCell('A1')->setValue("=TIME($formula)"); + self::assertEqualsWithDelta($expectedResult, $sheet->getCell('A1')->getCalculatedValue(), 1E-8); } public function providerTIME() @@ -44,17 +29,17 @@ class TimeTest extends TestCase public function testTIMEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC); + self::setUnixReturn(); - $result = DateTime::TIME(7, 30, 20); + $result = Time::funcTime(7, 30, 20); self::assertEqualsWithDelta(27020, $result, 1E-8); } public function testTIMEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_OBJECT); + self::setObjectReturn(); - $result = DateTime::TIME(7, 30, 20); + $result = Time::funcTime(7, 30, 20); // Must return an object... self::assertIsObject($result); // ... of the correct type @@ -65,17 +50,14 @@ class TimeTest extends TestCase public function testTIME1904(): void { - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - $result = DateTime::TIME(0, 0, 0); + self::setMac1904(); + $result = Time::funcTime(0, 0, 0); self::assertEquals(0, $result); } public function testTIME1900(): void { - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - $result = DateTime::TIME(0, 0, 0); + $result = Time::funcTime(0, 0, 0); self::assertEquals(0, $result); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php index 04b8c058..f144c6f2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TimeValueTest.php @@ -2,20 +2,10 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\TimeValue; -class TimeValueTest extends TestCase +class TimeValueTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerTIMEVALUE * @@ -24,7 +14,11 @@ class TimeValueTest extends TestCase */ public function testTIMEVALUE($expectedResult, $timeValue): void { - $result = DateTime::TIMEVALUE($timeValue); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('03:45:52'); + $sheet->getCell('A1')->setValue("=TIMEVALUE($timeValue)"); + $result = $sheet->getCell('A1')->getCalculatedValue(); self::assertEqualsWithDelta($expectedResult, $result, 1E-8); } @@ -35,18 +29,18 @@ class TimeValueTest extends TestCase public function testTIMEVALUEtoUnixTimestamp(): void { - Functions::setReturnDateType(Functions::RETURNDATE_UNIX_TIMESTAMP); + self::setUnixReturn(); - $result = DateTime::TIMEVALUE('7:30:20'); + $result = TimeValue::funcTimeValue('7:30:20'); self::assertEquals(23420, $result); self::assertEqualsWithDelta(23420, $result, 1E-8); } public function testTIMEVALUEtoDateTimeObject(): void { - Functions::setReturnDateType(Functions::RETURNDATE_PHP_DATETIME_OBJECT); + self::setObjectReturn(); - $result = DateTime::TIMEVALUE('7:30:20'); + $result = TimeValue::funcTimeValue('7:30:20'); // Must return an object... self::assertIsObject($result); // ... of the correct type diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php new file mode 100644 index 00000000..6ce82bfd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/TodayTest.php @@ -0,0 +1,34 @@ +sheet; + // Loop to avoid rare edge case where first calculation + // and second do not take place in same second. + do { + $dtStart = new DateTimeImmutable(); + $startSecond = $dtStart->format('s'); + $sheet->setCellValue('A1', '=TODAY()'); + $dtEnd = new DateTimeImmutable(); + $endSecond = $dtEnd->format('s'); + } while ($startSecond !== $endSecond); + $sheet->setCellValue('B1', '=YEAR(A1)'); + $sheet->setCellValue('C1', '=MONTH(A1)'); + $sheet->setCellValue('D1', '=DAY(A1)'); + $sheet->setCellValue('E1', '=HOUR(A1)'); + $sheet->setCellValue('F1', '=MINUTE(A1)'); + $sheet->setCellValue('G1', '=SECOND(A1)'); + self::assertSame((int) $dtStart->format('Y'), $sheet->getCell('B1')->getCalculatedValue()); + self::assertSame((int) $dtStart->format('m'), $sheet->getCell('C1')->getCalculatedValue()); + self::assertSame((int) $dtStart->format('d'), $sheet->getCell('D1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('E1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('F1')->getCalculatedValue()); + self::assertSame(0, $sheet->getCell('G1')->getCalculatedValue()); + } +} diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php index 99aa6f7c..f1bc51f3 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekDayTest.php @@ -2,33 +2,22 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel\Weekday; -class WeekDayTest extends TestCase +class WeekDayTest extends AllSetupTeardown { - private $excelCalendar; - - protected function setUp(): void - { - $this->excelCalendar = Date::getExcelCalendar(); - } - - protected function tearDown(): void - { - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerWEEKDAY * * @param mixed $expectedResult */ - public function testWEEKDAY($expectedResult, ...$args): void + public function testWEEKDAY($expectedResult, string $formula): void { - $result = DateTime::WEEKDAY(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKDAY($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerWEEKDAY() @@ -38,9 +27,9 @@ class WeekDayTest extends TestCase public function testWEEKDAYwith1904Calendar(): void { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - self::assertEquals(7, DateTime::WEEKDAY('1904-01-02')); - self::assertEquals(6, DateTime::WEEKDAY('1904-01-01')); - self::assertEquals(6, DateTime::WEEKDAY(null)); + self::setMac1904(); + self::assertEquals(7, Weekday::funcWeekDay('1904-01-02')); + self::assertEquals(6, Weekday::funcWeekDay('1904-01-01')); + self::assertEquals(6, Weekday::funcWeekDay(null)); } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php index 17119f28..c3e785f3 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WeekNumTest.php @@ -2,33 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class WeekNumTest extends TestCase +class WeekNumTest extends AllSetupTeardown { - private $excelCalendar; - - protected function setUp(): void - { - $this->excelCalendar = Date::getExcelCalendar(); - } - - protected function tearDown(): void - { - Date::setExcelCalendar($this->excelCalendar); - } - /** * @dataProvider providerWEEKNUM * * @param mixed $expectedResult */ - public function testWEEKNUM($expectedResult, ...$args): void + public function testWEEKNUM($expectedResult, string $formula): void { - $result = DateTime::WEEKNUM(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKNUM($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerWEEKNUM() @@ -36,13 +23,23 @@ class WeekNumTest extends TestCase return require 'tests/data/Calculation/DateTime/WEEKNUM.php'; } - public function testWEEKNUMwith1904Calendar(): void + /** + * @dataProvider providerWEEKNUM1904 + * + * @param mixed $expectedResult + */ + public function testWEEKNUM1904($expectedResult, string $formula): void { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - self::assertEquals(27, DateTime::WEEKNUM('2004-07-02')); - self::assertEquals(1, DateTime::WEEKNUM('1904-01-02')); - self::assertEquals(1, DateTime::WEEKNUM(null)); - // The following is a bug in Excel. - self::assertEquals(0, DateTime::WEEKNUM('1904-01-01')); + $this->mightHaveException($expectedResult); + self::setMac1904(); + $sheet = $this->sheet; + $sheet->getCell('B1')->setValue('1954-11-23'); + $sheet->getCell('A1')->setValue("=WEEKNUM($formula)"); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); + } + + public function providerWEEKNUM1904() + { + return require 'tests/data/Calculation/DateTime/WEEKNUM1904.php'; } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php index 4784e463..ec2a5402 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/WorkDayTest.php @@ -2,29 +2,47 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class WorkDayTest extends TestCase +class WorkDayTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerWORKDAY * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 */ - public function testWORKDAY($expectedResult, ...$args): void + public function testWORKDAY($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', ?array $arg3 = null): void { - $result = DateTime::WORKDAY(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + $dateArray = []; + if (is_array($arg3)) { + if (array_key_exists(0, $arg3) && is_array($arg3[0])) { + $dateArray = $arg3[0]; + } else { + $dateArray = $arg3; + } + } + $dateIndex = 0; + foreach ($dateArray as $date) { + ++$dateIndex; + $sheet->getCell("C$dateIndex")->setValue($date); + } + $arrayArg = $dateIndex ? ", C1:C$dateIndex" : ''; + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=WORKDAY()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=WORKDAY(A1)'); + } else { + $sheet->getCell('B1')->setValue("=WORKDAY(A1, A2$arrayArg)"); + } + self::assertEquals($expectedResult, $sheet->getCell('B1')->getCalculatedValue()); } public function providerWORKDAY() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php index 05f11310..e6ac823a 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearFracTest.php @@ -2,29 +2,39 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class YearFracTest extends TestCase +class YearFracTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerYEARFRAC * * @param mixed $expectedResult + * @param mixed $arg1 + * @param mixed $arg2 + * @param mixed $arg3 */ - public function testYEARFRAC($expectedResult, ...$args): void + public function testYEARFRAC($expectedResult, $arg1 = 'omitted', $arg2 = 'omitted', $arg3 = 'omitted'): void { - $result = DateTime::YEARFRAC(...$args); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + if ($arg1 !== null) { + $sheet->getCell('A1')->setValue($arg1); + } + if ($arg2 !== null) { + $sheet->getCell('A2')->setValue($arg2); + } + if ($arg3 !== null) { + $sheet->getCell('A3')->setValue($arg3); + } + if ($arg1 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC()'); + } elseif ($arg2 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1)'); + } elseif ($arg3 === 'omitted') { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1, A2)'); + } else { + $sheet->getCell('B1')->setValue('=YEARFRAC(A1, A2, A3)'); + } + self::assertEqualswithDelta($expectedResult, $sheet->getCell('B1')->getCalculatedValue(), 1E-6); } public function providerYEARFRAC() diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php index bbdaf92a..7942f06c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/DateTime/YearTest.php @@ -2,30 +2,20 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PHPUnit\Framework\TestCase; - -class YearTest extends TestCase +class YearTest extends AllSetupTeardown { - protected function setUp(): void - { - Functions::setCompatibilityMode(Functions::COMPATIBILITY_EXCEL); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - } - /** * @dataProvider providerYEAR * * @param mixed $expectedResult - * @param $dateTimeValue */ - public function testYEAR($expectedResult, $dateTimeValue): void + public function testYEAR($expectedResult, string $dateTimeValue): void { - $result = DateTime::YEAR($dateTimeValue); - self::assertEqualsWithDelta($expectedResult, $result, 1E-8); + $this->mightHaveException($expectedResult); + $sheet = $this->sheet; + $sheet->getCell('A1')->setValue("=YEAR($dateTimeValue)"); + $sheet->getCell('B1')->setValue('1954-11-23'); + self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); } public function providerYEAR() diff --git a/tests/data/Calculation/DateTime/DATE.php b/tests/data/Calculation/DateTime/DATE.php index 9acc6716..72816b76 100644 --- a/tests/data/Calculation/DateTime/DATE.php +++ b/tests/data/Calculation/DateTime/DATE.php @@ -1,319 +1,84 @@ [ - 6890, // '11th November 1918' - 18, 11, 11, - ], - 'Excel 1900 Calendar Base Date' => [ - 1, - 1900, 1, 1, - ], - 'Day before Excel mythical 1900 leap day' => [ - 59, - 1900, 2, 28, - ], - 'Excel mythical 1900 leap day' => [ - 60, - 1900, 2, 29, - ], - 'Day after Excel mythical 1900 leap day' => [ - 61, - 1900, 3, 1, - ], - 'Day after Excel actual 1904 leap day' => [ - 713, - 1901, 12, 13, - ], - 'signed 32-bit Unix Timestamp Earliest Date' => [ - 714, - 1901, 12, 14, - ], - 'Day before Excel 1904 Calendar Base Date' => [ - 1461, - 1903, 12, 31, - ], - 'Excel 1904 Calendar Base Date' => [ - 1462, - 1904, 1, 1, - ], - 'Day after Excel 1904 Calendar Base Date' => [ - 1463, - 1904, 1, 2, - ], - [ - 22269, - 1960, 12, 19, - ], - 'Unix Timestamp Base Date' => [ - 25569, - 1970, 1, 1, - ], - [ - 30292, - 1982, 12, 7, - ], - [ - 39611, - 2008, 6, 12, - ], - '32-bit signed Unix Timestamp Latest Date' => [ - 50424, - 2038, 1, 19, - ], - 'Day after 32-bit signed Unix Timestamp Latest Date' => [ - 50425, - 2038, 1, 20, - ], - [ - 39448, - 2008, 1, 1, - ], - [ - 39447, - 2008, 1, null, - ], - [ - 39446, - 2008, 1, -1, - ], - [ - 39417, - 2008, 1, -30, - ], - [ - 39416, - 2008, 1, -31, - ], - [ - 39082, - 2008, 1, -365, - ], - [ - 39508, - 2008, 3, 1, - ], - [ - 39507, - 2008, 3, null, - ], - [ - 39506, - 2008, 3, -1, - ], - [ - 39142, - 2008, 3, -365, - ], - [ - 39417, - 2008, null, 1, - ], - [ - 39387, - 2008, -1, 1, - ], - [ - 39083, - 2008, -11, 1, - ], - [ - 39052, - 2008, -12, 1, - ], - [ - 39022, - 2008, -13, 1, - ], - [ - 39051, - 2008, -13, 30, - ], - [ - 39021, - 2008, -13, null, - ], - [ - 38991, - 2008, -13, -30, - ], - [ - 38990, - 2008, -13, -31, - ], - [ - 39814, - 2008, 13, 1, - ], - [ - 39507, - 2007, 15, null, - ], - [ - 40210, - 2008, 26, 1, - ], - [ - 40199, - 2008, 26, -10, - ], - [ - 38686, - 2008, -26, 61, - ], - [ - 39641, - 2010, -15, -50, - ], - [ - 39741, - 2010, -15, 50, - ], - [ - 40552, - 2010, 15, -50, - ], - [ - 40652, - 2010, 15, 50, - ], - [ - 40179, - 2010, 1.5, 1, - ], - [ - 40178, - 2010, 1.5, 0, - ], - [ - 40148, - 2010, 0, 1.5, - ], - [ - 40179, - 2010, 1, 1.5, - ], - [ - 41075, - 2012, 6, 15, - ], - [ - 41060, - 2012, 6, null, - ], - [ - 40892, - 2012, null, 15, - ], - [ - 167, - null, 6, 15, - ], - [ - 3819, - 10, 6, 15, - ], - [ - 3622, - 10, null, null, - ], - [ - 274, - null, 10, null, - ], - [ - '#NUM!', - null, null, 10, - ], - [ - '#NUM!', - -20, null, null, - ], - [ - '#NUM!', - -20, 6, 15, - ], - 'Excel Maximum Date' => [ - 2958465, - 9999, 12, 31, - ], - 'Exceeded Excel Maximum Date' => [ - '#NUM!', - 10000, 1, 1, - ], - [ - 39670, - 2008, 8, 10, - ], - [ - 39813, - 2008, 12, 31, - ], - [ - 39692, - 2008, 8, 32, - ], - [ - 39844, - 2008, 13, 31, - ], - [ - 39813, - 2009, 1, 0, - ], - [ - 39812, - 2009, 1, -1, - ], - [ - 39782, - 2009, 0, 0, - ], - [ - 39781, - 2009, 0, -1, - ], - [ - 39752, - 2009, -1, 0, - ], - [ - 39751, - 2009, -1, -1, - ], - [ - 40146, - 2010, 0, -1, - ], - [ - 40329, - 2010, 5, 31, - ], + [6890, '18, 11, 11'], // year without centure + [1, '1900, 1, 1'], // Excel 1900 Calendar BaseDate + [59, '1900, 2, 28'], // Day before Excel mythical 1900 leap day + [60, '1900, 2, 29'], // Excel mythical 1900 leap day + [61, '1900, 3, 1'], // Day after Excel mythical 1900 leap day + [713, '1901, 12, 13'], // Day after actual 1904 leap day + [714, '1901, 12, 14'], // signed 32-bit Unix Timestamp Earliest Date + [1461, '1903, 12, 31'], // Day before Excel 1904 Calendar Base Date + [1462, '1904, 1, 1'], // Excel 1904 Calendar Base Date + [1463, '1904, 1, 2'], // Day after Excel 1904 Calendar Base Date + [22269, '1960, 12, 19'], + [25569, '1970, 1, 1'], // Unix Timestamp Base Date + [30292, '1982, 12, 7'], + [39611, '2008, 6, 12'], + [50424, '2038, 1, 19'], // 32-bit signed Unix Timestamp Latest Date + [50425, '2038, 1, 20'], // Day after 32-bit signed Unix Timestamp Latest Date + [39448, '2008, 1, 1'], + [39447, '2008, 1, Q15'], + [39446, '2008, 1, -1'], + [39417, '2008, 1, -30'], + [39416, '2008, 1, -31'], + [39082, '2008, 1, -365'], + [39508, '2008, 3, 1'], + [39507, '2008, 3, Q15'], + [39506, '2008, 3, -1'], + [39142, '2008, 3, -365'], + [39417, '2008, Q15, 1'], + [39387, '2008, -1, 1'], + [39083, '2008, -11, 1'], + [39052, '2008, -12, 1'], + [39022, '2008, -13, 1'], + [39051, '2008, -13, 30'], + [39021, '2008, -13, Q15'], + [38991, '2008, -13, -30'], + [38990, '2008, -13, -31'], + [39814, '2008, 13, 1'], + [39507, '2007, 15, Q15'], + [40210, '2008, 26, 1'], + [40199, '2008, 26, -10'], + [38686, '2008, -26, 61'], + [39641, '2010, -15, -50'], + [39741, '2010, -15, 50'], + [40552, '2010, 15, -50'], + [40652, '2010, 15, 50'], + [40179, '2010, 1.5, 1'], + [40178, '2010, 1.5, 0'], + [40148, '2010, 0, 1.5'], + [40179, '2010, 1, 1.5'], + [41075, '2012, 6, 15'], + [41060, '2012, 6, Q15'], + [40892, '2012, Q15, 15'], + [167, 'Q15, 6, 15'], + [3819, '10, 6, 15'], + [3622, '10, Q15, Q16'], + [274, 'Q14, 10, Q15'], + ['#NUM!', 'Q14, Q15, 10'], + ['#NUM!', '-20, Q14, Q15'], + ['#NUM!', '-20, 6, 15'], + [2958465, '9999, 12, 31'], // Excel maximum date + ['#NUM!', '10000, 1, 1'], // Exceeded Excel maximum date + [39670, '2008, 8, 10'], + [39813, '2008, 12, 31'], + [39692, '2008, 8, 32'], + [39844, '2008, 13, 31'], + [39813, '2009, 1, 0'], + [39812, '2009, 1, -1'], + [39782, '2009, 0, 0'], + [39781, '2009, 0, -1'], + [39752, '2009, -1, 0'], + [39751, '2009, -1, -1'], + [40146, '2010, 0, -1'], + [40329, '2010, 5, 31'], + [40199, '2010, 1, "21st"'], // Excel can't parse ordinal, PhpSpreadsheet can + [40258, '2010, "March", "21st"'], // ordinal and month name // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40199, - 2010, 1, '21st', - ], - // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40258, - 2010, 'March', '21st', - ], - // MS Excel will fail with a #VALUE return, but PhpSpreadsheet can parse this date - [ - 40258, - 2010, 'March', 21, - ], - [ - '#VALUE!', - 'ABC', 1, 21, - ], - [ - '#VALUE!', - 2010, 'DEF', 21, - ], - [ - '#VALUE!', - 2010, 3, 'GHI', - ], + [40258, '2010, "March", 21'], // Excel can't parse month name, PhpSpreadsheet can + ['#VALUE!', '"ABC", 1, 21'], + ['#VALUE!', '2010, "DEF", 21'], + ['#VALUE!', '2010, 3, "GHI"'], + ['exception', '2010, 3'], ]; diff --git a/tests/data/Calculation/DateTime/DATEDIF.php b/tests/data/Calculation/DateTime/DATEDIF.php index a6d2d761..5ba0bd3c 100644 --- a/tests/data/Calculation/DateTime/DATEDIF.php +++ b/tests/data/Calculation/DateTime/DATEDIF.php @@ -1,424 +1,112 @@