From ef4029df6327148ad3ddb593759d6c7d8fef06dc Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 26 Feb 2022 13:43:35 +0100 Subject: [PATCH] Refactor ISO data type validation from cell to shared date; add extra checks for invalid dates; and appropriate unit tests --- src/PhpSpreadsheet/Cell/Cell.php | 14 +------- src/PhpSpreadsheet/Shared/Date.php | 32 +++++++++++++++++++ tests/PhpSpreadsheetTests/Cell/CellTest.php | 14 ++++++++ tests/PhpSpreadsheetTests/Shared/DateTest.php | 10 ++++++ tests/data/Cell/SetValueExplicit.php | 20 ++++++++++-- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 87d13104..76d5e86d 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Cell; -use DateTime; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Collection\Cells; @@ -240,18 +239,7 @@ class Cell break; case DataType::TYPE_ISO_DATE: - if (!is_string($value)) { - throw new Exception('Non-string supplied for datatype Date'); - } - $date = new DateTime($value); - $newValue = SharedDate::PHPToExcel($date); - if ($newValue === false) { - throw new Exception("Invalid string $value supplied for datatype Date"); - } - if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) { - $newValue = fmod($newValue, 1.0); - } - $this->value = $newValue; + $this->value = SharedDate::convertIsoDate($value); $dataType = DataType::TYPE_NUMERIC; break; diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index b5c7c8fe..87278073 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -9,7 +9,9 @@ use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Date @@ -158,6 +160,36 @@ class Date throw new PhpSpreadsheetException('Invalid timezone'); } + /** + * @param mixed $value + * + * @return float|int + */ + public static function convertIsoDate($value) + { + if (!is_string($value)) { + throw new Exception('Non-string value supplied for Iso Date conversion'); + } + + $date = new DateTime($value); + $dateErrors = DateTime::getLastErrors(); + + if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) { + throw new Exception("Invalid string $value supplied for datatype Date"); + } + + $newValue = SharedDate::PHPToExcel($date); + if ($newValue === false) { + throw new Exception("Invalid string $value supplied for datatype Date"); + } + + if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) { + $newValue = fmod($newValue, 1.0); + } + + return $newValue; + } + /** * Convert a MS serialized datetime value from Excel to a PHP Date/Time object. * diff --git a/tests/PhpSpreadsheetTests/Cell/CellTest.php b/tests/PhpSpreadsheetTests/Cell/CellTest.php index 5fcf4eb4..2ffe0e29 100644 --- a/tests/PhpSpreadsheetTests/Cell/CellTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CellTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Cell; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Color; @@ -33,6 +34,19 @@ class CellTest extends TestCase return require 'tests/data/Cell/SetValueExplicit.php'; } + public function testInvalidIsoDateSetValueExplicit(): void + { + $spreadsheet = new Spreadsheet(); + $cell = $spreadsheet->getActiveSheet()->getCell('A1'); + + $dateValue = '2022-02-29'; // Invalid leap year + $this->expectException(Exception::class); + $this->expectExceptionMessage("Invalid string {$dateValue} supplied for datatype Date"); + $cell->setValueExplicit($dateValue, DataType::TYPE_ISO_DATE); + + $spreadsheet->disconnectWorksheets(); + } + /** * @dataProvider providerSetValueExplicitException * diff --git a/tests/PhpSpreadsheetTests/Shared/DateTest.php b/tests/PhpSpreadsheetTests/Shared/DateTest.php index e4bf6838..9fb4d919 100644 --- a/tests/PhpSpreadsheetTests/Shared/DateTest.php +++ b/tests/PhpSpreadsheetTests/Shared/DateTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Shared; use DateTimeZone; +use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PHPUnit\Framework\TestCase; @@ -209,6 +210,15 @@ class DateTest extends TestCase return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php'; } + public function testConvertIsoDateError(): void + { + Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('Non-string value supplied for Iso Date conversion'); + Date::convertIsoDate(false); + } + public function testVarious(): void { Date::setDefaultTimeZone('UTC'); diff --git a/tests/data/Cell/SetValueExplicit.php b/tests/data/Cell/SetValueExplicit.php index 50b81019..13a1e33a 100644 --- a/tests/data/Cell/SetValueExplicit.php +++ b/tests/data/Cell/SetValueExplicit.php @@ -1,5 +1,6 @@