mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-09-10 18:16:34 +00:00
Unexpected Exception in Php DateTime
Fix #4696. Fix #917 (which had gone stale but is now reopened). Fix #916. People were receiving unexpected exceptions calling `Date::isDateTime($cell)`. 916 and 917 had been mostly resolved long ago by adding a numeric check on the contents of $cell. However, it turns out that the use of values with very large absolute values can cause Php DateTime to throw an exception, which is the problem with 4696, when it calls `Date::toExcelDateTimeObject` for a cell with a large integer value. `Date::toExcelDateTimeObject` is coded to return a Php DateTime object. I don't see any reasonable non-breaking fix for that. People can always add try/catch if they are worried about that. However, `Date::isDateTime` can add an additional check and return `false` if this situation occurs. All 3 of the issues resolved by this PR mention isDateTime in their discussion; they are all fixed without additional coding changes by this. An additional change is needed for `Cell::getFormattedValue` to avoid this problem. This is fixed in `Style::NumberFormatter::DateFormatter::format`.
This commit is contained in:
@@ -11,6 +11,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Exception;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use Throwable;
|
||||
|
||||
class Date
|
||||
{
|
||||
@@ -376,15 +377,18 @@ class Date
|
||||
$cell->getCalculatedValue()
|
||||
);
|
||||
}
|
||||
$result = is_numeric($value)
|
||||
&& self::isDateTimeFormat(
|
||||
if (is_numeric($value)) {
|
||||
$result = self::isDateTimeFormat(
|
||||
$worksheet->getStyle(
|
||||
$cell->getCoordinate()
|
||||
)->getNumberFormat(),
|
||||
$dateWithoutTimeOkay
|
||||
);
|
||||
} catch (Exception) {
|
||||
// Result is already false, so no need to actually do anything here
|
||||
/** @var float|int $value */
|
||||
self::excelToDateTimeObject($value);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
$result = false;
|
||||
}
|
||||
$worksheet->setSelectedCells($selected);
|
||||
$spreadsheet->setActiveSheetIndex($index);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
|
||||
use Throwable;
|
||||
|
||||
class DateFormatter
|
||||
{
|
||||
@@ -161,7 +163,11 @@ class DateFormatter
|
||||
$callback = [self::class, 'escapeQuotesCallback'];
|
||||
$format = (string) preg_replace_callback('/"(.*)"/U', $callback, $format);
|
||||
|
||||
$dateObj = Date::excelToDateTimeObject($value);
|
||||
try {
|
||||
$dateObj = Date::excelToDateTimeObject($value);
|
||||
} catch (Throwable) {
|
||||
return StringHelper::convertToString($value);
|
||||
}
|
||||
// If the colon preceding minute had been quoted, as happens in
|
||||
// Excel 2003 XML formats, m will not have been changed to i above.
|
||||
// Change it now.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace PhpOffice\PhpSpreadsheetTests\Shared;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class Issue4696Test extends TestCase
|
||||
{
|
||||
private ?Spreadsheet $spreadsheet = null;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if ($this->spreadsheet !== null) {
|
||||
$this->spreadsheet->disconnectWorksheets();
|
||||
$this->spreadsheet = null;
|
||||
}
|
||||
}
|
||||
|
||||
#[DataProvider('providerIsDateTime')]
|
||||
public function testTimeOnly(bool $expectedResult, string $expectedFormatted, int|float|string $value): void
|
||||
{
|
||||
$this->spreadsheet = new Spreadsheet();
|
||||
$sheet = $this->spreadsheet->getActiveSheet();
|
||||
if (is_string($value) && $value[0] !== '=') {
|
||||
$sheet->getCell('A1')->setValueExplicit($value, DataType::TYPE_STRING);
|
||||
} else {
|
||||
$sheet->getCell('A1')->setValue($value);
|
||||
}
|
||||
$sheet->getStyle('A1')->getNumberFormat()
|
||||
->setFormatCode('yyyy-mm-dd');
|
||||
self::assertSame(
|
||||
$expectedResult,
|
||||
Date::isDateTime($sheet->getCell('A1'))
|
||||
);
|
||||
self::assertSame(
|
||||
$expectedFormatted,
|
||||
$sheet->getCell('A1')->getFormattedValue()
|
||||
);
|
||||
}
|
||||
|
||||
public static function providerIsDateTime(): array
|
||||
{
|
||||
return [
|
||||
'valid integer' => [true, '1903-12-31', 1461],
|
||||
'valid integer stored as string' => [true, '1904-01-01', '1462'],
|
||||
'valid integer stored as concatenated string' => [true, '1904-01-01', '="14"&"62"'],
|
||||
'valid float' => [true, '1903-12-31', 1461.5],
|
||||
'valid float stored as string' => [true, '1903-12-31', '1461.5'],
|
||||
'out-of-range integer' => [false, '7000989091802000122', 7000989091802000122],
|
||||
'out-of-range float' => [false, '7.000989091802E+18', 7000989091802000122.1],
|
||||
'out-of-range float stored as string' => [false, '7000989091802000122.1', '7000989091802000122.1'],
|
||||
'non-numeric' => [false, 'xyz', 'xyz'],
|
||||
'issue 917' => [false, '5e8630b8-603c-43fe-b038-6154a3f893ab', '5e8630b8-603c-43fe-b038-6154a3f893ab'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user