Skip to content

Commit d88e8f8

Browse files
author
Mark Baker
authored
Merge pull request #2625 from PHPOffice/Iso-Date-Datatype-Additional-Unit-Tests
Minor refactoring of ISO Date checking
2 parents 9cf526a + 138638a commit d88e8f8

File tree

8 files changed

+139
-70
lines changed

8 files changed

+139
-70
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
1313

1414
### Changed
1515

16-
- Nothing
16+
- Gnumeric Reader now loads number formatting for cells.
17+
- Gnumeric Reader now correctly identifies selected worksheet.
1718

1819
### Deprecated
1920

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Cell;
44

5-
use DateTime;
65
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
76
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
87
use PhpOffice\PhpSpreadsheet\Collection\Cells;
@@ -240,18 +239,7 @@ public function setValueExplicit($value, $dataType)
240239

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

257245
break;

src/PhpSpreadsheet/Reader/Gnumeric.php

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,8 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
284284
$row = (int) $cellAttributes->Row + 1;
285285
$column = (int) $cellAttributes->Col;
286286

287-
if ($row > $maxRow) {
288-
$maxRow = $row;
289-
}
290-
if ($column > $maxCol) {
291-
$maxCol = $column;
292-
}
287+
$maxRow = max($maxRow, $row);
288+
$maxCol = max($maxCol, $column);
293289

294290
$column = Coordinate::stringFromColumnIndex($column + 1);
295291

@@ -300,38 +296,7 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
300296
}
301297
}
302298

303-
$ValueType = $cellAttributes->ValueType;
304-
$ExprID = (string) $cellAttributes->ExprID;
305-
$type = DataType::TYPE_FORMULA;
306-
if ($ExprID > '') {
307-
if (((string) $cell) > '') {
308-
$this->expressions[$ExprID] = [
309-
'column' => $cellAttributes->Col,
310-
'row' => $cellAttributes->Row,
311-
'formula' => (string) $cell,
312-
];
313-
} else {
314-
$expression = $this->expressions[$ExprID];
315-
316-
$cell = $this->referenceHelper->updateFormulaReferences(
317-
$expression['formula'],
318-
'A1',
319-
$cellAttributes->Col - $expression['column'],
320-
$cellAttributes->Row - $expression['row'],
321-
$worksheetName
322-
);
323-
}
324-
$type = DataType::TYPE_FORMULA;
325-
} else {
326-
$vtype = (string) $ValueType;
327-
if (array_key_exists($vtype, self::$mappings['dataType'])) {
328-
$type = self::$mappings['dataType'][$vtype];
329-
}
330-
if ($vtype === '20') { // Boolean
331-
$cell = $cell == 'TRUE';
332-
}
333-
}
334-
$this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
299+
$this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row);
335300
}
336301

337302
if ($sheet->Styles !== null) {
@@ -349,10 +314,21 @@ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Sp
349314

350315
$this->processDefinedNames($gnmXML);
351316

317+
$this->setSelectedSheet($gnmXML);
318+
352319
// Return
353320
return $this->spreadsheet;
354321
}
355322

323+
private function setSelectedSheet(SimpleXMLElement $gnmXML): void
324+
{
325+
if (isset($gnmXML->UIData)) {
326+
$attributes = self::testSimpleXml($gnmXML->UIData->attributes());
327+
$selectedSheet = (int) $attributes['SelectedTab'];
328+
$this->spreadsheet->setActiveSheetIndex($selectedSheet);
329+
}
330+
}
331+
356332
private function processMergedCells(?SimpleXMLElement $sheet): void
357333
{
358334
// Handle Merged Cells in this worksheet
@@ -530,4 +506,51 @@ private function parseRichText(string $is): RichText
530506

531507
return $value;
532508
}
509+
510+
private function loadCell(
511+
SimpleXMLElement $cell,
512+
string $worksheetName,
513+
SimpleXMLElement $cellAttributes,
514+
string $column,
515+
int $row
516+
): void {
517+
$ValueType = $cellAttributes->ValueType;
518+
$ExprID = (string) $cellAttributes->ExprID;
519+
$type = DataType::TYPE_FORMULA;
520+
if ($ExprID > '') {
521+
if (((string) $cell) > '') {
522+
$this->expressions[$ExprID] = [
523+
'column' => $cellAttributes->Col,
524+
'row' => $cellAttributes->Row,
525+
'formula' => (string) $cell,
526+
];
527+
} else {
528+
$expression = $this->expressions[$ExprID];
529+
530+
$cell = $this->referenceHelper->updateFormulaReferences(
531+
$expression['formula'],
532+
'A1',
533+
$cellAttributes->Col - $expression['column'],
534+
$cellAttributes->Row - $expression['row'],
535+
$worksheetName
536+
);
537+
}
538+
$type = DataType::TYPE_FORMULA;
539+
} else {
540+
$vtype = (string) $ValueType;
541+
if (array_key_exists($vtype, self::$mappings['dataType'])) {
542+
$type = self::$mappings['dataType'][$vtype];
543+
}
544+
if ($vtype === '20') { // Boolean
545+
$cell = $cell == 'TRUE';
546+
}
547+
}
548+
549+
$this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
550+
if (isset($cellAttributes->ValueFormat)) {
551+
$this->spreadsheet->getActiveSheet()->getCell($column . $row)
552+
->getStyle()->getNumberFormat()
553+
->setFormatCode((string) $cellAttributes->ValueFormat);
554+
}
555+
}
533556
}

