Skip to content

Commit 5dd7e88

Browse files
authored
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. #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.
1 parent 585409a commit 5dd7e88

File tree

5 files changed

+125
-18
lines changed

5 files changed

+125
-18
lines changed

src/PhpSpreadsheet/Shared/Date.php

+13-12
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
use DateTimeInterface;
66
use DateTimeZone;
7-
use Exception;
87
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
98
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
109
use PhpOffice\PhpSpreadsheet\Cell\Cell;
10+
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
1111
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
1212

1313
class Date
@@ -97,17 +97,18 @@ public static function getExcelCalendar()
9797
* @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
9898
*
9999
* @return bool Success or failure
100-
* @return bool Success or failure
101100
*/
102101
public static function setDefaultTimezone($timeZone)
103102
{
104-
if ($timeZone = self::validateTimeZone($timeZone)) {
103+
try {
104+
$timeZone = self::validateTimeZone($timeZone);
105105
self::$defaultTimeZone = $timeZone;
106-
107-
return true;
106+
$retval = true;
107+
} catch (PhpSpreadsheetException $e) {
108+
$retval = false;
108109
}
109110

110-
return false;
111+
return $retval;
111112
}
112113

113114
/**
@@ -130,17 +131,17 @@ public static function getDefaultTimezone()
130131
* @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
131132
*
132133
* @return DateTimeZone The timezone as a timezone object
133-
* @return DateTimeZone The timezone as a timezone object
134134
*/
135-
protected static function validateTimeZone($timeZone)
135+
private static function validateTimeZone($timeZone)
136136
{
137-
if (is_object($timeZone) && $timeZone instanceof DateTimeZone) {
137+
if ($timeZone instanceof DateTimeZone) {
138138
return $timeZone;
139-
} elseif (is_string($timeZone)) {
139+
}
140+
if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
140141
return new DateTimeZone($timeZone);
141142
}
142143

143-
throw new Exception('Invalid timezone');
144+
throw new PhpSpreadsheetException('Invalid timezone');
144145
}
145146

146147
/**
@@ -316,7 +317,7 @@ public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $min
316317
*/
317318
public static function isDateTime(Cell $pCell)
318319
{
319-
return is_numeric($pCell->getValue()) &&
320+
return is_numeric($pCell->getCalculatedValue()) &&
320321
self::isDateTimeFormat(
321322
$pCell->getWorksheet()->getStyle(
322323
$pCell->getCoordinate()

src/PhpSpreadsheet/Shared/TimeZone.php

+1-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class TimeZone
2323
*/
2424
private static function validateTimeZone($timezone)
2525
{
26-
return in_array($timezone, DateTimeZone::listIdentifiers());
26+
return in_array($timezone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC));
2727
}
2828

2929
/**
@@ -73,10 +73,6 @@ public static function getTimeZoneAdjustment($timezone, $timestamp)
7373
$timezone = self::$timezone;
7474
}
7575

76-
if ($timezone == 'UST') {
77-
return 0;
78-
}
79-
8076
$objTimezone = new DateTimeZone($timezone);
8177
$transitions = $objTimezone->getTransitions($timestamp, $timestamp);
8278

tests/PhpSpreadsheetTests/Shared/DateTest.php

+50
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,23 @@
33
namespace PhpOffice\PhpSpreadsheetTests\Shared;
44

55
use PhpOffice\PhpSpreadsheet\Shared\Date;
6+
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
67
use PHPUnit\Framework\TestCase;
78

89
class DateTest extends TestCase
910
{
11+
private $dttimezone;
12+
13+
protected function setUp(): void
14+
{
15+
$this->dttimezone = Date::getDefaultTimeZone();
16+
}
17+
18+
protected function tearDown(): void
19+
{
20+
Date::setDefaultTimeZone($this->dttimezone);
21+
}
22+
1023
public function testSetExcelCalendar(): void
1124
{
1225
$calendarValues = [
@@ -168,4 +181,41 @@ public function providerDateTimeExcelToTimestamp1900Timezone()
168181
{
169182
return require 'tests/data/Shared/Date/ExcelToTimestamp1900Timezone.php';
170183
}
184+
185+
public function testVarious(): void
186+
{
187+
Date::setDefaultTimeZone('UTC');
188+
self::assertFalse(Date::stringToExcel('2019-02-29'));
189+
self::assertTrue((bool) Date::stringToExcel('2019-02-28'));
190+
self::assertTrue((bool) Date::stringToExcel('2019-02-28 11:18'));
191+
self::assertFalse(Date::stringToExcel('2019-02-28 11:71'));
192+
$date = Date::PHPToExcel('2020-01-01');
193+
self::assertEquals(43831.0, $date);
194+
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
195+
$sheet = $spreadsheet->getActiveSheet();
196+
$sheet->setCellValue('B1', 'x');
197+
$val = $sheet->getCell('B1')->getValue();
198+
self::assertFalse(Date::timestampToExcel($val));
199+
$cell = $sheet->getCell('A1');
200+
self::assertNotNull($cell);
201+
$cell->setValue($date);
202+
$sheet->getStyle('A1')
203+
->getNumberFormat()
204+
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
205+
self::assertTrue(null !== $cell && Date::isDateTime($cell));
206+
$cella2 = $sheet->getCell('A2');
207+
self::assertNotNull($cella2);
208+
$cella2->setValue('=A1+2');
209+
$sheet->getStyle('A2')
210+
->getNumberFormat()
211+
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
212+
self::assertTrue(null !== $cella2 && Date::isDateTime($cella2));
213+
$cella3 = $sheet->getCell('A3');
214+
self::assertNotNull($cella3);
215+
$cella3->setValue('=A1+4');
216+
$sheet->getStyle('A3')
217+
->getNumberFormat()
218+
->setFormatCode('0.00E+00');
219+
self::assertFalse(null !== $cella3 && Date::isDateTime($cella3));
220+
}
171221
}

tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php

+57-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,29 @@
22

33
namespace PhpOffice\PhpSpreadsheetTests\Shared;
44

5+
use DateTime;
6+
use PhpOffice\PhpSpreadsheet\Shared\Date;
57
use PhpOffice\PhpSpreadsheet\Shared\TimeZone;
68
use PHPUnit\Framework\TestCase;
79

810
class TimeZoneTest extends TestCase
911
{
12+
private $tztimezone;
13+
14+
private $dttimezone;
15+
16+
protected function setUp(): void
17+
{
18+
$this->tztimezone = TimeZone::getTimeZone();
19+
$this->dttimezone = Date::getDefaultTimeZone();
20+
}
21+
22+
protected function tearDown(): void
23+
{
24+
TimeZone::setTimeZone($this->tztimezone);
25+
Date::setDefaultTimeZone($this->dttimezone);
26+
}
27+
1028
public function testSetTimezone(): void
1129
{
1230
$timezoneValues = [
@@ -20,13 +38,51 @@ public function testSetTimezone(): void
2038
foreach ($timezoneValues as $timezoneValue) {
2139
$result = TimeZone::setTimezone($timezoneValue);
2240
self::assertTrue($result);
41+
$result = Date::setDefaultTimezone($timezoneValue);
42+
self::assertTrue($result);
2343
}
2444
}
2545

46+
public function testSetTimezoneBackwardCompatible(): void
47+
{
48+
$bcTimezone = 'Etc/GMT+10';
49+
$result = TimeZone::setTimezone($bcTimezone);
50+
self::assertTrue($result);
51+
$result = Date::setDefaultTimezone($bcTimezone);
52+
self::assertTrue($result);
53+
}
54+
2655
public function testSetTimezoneWithInvalidValue(): void
2756
{
28-
$unsupportedTimezone = 'Etc/GMT+10';
57+
$unsupportedTimezone = 'XEtc/GMT+10';
2958
$result = TimeZone::setTimezone($unsupportedTimezone);
3059
self::assertFalse($result);
60+
$result = Date::setDefaultTimezone($unsupportedTimezone);
61+
self::assertFalse($result);
62+
}
63+
64+
public function testTimeZoneAdjustmentsInvalidTz(): void
65+
{
66+
$this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class);
67+
$dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-09-22 00:00:00');
68+
$tstmp = $dtobj->getTimestamp();
69+
$unsupportedTimeZone = 'XEtc/GMT+10';
70+
TimeZone::getTimeZoneAdjustment($unsupportedTimeZone, $tstmp);
71+
}
72+
73+
public function testTimeZoneAdjustments(): void
74+
{
75+
$dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-01-01 00:00:00');
76+
$tstmp = $dtobj->getTimestamp();
77+
$supportedTimeZone = 'UTC';
78+
$adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp);
79+
self::assertEquals(0, $adj);
80+
$supportedTimeZone = 'America/Toronto';
81+
$adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp);
82+
self::assertEquals(-18000, $adj);
83+
$supportedTimeZone = 'America/Chicago';
84+
TimeZone::setTimeZone($supportedTimeZone);
85+
$adj = TimeZone::getTimeZoneAdjustment(null, $tstmp);
86+
self::assertEquals(-21600, $adj);
3187
}
3288
}

tests/data/Shared/Date/FormatCodes.php

+4
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,8 @@
146146
false,
147147
'#,##0.00 "dollars"',
148148
],
149+
[
150+
true,
151+
'"date " y-m-d',
152+
],
149153
];

0 commit comments

Comments
 (0)