From 8ab834520db2b87790a01c37fdafcbca7d0baad5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 13 Jan 2022 18:40:18 -0800 Subject: [PATCH] Handle Explicit "Date" Type for Cell (#2485) Fix #2373. Excel can handle DateTime/Date/Time as a string if the datatype of the cell is set to "d". The string is, apparently, supposed to follow the ISO8601 spec. Openpyxl can be configured to generate a file with such values, so I've added support and set up unit tests. Excel, naturally, converts such a string input into its numeric representation of the date/time stamp. So will PhpSpreadsheet, so a call to setValueExplicit specifying Date format will actually see the cell wind up with Numeric format - there is no way (and no reason) for the Date type to 'stick'. --- src/PhpSpreadsheet/Cell/Cell.php | 18 ++++++ src/PhpSpreadsheet/Cell/DataType.php | 1 + src/PhpSpreadsheet/Shared/Date.php | 2 +- .../Reader/Xlsx/ExplicitDateTest.php | 52 ++++++++++++++++++ tests/data/Reader/XLSX/explicitdate.xlsx | Bin 0 -> 4955 bytes 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php create mode 100644 tests/data/Reader/XLSX/explicitdate.xlsx diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 5a9def37..a4d74daf 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -2,10 +2,12 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use DateTime; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; @@ -234,6 +236,22 @@ class Cell case DataType::TYPE_BOOL: $this->value = (bool) $value; + 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; + $dataType = DataType::TYPE_NUMERIC; + break; case DataType::TYPE_ERROR: $this->value = DataType::checkErrorCode($value); diff --git a/src/PhpSpreadsheet/Cell/DataType.php b/src/PhpSpreadsheet/Cell/DataType.php index cee3e1e5..0f7efe2a 100644 --- a/src/PhpSpreadsheet/Cell/DataType.php +++ b/src/PhpSpreadsheet/Cell/DataType.php @@ -16,6 +16,7 @@ class DataType const TYPE_NULL = 'null'; const TYPE_INLINE = 'inlineStr'; const TYPE_ERROR = 'e'; + const TYPE_ISO_DATE = 'd'; /** * List of error codes. diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index 5b0a2907..b4cf1913 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -228,7 +228,7 @@ class Date * @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged; * not Y2038-safe on a 32-bit system, and no timezone info * - * @return bool|float Excel date/time value + * @return false|float Excel date/time value * or boolean FALSE on failure */ public static function PHPToExcel($dateValue) diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php new file mode 100644 index 00000000..23af5222 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ExplicitDateTest.php @@ -0,0 +1,52 @@ +2021-12-31T23:44:51.894', $data); + self::assertStringContainsString('2021-12-31', $data); + self::assertStringContainsString('23:44:51.894', $data); + } + } + + public static function testExplicitDate(): void + { + $spreadsheet = IOFactory::load(self::$testbook); + $sheet = $spreadsheet->getActiveSheet(); + // DateTime + $value = $sheet->getCell('A3')->getValue(); + $formatted = $sheet->getCell('A3')->getFormattedValue(); + self::assertEqualsWithDelta(44561.98948, $value, 0.00001); + self::assertSame('2021-12-31 23:44:51', $formatted); + // Date only + $value = $sheet->getCell('B3')->getValue(); + $formatted = $sheet->getCell('B3')->getFormattedValue(); + self::assertEquals(44561, $value); + self::assertSame('2021-12-31', $formatted); + // Time only + $value = $sheet->getCell('C3')->getValue(); + $formatted = $sheet->getCell('C3')->getFormattedValue(); + self::assertEqualsWithDelta(0.98948, $value, 0.00001); + self::assertSame('23:44:51', $formatted); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/explicitdate.xlsx b/tests/data/Reader/XLSX/explicitdate.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c4017b87806695c9924de847e77019f5455f38f1 GIT binary patch literal 4955 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