src/PhpSpreadsheet/Reader/Ods.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Reader;
44

5-
use DateTime;
65
use DOMAttr;
76
use DOMDocument;
87
use DOMElement;
@@ -478,21 +477,7 @@ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
478477
case 'date':
479478
$type = DataType::TYPE_NUMERIC;
480479
$value = $cellData->getAttributeNS($officeNs, 'date-value');
481-
482-
$dateObj = new DateTime($value);
483-
[$year, $month, $day, $hour, $minute, $second] = explode(
484-
' ',
485-
$dateObj->format('Y m d H i s')
486-
);
487-
488-
$dataValue = Date::formattedPHPToExcel(
489-
(int) $year,
490-
(int) $month,
491-
(int) $day,
492-
(int) $hour,
493-
(int) $minute,
494-
(int) $second
495-
);
480+
$dataValue = Date::convertIsoDate($value);
496481

497482
if ($dataValue != floor($dataValue)) {
498483
$formatting = NumberFormat::FORMAT_DATE_XLSX15

src/PhpSpreadsheet/Shared/Date.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
1010
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
1111
use PhpOffice\PhpSpreadsheet\Cell\Cell;
12+
use PhpOffice\PhpSpreadsheet\Exception;
1213
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
14+
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
1315
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1416

1517
class Date
@@ -158,6 +160,36 @@ private static function validateTimeZone($timeZone)
158160
throw new PhpSpreadsheetException('Invalid timezone');
159161
}
160162

163+
/**
164+
* @param mixed $value
165+
*
166+
* @return float|int
167+
*/
168+
public static function convertIsoDate($value)
169+
{
170+
if (!is_string($value)) {
171+
throw new Exception('Non-string value supplied for Iso Date conversion');
172+
}
173+
174+
$date = new DateTime($value);
175+
$dateErrors = DateTime::getLastErrors();
176+
177+
if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
178+
throw new Exception("Invalid string $value supplied for datatype Date");
179+
}
180+
181+
$newValue = SharedDate::PHPToExcel($date);
182+
if ($newValue === false) {
183+
throw new Exception("Invalid string $value supplied for datatype Date");
184+
}
185+
186+
if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) {
187+
$newValue = fmod($newValue, 1.0);
188+
}
189+
190+
return $newValue;
191+
}
192+
161193
/**
162194
* Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
163195
*

tests/PhpSpreadsheetTests/Cell/CellTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheetTests\Cell;
44

5+
use PhpOffice\PhpSpreadsheet\Cell\DataType;
56
use PhpOffice\PhpSpreadsheet\Exception;
67
use PhpOffice\PhpSpreadsheet\Spreadsheet;
78
use PhpOffice\PhpSpreadsheet\Style\Color;
@@ -33,6 +34,19 @@ public function providerSetValueExplicit(): array
3334
return require 'tests/data/Cell/SetValueExplicit.php';
3435
}
3536

37+
public function testInvalidIsoDateSetValueExplicit(): void
38+
{
39+
$spreadsheet = new Spreadsheet();
40+
$cell = $spreadsheet->getActiveSheet()->getCell('A1');
41+
42+
$dateValue = '2022-02-29'; // Invalid leap year
43+
$this->expectException(Exception::class);
44+
$this->expectExceptionMessage("Invalid string {$dateValue} supplied for datatype Date");
45+
$cell->setValueExplicit($dateValue, DataType::TYPE_ISO_DATE);
46+
47+
$spreadsheet->disconnectWorksheets();
48+
}
49+
3650
/**
3751
* @dataProvider providerSetValueExplicitException
3852
*

tests/PhpSpreadsheetTests/Shared/DateTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Shared;
44

55
use DateTimeZone;
6+
use PhpOffice\PhpSpreadsheet\Exception;
67
use PhpOffice\PhpSpreadsheet\Shared\Date;
78
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
89
use PHPUnit\Framework\TestCase;
@@ -209,6 +210,15 @@ public function providerDateTimeExcelToTimestamp1900Timezone(): array
209210
return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php';
210211
}
211212

213+
public function testConvertIsoDateError(): void
214+
{
215+
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
216+
217+
$this->expectException(Exception::class);
218+
$this->expectExceptionMessage('Non-string value supplied for Iso Date conversion');
219+
Date::convertIsoDate(false);
220+
}
221+
212222
public function testVarious(): void
213223
{
214224
Date::setDefaultTimeZone('UTC');

tests/data/Cell/SetValueExplicit.php

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
34
use PhpOffice\PhpSpreadsheet\Cell\DataType;
45

56
return [
@@ -39,12 +40,27 @@
3940
DataType::TYPE_NUMERIC,
4041
],
4142
[
42-
'#DIV/0!',
43+
44613.43090277778,
44+
'2022-02-21 10:20:30',
45+
DataType::TYPE_ISO_DATE,
46+
],
47+
[
48+
44613.0,
49+
'2022-02-21',
50+
DataType::TYPE_ISO_DATE,
51+
],
52+
[
53+
-30879.0,
54+
'1815-06-15', // Dates outside the Excel Range should fail really
55+
DataType::TYPE_ISO_DATE,
56+
],
57+
[
58+
ExcelError::DIV0(),
4359
'#DIV/0!',
4460
DataType::TYPE_ERROR,
4561
],
4662
[
47-
'#NULL!',
63+
ExcelError::null(),
4864
'NOT A VALID ERROR TYPE VALUE',
4965
DataType::TYPE_ERROR,
5066
],

0 commit comments

Comments
 (0)