Skip to content

Commit 8ab8345

Browse files
authored
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'.
1 parent 1a359d2 commit 8ab8345

File tree

5 files changed

+72
-1
lines changed

5 files changed

+72
-1
lines changed

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Cell;
44

5+
use DateTime;
56
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
67
use PhpOffice\PhpSpreadsheet\Collection\Cells;
78
use PhpOffice\PhpSpreadsheet\Exception;
89
use PhpOffice\PhpSpreadsheet\RichText\RichText;
10+
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
911
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1012
use PhpOffice\PhpSpreadsheet\Style\Style;
1113
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
@@ -234,6 +236,22 @@ public function setValueExplicit($value, $dataType)
234236
case DataType::TYPE_BOOL:
235237
$this->value = (bool) $value;
236238

239+
break;
240+
case DataType::TYPE_ISO_DATE:
241+
if (!is_string($value)) {
242+
throw new Exception('Non-string supplied for datatype Date');
243+
}
244+
$date = new DateTime($value);
245+
$newValue = SharedDate::PHPToExcel($date);
246+
if ($newValue === false) {
247+
throw new Exception("Invalid string $value supplied for datatype Date");
248+
}
249+
if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) {
250+
$newValue = fmod($newValue, 1.0);
251+
}
252+
$this->value = $newValue;
253+
$dataType = DataType::TYPE_NUMERIC;
254+
237255
break;
238256
case DataType::TYPE_ERROR:
239257
$this->value = DataType::checkErrorCode($value);

src/PhpSpreadsheet/Cell/DataType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class DataType
1616
const TYPE_NULL = 'null';
1717
const TYPE_INLINE = 'inlineStr';
1818
const TYPE_ERROR = 'e';
19+
const TYPE_ISO_DATE = 'd';
1920

2021
/**
2122
* List of error codes.

src/PhpSpreadsheet/Shared/Date.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ public static function excelToTimestamp($excelTimestamp, $timeZone = null)
228228
* @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged;
229229
* not Y2038-safe on a 32-bit system, and no timezone info
230230
*
231-
* @return bool|float Excel date/time value
231+
* @return false|float Excel date/time value
232232
* or boolean FALSE on failure
233233
*/
234234
public static function PHPToExcel($dateValue)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
4+
5+
use PhpOffice\PhpSpreadsheet\IOFactory;
6+
7+
class ExplicitDateTest extends \PHPUnit\Framework\TestCase
8+
{
9+
/**
10+
* @var string
11+
*/
12+
private static $testbook = 'tests/data/Reader/XLSX/explicitdate.xlsx';
13+
14+
public function testPreliminaries(): void
15+
{
16+
$file = 'zip://';
17+
$file .= self::$testbook;
18+
$file .= '#xl/worksheets/sheet1.xml';
19+
$data = file_get_contents($file);
20+
if ($data === false) {
21+
self::fail('Unable to read file');
22+
} else {
23+
// confirm that file contains type "d" cells
24+
self::assertStringContainsString('<c r="A3" s="1" t="d"><v>2021-12-31T23:44:51.894</v></c>', $data);
25+
self::assertStringContainsString('<c r="B3" s="2" t="d"><v>2021-12-31</v></c>', $data);
26+
self::assertStringContainsString('<c r="C3" s="3" t="d"><v>23:44:51.894</v></c>', $data);
27+
}
28+
}
29+
30+
public static function testExplicitDate(): void
31+
{
32+
$spreadsheet = IOFactory::load(self::$testbook);
33+
$sheet = $spreadsheet->getActiveSheet();
34+
// DateTime
35+
$value = $sheet->getCell('A3')->getValue();
36+
$formatted = $sheet->getCell('A3')->getFormattedValue();
37+
self::assertEqualsWithDelta(44561.98948, $value, 0.00001);
38+
self::assertSame('2021-12-31 23:44:51', $formatted);
39+
// Date only
40+
$value = $sheet->getCell('B3')->getValue();
41+
$formatted = $sheet->getCell('B3')->getFormattedValue();
42+
self::assertEquals(44561, $value);
43+
self::assertSame('2021-12-31', $formatted);
44+
// Time only
45+
$value = $sheet->getCell('C3')->getValue();
46+
$formatted = $sheet->getCell('C3')->getFormattedValue();
47+
self::assertEqualsWithDelta(0.98948, $value, 0.00001);
48+
self::assertSame('23:44:51', $formatted);
49+
50+
$spreadsheet->disconnectWorksheets();
51+
}
52+
}
4.84 KB
Binary file not shown.

0 commit comments

Comments
 (0)