Fix Issue 1441 (isDateTime and Formulas) (#1480)

* Fix Issue 1441 (isDateTime and Formulas)

When you have a date-field which is a formula, isDateTime returns false.
https://github.com/PHPOffice/PhpSpreadsheet/issues/1441

Report makes sense; fixed as suggested. Also fixed a few minor
related issues, and added tests so that Shared/Date and Shared/TimeZone
are now completely covered.

Date/setDefaultTimeZone and TimeZone/setTimeZone were not consistent
about what to do in event of failure - return false or throw.
They will now both return false, which is what Date's function
said it would do in its doc block anyhow. Date/validateTimeZone will
continue to throw; it was protected, but was never called outside
Date, so I changed it to private.

TimeZone/getTimeZoneAdjustment checked for 'UST' when it probably
meant 'UTC', and, as it turns out, the check is not even needed.

The most serious problem was that TimeZone/validateTimeZone does not
check the backwards-compatible time zones. The timezone project
aggressively, and very controversially, "demotes" timezones;
such timezones eventually wind up in the PHP backwards-compatible list.
We want to make sure to check that list so that our applications do not
break when this happens.
This commit is contained in:
oleibman 2020-05-24 11:02:39 -07:00 committed by GitHub
parent 585409a949
commit 5dd7e883c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 125 additions and 18 deletions

View File

@ -4,10 +4,10 @@ namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTimeInterface; use DateTimeInterface;
use DateTimeZone; use DateTimeZone;
use Exception;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime; use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Date class Date
@ -97,17 +97,18 @@ class Date
* @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
* *
* @return bool Success or failure * @return bool Success or failure
* @return bool Success or failure
*/ */
public static function setDefaultTimezone($timeZone) public static function setDefaultTimezone($timeZone)
{ {
if ($timeZone = self::validateTimeZone($timeZone)) { try {
$timeZone = self::validateTimeZone($timeZone);
self::$defaultTimeZone = $timeZone; self::$defaultTimeZone = $timeZone;
$retval = true;
return true; } catch (PhpSpreadsheetException $e) {
$retval = false;
} }
return false; return $retval;
} }
/** /**
@ -130,17 +131,17 @@ class Date
* @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
* *
* @return DateTimeZone The timezone as a timezone object * @return DateTimeZone The timezone as a timezone object
* @return DateTimeZone The timezone as a timezone object
*/ */
protected static function validateTimeZone($timeZone) private static function validateTimeZone($timeZone)
{ {
if (is_object($timeZone) && $timeZone instanceof DateTimeZone) { if ($timeZone instanceof DateTimeZone) {
return $timeZone; return $timeZone;
} elseif (is_string($timeZone)) { }
if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
return new DateTimeZone($timeZone); return new DateTimeZone($timeZone);
} }
throw new Exception('Invalid timezone'); throw new PhpSpreadsheetException('Invalid timezone');
} }
/** /**
@ -316,7 +317,7 @@ class Date
*/ */
public static function isDateTime(Cell $pCell) public static function isDateTime(Cell $pCell)
{ {
return is_numeric($pCell->getValue()) && return is_numeric($pCell->getCalculatedValue()) &&
self::isDateTimeFormat( self::isDateTimeFormat(
$pCell->getWorksheet()->getStyle( $pCell->getWorksheet()->getStyle(
$pCell->getCoordinate() $pCell->getCoordinate()

View File

@ -23,7 +23,7 @@ class TimeZone
*/ */
private static function validateTimeZone($timezone) private static function validateTimeZone($timezone)
{ {
return in_array($timezone, DateTimeZone::listIdentifiers()); return in_array($timezone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC));
} }
/** /**
@ -73,10 +73,6 @@ class TimeZone
$timezone = self::$timezone; $timezone = self::$timezone;
} }
if ($timezone == 'UST') {
return 0;
}
$objTimezone = new DateTimeZone($timezone); $objTimezone = new DateTimeZone($timezone);
$transitions = $objTimezone->getTransitions($timestamp, $timestamp); $transitions = $objTimezone->getTransitions($timestamp, $timestamp);

View File

@ -3,10 +3,23 @@
namespace PhpOffice\PhpSpreadsheetTests\Shared; namespace PhpOffice\PhpSpreadsheetTests\Shared;
use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class DateTest extends TestCase class DateTest extends TestCase
{ {
private $dttimezone;
protected function setUp(): void
{
$this->dttimezone = Date::getDefaultTimeZone();
}
protected function tearDown(): void
{
Date::setDefaultTimeZone($this->dttimezone);
}
public function testSetExcelCalendar(): void public function testSetExcelCalendar(): void
{ {
$calendarValues = [ $calendarValues = [
@ -168,4 +181,41 @@ class DateTest extends TestCase
{ {
return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php'; return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php';
} }
public function testVarious(): void
{
Date::setDefaultTimeZone('UTC');
self::assertFalse(Date::stringToExcel('2019-02-29'));
self::assertTrue((bool) Date::stringToExcel('2019-02-28'));
self::assertTrue((bool) Date::stringToExcel('2019-02-28 11:18'));
self::assertFalse(Date::stringToExcel('2019-02-28 11:71'));
$date = Date::PHPToExcel('2020-01-01');
self::assertEquals(43831.0, $date);
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('B1', 'x');
$val = $sheet->getCell('B1')->getValue();
self::assertFalse(Date::timestampToExcel($val));
$cell = $sheet->getCell('A1');
self::assertNotNull($cell);
$cell->setValue($date);
$sheet->getStyle('A1')
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
self::assertTrue(null !== $cell && Date::isDateTime($cell));
$cella2 = $sheet->getCell('A2');
self::assertNotNull($cella2);
$cella2->setValue('=A1+2');
$sheet->getStyle('A2')
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
self::assertTrue(null !== $cella2 && Date::isDateTime($cella2));
$cella3 = $sheet->getCell('A3');
self::assertNotNull($cella3);
$cella3->setValue('=A1+4');
$sheet->getStyle('A3')
->getNumberFormat()
->setFormatCode('0.00E+00');
self::assertFalse(null !== $cella3 && Date::isDateTime($cella3));
}
} }

View File

@ -2,11 +2,29 @@
namespace PhpOffice\PhpSpreadsheetTests\Shared; namespace PhpOffice\PhpSpreadsheetTests\Shared;
use DateTime;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\TimeZone; use PhpOffice\PhpSpreadsheet\Shared\TimeZone;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class TimeZoneTest extends TestCase class TimeZoneTest extends TestCase
{ {
private $tztimezone;
private $dttimezone;
protected function setUp(): void
{
$this->tztimezone = TimeZone::getTimeZone();
$this->dttimezone = Date::getDefaultTimeZone();
}
protected function tearDown(): void
{
TimeZone::setTimeZone($this->tztimezone);
Date::setDefaultTimeZone($this->dttimezone);
}
public function testSetTimezone(): void public function testSetTimezone(): void
{ {
$timezoneValues = [ $timezoneValues = [
@ -20,13 +38,51 @@ class TimeZoneTest extends TestCase
foreach ($timezoneValues as $timezoneValue) { foreach ($timezoneValues as $timezoneValue) {
$result = TimeZone::setTimezone($timezoneValue); $result = TimeZone::setTimezone($timezoneValue);
self::assertTrue($result); self::assertTrue($result);
$result = Date::setDefaultTimezone($timezoneValue);
self::assertTrue($result);
} }
} }
public function testSetTimezoneBackwardCompatible(): void
{
$bcTimezone = 'Etc/GMT+10';
$result = TimeZone::setTimezone($bcTimezone);
self::assertTrue($result);
$result = Date::setDefaultTimezone($bcTimezone);
self::assertTrue($result);
}
public function testSetTimezoneWithInvalidValue(): void public function testSetTimezoneWithInvalidValue(): void
{ {
$unsupportedTimezone = 'Etc/GMT+10'; $unsupportedTimezone = 'XEtc/GMT+10';
$result = TimeZone::setTimezone($unsupportedTimezone); $result = TimeZone::setTimezone($unsupportedTimezone);
self::assertFalse($result); self::assertFalse($result);
$result = Date::setDefaultTimezone($unsupportedTimezone);
self::assertFalse($result);
}
public function testTimeZoneAdjustmentsInvalidTz(): void
{
$this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class);
$dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-09-22 00:00:00');
$tstmp = $dtobj->getTimestamp();
$unsupportedTimeZone = 'XEtc/GMT+10';
TimeZone::getTimeZoneAdjustment($unsupportedTimeZone, $tstmp);
}
public function testTimeZoneAdjustments(): void
{
$dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-01-01 00:00:00');
$tstmp = $dtobj->getTimestamp();
$supportedTimeZone = 'UTC';
$adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp);
self::assertEquals(0, $adj);
$supportedTimeZone = 'America/Toronto';
$adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp);
self::assertEquals(-18000, $adj);
$supportedTimeZone = 'America/Chicago';
TimeZone::setTimeZone($supportedTimeZone);
$adj = TimeZone::getTimeZoneAdjustment(null, $tstmp);
self::assertEquals(-21600, $adj);
} }
} }

View File

@ -146,4 +146,8 @@ return [
false, false,
'#,##0.00 "dollars"', '#,##0.00 "dollars"',
], ],
[
true,
'"date " y-m-d',
],
]; ];