diff --git a/docs/topics/autofilters.md b/docs/topics/autofilters.md index d5a07f8b..16efb8ff 100644 --- a/docs/topics/autofilters.md +++ b/docs/topics/autofilters.md @@ -327,14 +327,14 @@ $columnFilter->setFilterType( ``` When defining the rule for a dynamic filter, we don't define a value (we -can simply set that to NULL) but we do specify the dynamic filter +can simply set that to null string) but we do specify the dynamic filter category. ```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, - NULL, + '', \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE ) ->setRuleType( diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 4ee0a9ef..a64f3781 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5417,7 +5417,7 @@ parameters: - message: "#^Parameter \\#1 \\$excelTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:excelToTimestamp\\(\\) expects float\\|int, float\\|int\\|string given\\.$#" - count: 2 + count: 1 path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - @@ -5430,36 +5430,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - message: "#^Parameter \\#2 \\$now of function strtotime expects int, int\\|false given\\.$#" - count: 6 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - - message: "#^Parameter \\#5 \\$day of function gmmktime expects int, string given\\.$#" - count: 8 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - - message: "#^Parameter \\#6 \\$year of function gmmktime expects int, string given\\.$#" - count: 13 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - - message: "#^Parameter \\#4 \\$mon of function gmmktime expects int, string given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - - message: "#^Parameter \\#5 \\$day of function gmmktime expects int, float given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - - - message: "#^Parameter \\#1 \\$attributes of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttributes\\(\\) expects array\\, array\\ given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Worksheet/AutoFilter.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:calculateTopTenValue\\(\\) has no return typehint specified\\.$#" count: 1 @@ -7445,21 +7415,11 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/Column/RuleTest.php - - - message: "#^Parameter \\#1 \\$attributes of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttributes\\(\\) expects array\\, array\\ given\\.$#" - count: 3 - path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php - - message: "#^Parameter \\#2 \\$pValue of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\:\\:setAttribute\\(\\) expects string, int given\\.$#" count: 1 path: tests/PhpSpreadsheetTests/Worksheet/AutoFilter/ColumnTest.php - - - message: "#^Parameter \\#1 \\$pColumn of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\:\\:setColumn\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\AutoFilter\\\\Column\\|string, float given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php - - message: "#^Parameter \\#1 \\$im of function imagecolorallocate expects resource, resource\\|false given\\.$#" count: 1 diff --git a/samples/Autofilter/10_Autofilter_dynamic_dates.php b/samples/Autofilter/10_Autofilter_dynamic_dates.php new file mode 100644 index 00000000..959925c1 --- /dev/null +++ b/samples/Autofilter/10_Autofilter_dynamic_dates.php @@ -0,0 +1,100 @@ +createSheet(); + $sheet->setTitle($rule); + $sheet->getCell('A1')->setValue('Date'); + $row = 1; + $date = new DateTime(); + $year = (int) $date->format('Y'); + $month = (int) $date->format('m'); + $day = (int) $date->format('d'); + $yearMinus2 = $year - 2; + $sheet->getCell('B1')->setValue("=DATE($year, $month, $day)"); + // Each day for two weeks before today through 2 weeks after + for ($dayOffset = -14; $dayOffset < 14; ++$dayOffset) { + ++$row; + $sheet->getCell("A$row")->setValue("=B1+($dayOffset)"); + } + // First and last day of each month, starting with January 2 years before, + // through December 2 years after. + for ($monthOffset = 0; $monthOffset < 48; ++$monthOffset) { + ++$row; + $sheet->getCell("A$row")->setValue("=DATE($yearMinus2, $monthOffset, 1)"); + ++$row; + $sheet->getCell("A$row")->setValue("=DATE($yearMinus2, $monthOffset + 1, 0)"); + } + $sheet->getStyle("A2:A$row")->getNumberFormat()->setFormatCode('yyyy-mm-dd'); + $sheet->getStyle('B1')->getNumberFormat()->setFormatCode('yyyy-mm-dd'); + $sheet->getColumnDimension('A')->setAutoSize(true); + $sheet->getColumnDimension('B')->setAutoSize(true); + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$row"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $sheet->setSelectedCell('B1'); +} + +// Create new Spreadsheet object +$helper->log('Create new Spreadsheet object'); +$spreadsheet = new Spreadsheet(); + +// Set document properties +$helper->log('Set document properties'); +$spreadsheet->getProperties()->setCreator('Owen Leibman') + ->setLastModifiedBy('Owen Leibman') + ->setTitle('PhpSpreadsheet Test Document') + ->setSubject('PhpSpreadsheet Test Document') + ->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.') + ->setKeywords('office PhpSpreadsheet php') + ->setCategory('Test result file'); + +$ruleNames = [ + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2, + Rule::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3, +]; + +// Create the worksheets +foreach ($ruleNames as $ruleName) { + $helper->log("Add data and filter for $ruleName"); + createSheet($spreadsheet, $ruleName); +} +$spreadsheet->removeSheetByIndex(0); +$spreadsheet->setActiveSheetIndex(0); +// Save +$helper->write($spreadsheet, __FILE__); diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php index b846f7b6..c75e20ba 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -2,12 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use DateTime; +use DateTimeZone; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; class AutoFilter { @@ -358,38 +360,38 @@ class AutoFilter if (is_numeric($rule['value'])) { // Numeric values are tested using the appropriate operator switch ($rule['operator']) { - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = ($cellValue == $rule['value']); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = ($cellValue != $rule['value']); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: $retVal = ($cellValue > $rule['value']); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: $retVal = ($cellValue >= $rule['value']); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: $retVal = ($cellValue < $rule['value']); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: $retVal = ($cellValue <= $rule['value']); break; } } elseif ($rule['value'] == '') { switch ($rule['operator']) { - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = (($cellValue == '') || ($cellValue === null)); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = (($cellValue != '') && ($cellValue !== null)); break; @@ -439,7 +441,8 @@ class AutoFilter } if (is_numeric($cellValue)) { - $dateValue = date('m', Date::excelToTimestamp($cellValue)); + $dateObject = Date::excelToDateTimeObject((float) $cellValue, new DateTimeZone('UTC')); + $dateValue = (int) $dateObject->format('m'); if (in_array($dateValue, $monthSet)) { return true; } @@ -457,6 +460,224 @@ class AutoFilter private static $toReplace = ['.*', '.', '~', '\*', '\?']; + private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime + { + $baseDate = new DateTime(); + $baseDate->setDate($year, $month, $day); + $baseDate->setTime($hour, $minute, $second); + + return $baseDate; + } + + private const DATE_FUNCTIONS = [ + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH => 'dynamicLastMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER => 'dynamicLastQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK => 'dynamicLastWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR => 'dynamicLastYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH => 'dynamicNextMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER => 'dynamicNextQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK => 'dynamicNextWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR => 'dynamicNextYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH => 'dynamicThisMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER => 'dynamicThisQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK => 'dynamicThisWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR => 'dynamicThisYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY => 'dynamicToday', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW => 'dynamicTomorrow', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE => 'dynamicYearToDate', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY => 'dynamicYesterday', + ]; + + private static function dynamicLastMonth(): array + { + $maxval = new DateTime(); + $year = (int) $maxval->format('Y'); + $month = (int) $maxval->format('m'); + $maxval->setDate($year, $month, 1); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 month'); + + return [$val, $maxval]; + } + + private static function firstDayOfQuarter(): DateTime + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $month = 3 * intdiv($month - 1, 3) + 1; + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + + return $val; + } + + private static function dynamicLastQuarter(): array + { + $maxval = self::firstDayOfQuarter(); + $val = clone $maxval; + $val->modify('-3 months'); + + return [$val, $maxval]; + } + + private static function dynamicLastWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek + 7; // revert to prior Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicLastYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year - 1, 1, 1); + $maxval = self::makeDateObject($year, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicNextMonth(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + $val->modify('+1 month'); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicNextQuarter(): array + { + $val = self::firstDayOfQuarter(); + $val->modify('+3 months'); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicNextWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $add = 7 - $dayOfWeek; // move to next Sunday + $val->modify("+$add days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicNextYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year + 1, 1, 1); + $maxval = self::makeDateObject($year + 2, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicThisMonth(): array + { + $baseDate = new DateTime(); + $baseDate->setTime(0, 0, 0); + $year = (int) $baseDate->format('Y'); + $month = (int) $baseDate->format('m'); + $val = self::makeDateObject($year, $month, 1); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicThisQuarter(): array + { + $val = self::firstDayOfQuarter(); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicThisWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek; // revert to Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicThisYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year, 1, 1); + $maxval = self::makeDateObject($year + 1, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicToday(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicTomorrow(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $val->modify('+1 day'); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYearToDate(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = self::makeDateObject((int) $maxval->format('Y'), 1, 1); + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYesterday(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 day'); + + return [$val, $maxval]; + } + /** * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation. * @@ -467,118 +688,25 @@ class AutoFilter */ private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn) { - $rDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC); - $val = $maxVal = null; - $ruleValues = []; - $baseDate = DateTimeExcel\Current::now(); + $val = $maxVal = null; + $callBack = [__CLASS__, self::DATE_FUNCTIONS[$dynamicRuleType]]; // What if not found? // Calculate start/end dates for the required date range based on current date - switch ($dynamicRuleType) { - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK: - $baseDate = strtotime('-7 days', $baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK: - $baseDate = strtotime('-7 days', $baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH: - $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH: - $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER: - $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER: - $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR: - $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR: - $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - } - - switch ($dynamicRuleType) { - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW: - $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate)); - $val = (int) Date::PHPToExcel($baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE: - $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate)); - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR: - $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER: - $thisMonth = date('m', $baseDate); - $thisQuarter = floor(--$thisMonth / 3); - $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH: - $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK: - $dayOfWeek = date('w', $baseDate); - $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek; - $maxVal = $val + 7; - - break; - } - - switch ($dynamicRuleType) { - // Adjust Today dates for Yesterday and Tomorrow - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY: - --$maxVal; - --$val; - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW: - ++$maxVal; - ++$val; - - break; + // Val is lowest permitted value. + // Maxval is greater than highest permitted value + $val = $maxval = 0; + if (is_callable($callBack)) { + [$val, $maxval] = $callBack(); } + $val = Date::dateTimeToExcel($val); + $maxval = Date::dateTimeToExcel($maxval); // Set the filter column rule attributes ready for writing - $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]); + $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxval]); // Set the rules for identifying rows for hide/show - $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; - $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal]; - Functions::setReturnDateType($rDateType); + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxval]; return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]]; } @@ -589,7 +717,7 @@ class AutoFilter $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); $dataValues = array_filter($dataValues); - if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { + if ($ruleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { rsort($dataValues); } else { sort($dataValues); @@ -630,7 +758,7 @@ class AutoFilter if (count($ruleValues) != count($ruleDataSet)) { $blanks = true; } - if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) { + if ($ruleType == Rule::AUTOFILTER_RULETYPE_FILTER) { // Filter on absolute values $columnFilterTests[$columnID] = [ 'method' => 'filterTestInSimpleDataSet', @@ -646,40 +774,40 @@ class AutoFilter foreach ($ruleDataSet as $ruleValue) { $date = $time = ''; if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') ) { - $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); + $date .= sprintf('%04d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); } if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') ) { - $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); } if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') ) { - $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); } if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') ) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); } if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') ) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); } if ( - (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') ) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); } $dateTime = $date . $time; $arguments['date'][] = $date; @@ -727,17 +855,17 @@ class AutoFilter // We should only ever have one Dynamic Filter Rule anyway $dynamicRuleType = $rule->getGrouping(); if ( - ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || - ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) ) { // Number (Average) based // Calculate the average $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')'; $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); // Set above/below rule based on greaterThan or LessTan - $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) - ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN - : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; + $operator = ($dynamicRuleType === Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; $ruleValues[] = [ 'operator' => $operator, 'value' => $average, @@ -788,7 +916,7 @@ class AutoFilter $ruleValue = $rule->getValue(); $ruleOperator = $rule->getOperator(); } - if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { + if ($ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { $ruleValue = floor($ruleValue * ($dataRowCount / 100)); } if (!is_array($ruleValue) && $ruleValue < 1) { @@ -800,9 +928,9 @@ class AutoFilter $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue); - $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) - ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL - : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; + $operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; $ruleValues[] = ['operator' => $operator, 'value' => $maxVal]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php index 8ec0ca3b..1309b30f 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php @@ -215,11 +215,11 @@ class Column /** * Set AutoFilter Attributes. * - * @param string[] $attributes + * @param mixed[] $attributes * * @return $this */ - public function setAttributes(array $attributes) + public function setAttributes($attributes) { $this->attributes = $attributes; diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterMonthTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterMonthTest.php new file mode 100644 index 00000000..064c0dac --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterMonthTest.php @@ -0,0 +1,91 @@ +getCell('A1')->setValue('Date'); + $sheet->getCell('A2')->setValue('=TODAY()'); + $sheet->getCell('A3')->setValue('=DATE(YEAR(A2), MONTH(A2), 1)'); + if ($startMonth === 12) { + $sheet->getCell('A4')->setValue('=DATE(YEAR(A2) + 1, 1, 1)'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A2) + 1, 2, 1)'); + } elseif ($startMonth === 11) { + $sheet->getCell('A4')->setValue('=DATE(YEAR(A2), MONTH(A2) + 1, 1)'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A2) + 1, 1, 1)'); + } else { + $sheet->getCell('A4')->setValue('=DATE(YEAR(A2), MONTH(A2) + 1, 1)'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A2), MONTH(A2) + 2, 1)'); + } + if ($startMonth === 1) { + $sheet->getCell('A6')->setValue('=DATE(YEAR(A2) - 1, 12, 1)'); + $sheet->getCell('A7')->setValue('=DATE(YEAR(A2) - 1, 10, 1)'); + } elseif ($startMonth === 2) { + $sheet->getCell('A6')->setValue('=DATE(YEAR(A2), 1, 1)'); + $sheet->getCell('A7')->setValue('=DATE(YEAR(A2) - 1, 12, 1)'); + } else { + $sheet->getCell('A6')->setValue('=DATE(YEAR(A2), MONTH(A2) - 1, 1)'); + $sheet->getCell('A7')->setValue('=DATE(YEAR(A2), MONTH(A2) - 2, 1)'); + } + $sheet->getCell('A8')->setValue('=DATE(YEAR(A2) + 1, MONTH(A2), 1)'); + $sheet->getCell('A9')->setValue('=DATE(YEAR(A2) - 1, MONTH(A2), 1)'); + } + + /** + * @dataProvider providerMonth + */ + public function testMonths(array $expectedVisible, string $rule): void + { + // Loop to avoid rare edge case where first calculation + // and second do not take place in same day. + do { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = (int) $dtStart->format('d'); + $startMonth = (int) $dtStart->format('m'); + self::setCells($sheet, $startMonth); + + $maxRow = 9; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = (int) $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + self::assertEquals($expectedVisible, $actualVisible); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterQuarterTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterQuarterTest.php new file mode 100644 index 00000000..d75f1bc2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterQuarterTest.php @@ -0,0 +1,75 @@ +getCell('A1')->setValue('Date'); + $sheet->getCell('A2')->setValue('=TODAY()'); + $sheet->getCell('A3')->setValue('=DATE(YEAR(A2), MONTH(A2), 1)'); + $sheet->getCell('A4')->setValue('=DATE(YEAR(A2), MONTH(A2) + 3, 1)'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A2), MONTH(A2) + 6, 1)'); + $sheet->getCell('A6')->setValue('=DATE(YEAR(A2), MONTH(A2) - 3, 1)'); + $sheet->getCell('A7')->setValue('=DATE(YEAR(A2), MONTH(A2) - 6, 1)'); + $sheet->getCell('A8')->setValue('=DATE(YEAR(A2) + 1, MONTH(A2), 1)'); + $sheet->getCell('A9')->setValue('=DATE(YEAR(A2) - 1, MONTH(A2), 1)'); + } + + /** + * @dataProvider providerQuarter + */ + public function testQuarters(array $expectedVisible, string $rule): void + { + // Loop to avoid rare edge case where first calculation + // and second do not take place in same day. + do { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = (int) $dtStart->format('d'); + $startMonth = (int) $dtStart->format('m'); + self::setCells($sheet, $startMonth); + + $maxRow = 9; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = (int) $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + self::assertEquals($expectedVisible, $actualVisible); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTest.php similarity index 99% rename from tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php rename to tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTest.php index 7f218b54..a6693d34 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoFilterTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTest.php @@ -1,6 +1,6 @@ expectException(\PhpOffice\PhpSpreadsheet\Exception::class); $invalidColumn = 123.456; + // @phpstan-ignore-next-line $this->testAutoFilterObject->setColumn($invalidColumn); } diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTodayTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTodayTest.php new file mode 100644 index 00000000..3875eb8d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterTodayTest.php @@ -0,0 +1,65 @@ +getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = $dtStart->format('d'); + $sheet->getCell('A1')->setValue('Date'); + $sheet->getCell('A2')->setValue('=NOW()'); + $sheet->getCell('A3')->setValue('=A2+1'); + $sheet->getCell('A4')->setValue('=A2-1'); + $sheet->getCell('A5')->setValue('=TODAY()'); + $sheet->getCell('A6')->setValue('=A5+1'); + $sheet->getCell('A7')->setValue('=A5-1'); + $maxRow = 7; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + self::assertEquals($expectedVisible, $actualVisible); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterWeekTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterWeekTest.php new file mode 100644 index 00000000..3e8fcdb8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterWeekTest.php @@ -0,0 +1,76 @@ +getCell('A1')->setValue('Date'); + $sheet->getCell('A2')->setValue('=TODAY()'); + $sheet->getCell('B2')->setValue('=WEEKDAY(A2) - 1'); // subtract to get to Sunday + $sheet->getCell('A3')->setValue('=DATE(YEAR(A2), MONTH(A2), DAY(A2) - B2)'); + $sheet->getCell('A4')->setValue('=DATE(YEAR(A3), MONTH(A3), DAY(A3) + 8)'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A3), MONTH(A3), DAY(A3) + 19)'); + $sheet->getCell('A6')->setValue('=DATE(YEAR(A3), MONTH(A3), DAY(A3) - 6)'); + $sheet->getCell('A7')->setValue('=DATE(YEAR(A3), MONTH(A3), DAY(A3) - 12)'); + $sheet->getCell('A8')->setValue('=DATE(YEAR(A2) + 1, MONTH(A2), 1)'); + $sheet->getCell('A9')->setValue('=DATE(YEAR(A2) - 1, MONTH(A2), 1)'); + } + + /** + * @dataProvider providerWeek + */ + public function testWeek(array $expectedVisible, string $rule): void + { + // Loop to avoid rare edge case where first calculation + // and second do not take place in same day. + do { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = (int) $dtStart->format('d'); + $startMonth = (int) $dtStart->format('m'); + self::setCells($sheet); + + $maxRow = 9; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = (int) $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + self::assertEquals($expectedVisible, $actualVisible); + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterYearTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterYearTest.php new file mode 100644 index 00000000..105608ae --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoFilter/AutoFilterYearTest.php @@ -0,0 +1,119 @@ +getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = (int) $dtStart->format('d'); + $sheet->getCell('A1')->setValue('Date'); + $year = (int) $dtStart->format('Y') - 1; + $row = 1; + $iteration = 0; + while ($iteration < 3) { + for ($month = 3; $month < 13; $month += 4) { + ++$row; + $sheet->getCell("A$row")->setValue("=DATE($year, $month, 1)"); + } + ++$year; + ++$iteration; + } + ++$row; + $sheet->getCell("A$row")->setValue('=DATE(2041, 1, 1)'); // beyond epoch + $maxRow = $row; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + $rule + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = (int) $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + self::assertEquals($expectedVisible, $actualVisible); + } + + public function testYearToDate(): void + { + // Loop to avoid rare edge case where first calculation + // and second do not take place in same day. + do { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $dtStart = new DateTimeImmutable(); + $startDay = (int) $dtStart->format('d'); + $startMonth = (int) $dtStart->format('m'); + $sheet->getCell('A1')->setValue('Date'); + $year = (int) $dtStart->format('Y'); + $sheet->getCell('A2')->setValue('=TODAY()'); + $sheet->getCell('A3')->setValue('=DATE(YEAR(A2), 12, 31)'); + $sheet->getCell('A4')->setValue('=A3 + 1'); + $sheet->getCell('A5')->setValue('=DATE(YEAR(A2), 1, 1)'); + $sheet->getCell('A6')->setValue('=A5 - 1'); + + $maxRow = 6; + $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); + $autoFilter->setRange("A1:A$maxRow"); + $columnFilter = $autoFilter->getColumn('A'); + $columnFilter->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); + $columnFilter->createRule() + ->setRule( + Rule::AUTOFILTER_COLUMN_RULE_EQUAL, + '', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE + ) + ->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); + $autoFilter->showHideRows(); + $dtEnd = new DateTimeImmutable(); + $endDay = (int) $dtEnd->format('d'); + } while ($startDay !== $endDay); + $actualVisible = []; + for ($row = 2; $row <= $maxRow; ++$row) { + if ($sheet->getRowDimension($row)->getVisible()) { + $actualVisible[] = $row; + } + } + $expected = ($startMonth === 12 && $startDay === 31) ? [2, 3, 5] : [2, 5]; + self::assertEquals($expected, $actualVisible); + } +}