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:
parent
585409a949
commit
5dd7e883c6
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,4 +146,8 @@ return [
|
||||||
false,
|
false,
|
||||||
'#,##0.00 "dollars"',
|
'#,##0.00 "dollars"',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
true,
|
||||||
|
'"date " y-m-d',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in New Issue