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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