Skip to content

Commit 9b3f205

Browse files
authored
Merge pull request #3939 from oleibman/sysformats2
Additional Support for Date/Time Styles
2 parents d015754 + accb321 commit 9b3f205

File tree

8 files changed

+224
-10
lines changed

8 files changed

+224
-10
lines changed

src/PhpSpreadsheet/Calculation/TextData/Format.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public static function TEXTFORMAT(mixed $value, mixed $format): array|string
124124

125125
$value = Helpers::extractString($value);
126126
$format = Helpers::extractString($format);
127+
$format = (string) NumberFormat::convertSystemFormats($format);
127128

128129
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
129130
$value1 = DateTimeExcel\DateValue::fromString($value);

src/PhpSpreadsheet/Cell/Cell.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public function getFormattedValue(): string
182182
{
183183
return (string) NumberFormat::toFormattedString(
184184
$this->getCalculatedValue(),
185-
(string) $this->getStyle()->getNumberFormat()->getFormatCode()
185+
(string) $this->getStyle()->getNumberFormat()->getFormatCode(true)
186186
);
187187
}
188188

src/PhpSpreadsheet/Shared/Date.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ public static function isDateTimeFormatCode(string $excelFormatCode, bool $dateW
411411
}
412412

413413
// Switch on formatcode
414+
$excelFormatCode = (string) NumberFormat::convertSystemFormats($excelFormatCode);
414415
if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) {
415416
return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY);
416417
}

src/PhpSpreadsheet/Style/NumberFormat.php

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ class NumberFormat extends Supervisor
2626
const FORMAT_DATE_DMMINUS = 'd-m';
2727
const FORMAT_DATE_MYMINUS = 'm-yy';
2828
const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
29+
const FORMAT_DATE_XLSX14_ACTUAL = 'm/d/yyyy';
2930
const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
3031
const FORMAT_DATE_XLSX16 = 'd-mmm';
3132
const FORMAT_DATE_XLSX17 = 'mmm-yy';
3233
const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
34+
const FORMAT_DATE_XLSX22_ACTUAL = 'm/d/yyyy h:mm';
3335
const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
3436
const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
3537
const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
@@ -40,6 +42,7 @@ class NumberFormat extends Supervisor
4042
const FORMAT_DATE_TIME7 = 'i:s.S';
4143
const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
4244
const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@';
45+
const FORMAT_DATE_LONG_DATE = 'dddd, mmmm d, yyyy';
4346

4447
const DATE_TIME_OR_DATETIME_ARRAY = [
4548
self::FORMAT_DATE_YYYYMMDD,
@@ -49,10 +52,12 @@ class NumberFormat extends Supervisor
4952
self::FORMAT_DATE_DMMINUS,
5053
self::FORMAT_DATE_MYMINUS,
5154
self::FORMAT_DATE_XLSX14,
55+
self::FORMAT_DATE_XLSX14_ACTUAL,
5256
self::FORMAT_DATE_XLSX15,
5357
self::FORMAT_DATE_XLSX16,
5458
self::FORMAT_DATE_XLSX17,
5559
self::FORMAT_DATE_XLSX22,
60+
self::FORMAT_DATE_XLSX22_ACTUAL,
5661
self::FORMAT_DATE_DATETIME,
5762
self::FORMAT_DATE_TIME1,
5863
self::FORMAT_DATE_TIME2,
@@ -63,6 +68,7 @@ class NumberFormat extends Supervisor
6368
self::FORMAT_DATE_TIME7,
6469
self::FORMAT_DATE_TIME8,
6570
self::FORMAT_DATE_YYYYMMDDSLASH,
71+
self::FORMAT_DATE_LONG_DATE,
6672
];
6773
const TIME_OR_DATETIME_ARRAY = [
6874
self::FORMAT_DATE_XLSX22,
@@ -84,6 +90,21 @@ class NumberFormat extends Supervisor
8490
const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
8591
const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
8692

93+
const SHORT_DATE_INDEX = 14;
94+
const DATE_TIME_INDEX = 22;
95+
const FORMAT_SYSDATE_X = '[$-x-sysdate]';
96+
const FORMAT_SYSDATE_F800 = '[$-F800]';
97+
const FORMAT_SYSTIME_X = '[$-x-systime]';
98+
const FORMAT_SYSTIME_F400 = '[$-F400]';
99+
100+
protected static string $shortDateFormat = self::FORMAT_DATE_XLSX14_ACTUAL;
101+
102+
protected static string $longDateFormat = self::FORMAT_DATE_LONG_DATE;
103+
104+
protected static string $dateTimeFormat = self::FORMAT_DATE_XLSX22_ACTUAL;
105+
106+
protected static string $timeFormat = self::FORMAT_DATE_TIME2;
107+
87108
/**
88109
* Excel built-in number formats.
89110
*/
@@ -178,16 +199,40 @@ public function applyFromArray(array $styleArray): static
178199
/**
179200
* Get Format Code.
180201
*/
181-
public function getFormatCode(): ?string
202+
public function getFormatCode(bool $extended = false): ?string
182203
{
183204
if ($this->isSupervisor) {
184-
return $this->getSharedComponent()->getFormatCode();
205+
return $this->getSharedComponent()->getFormatCode($extended);
185206
}
186-
if (is_int($this->builtInFormatCode)) {
187-
return self::builtInFormatCode($this->builtInFormatCode);
207+
$builtin = $this->getBuiltInFormatCode();
208+
if (is_int($builtin)) {
209+
if ($extended) {
210+
if ($builtin === self::SHORT_DATE_INDEX) {
211+
return self::$shortDateFormat;
212+
}
213+
if ($builtin === self::DATE_TIME_INDEX) {
214+
return self::$dateTimeFormat;
215+
}
216+
}
217+
218+
return self::builtInFormatCode($builtin);
188219
}
189220

190-
return $this->formatCode;
221+
return $extended ? self::convertSystemFormats($this->formatCode) : $this->formatCode;
222+
}
223+
224+
public static function convertSystemFormats(?string $formatCode): ?string
225+
{
226+
if (is_string($formatCode)) {
227+
if (stripos($formatCode, self::FORMAT_SYSDATE_F800) !== false || stripos($formatCode, self::FORMAT_SYSDATE_X) !== false) {
228+
return self::$longDateFormat;
229+
}
230+
if (stripos($formatCode, self::FORMAT_SYSTIME_F400) !== false || stripos($formatCode, self::FORMAT_SYSTIME_X) !== false) {
231+
return self::$timeFormat;
232+
}
233+
}
234+
235+
return $formatCode;
191236
}
192237

193238
/**
@@ -290,15 +335,15 @@ private static function fillBuiltInFormatCodes(): void
290335
self::$builtInFormats[11] = '0.00E+00';
291336
self::$builtInFormats[12] = '# ?/?';
292337
self::$builtInFormats[13] = '# ??/??';
293-
self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
294-
self::$builtInFormats[15] = 'd-mmm-yy';
338+
self::$builtInFormats[14] = self::FORMAT_DATE_XLSX14_ACTUAL; // Despite ECMA 'mm-dd-yy';
339+
self::$builtInFormats[15] = self::FORMAT_DATE_XLSX15;
295340
self::$builtInFormats[16] = 'd-mmm';
296341
self::$builtInFormats[17] = 'mmm-yy';
297342
self::$builtInFormats[18] = 'h:mm AM/PM';
298343
self::$builtInFormats[19] = 'h:mm:ss AM/PM';
299344
self::$builtInFormats[20] = 'h:mm';
300345
self::$builtInFormats[21] = 'h:mm:ss';
301-
self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
346+
self::$builtInFormats[22] = self::FORMAT_DATE_XLSX22_ACTUAL; // Despite ECMA 'm/d/yy h:mm';
302347

303348
self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
304349
self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
@@ -427,4 +472,44 @@ protected function exportArray1(): array
427472

428473
return $exportedArray;
429474
}
475+
476+
public static function getShortDateFormat(): string
477+
{
478+
return self::$shortDateFormat;
479+
}
480+
481+
public static function setShortDateFormat(string $shortDateFormat): void
482+
{
483+
self::$shortDateFormat = $shortDateFormat;
484+
}
485+
486+
public static function getLongDateFormat(): string
487+
{
488+
return self::$longDateFormat;
489+
}
490+
491+
public static function setLongDateFormat(string $longDateFormat): void
492+
{
493+
self::$longDateFormat = $longDateFormat;
494+
}
495+
496+
public static function getDateTimeFormat(): string
497+
{
498+
return self::$dateTimeFormat;
499+
}
500+
501+
public static function setDateTimeFormat(string $dateTimeFormat): void
502+
{
503+
self::$dateTimeFormat = $dateTimeFormat;
504+
}
505+
506+
public static function getTimeFormat(): string
507+
{
508+
return self::$timeFormat;
509+
}
510+
511+
public static function setTimeFormat(string $timeFormat): void
512+
{
513+
self::$timeFormat = $timeFormat;
514+
}
430515
}

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ public function calculateColumnWidths(): static
747747
$cellValue = NumberFormat::toFormattedString(
748748
$cell->getCalculatedValue(),
749749
(string) $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex())
750-
->getNumberFormat()->getFormatCode()
750+
->getNumberFormat()->getFormatCode(true)
751751
);
752752

753753
if ($cellValue !== null && $cellValue !== '') {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Style;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class NumberFormatSystemDateTimeTest extends TestCase
12+
{
13+
private string $shortDateFormat;
14+
15+
private string $longDateFormat;
16+
17+
private string $dateTimeFormat;
18+
19+
private string $timeFormat;
20+
21+
protected function setUp(): void
22+
{
23+
$this->shortDateFormat = NumberFormat::getShortDateFormat();
24+
$this->longDateFormat = NumberFormat::getLongDateFormat();
25+
$this->dateTimeFormat = NumberFormat::getDateTimeFormat();
26+
$this->timeFormat = NumberFormat::getTimeFormat();
27+
}
28+
29+
protected function tearDown(): void
30+
{
31+
NumberFormat::setShortDateFormat($this->shortDateFormat);
32+
NumberFormat::setLongDateFormat($this->longDateFormat);
33+
NumberFormat::setDateTimeFormat($this->dateTimeFormat);
34+
NumberFormat::setTimeFormat($this->timeFormat);
35+
}
36+
37+
public function testOverrides(): void
38+
{
39+
$spreadsheet = new Spreadsheet();
40+
$sheet = $spreadsheet->getActiveSheet();
41+
$formula = '=DATEVALUE("2024-02-29")+TIMEVALUE("8:12:15 AM")';
42+
$sheet->getCell('A1')->setValue($formula);
43+
$sheet->getCell('A2')->setValue($formula);
44+
$sheet->getStyle('A2')->getNumberFormat()
45+
->setBuiltinFormatCode(14);
46+
$sheet->getCell('A3')->setValue($formula);
47+
$sheet->getStyle('A3')->getNumberFormat()
48+
->setBuiltinFormatCode(15);
49+
$sheet->getCell('A4')->setValue($formula);
50+
$sheet->getStyle('A4')->getNumberFormat()
51+
->setBuiltinFormatCode(22);
52+
$sheet->getCell('A5')->setValue($formula);
53+
$sheet->getStyle('A5')->getNumberFormat()
54+
->setFormatCode('[$-F800]');
55+
$sheet->getCell('A6')->setValue($formula);
56+
$sheet->getStyle('A6')->getNumberFormat()
57+
->setFormatCode('[$-F400]');
58+
$sheet->getCell('A7')->setValue($formula);
59+
$sheet->getStyle('A7')->getNumberFormat()
60+
->setFormatCode('[$-x-sysdate]');
61+
$sheet->getCell('A8')->setValue($formula);
62+
$sheet->getStyle('A8')->getNumberFormat()
63+
->setFormatCode('[$-x-systime]');
64+
$sheet->getCell('A9')->setValue($formula);
65+
$sheet->getStyle('A9')->getNumberFormat()
66+
->setFormatCode('hello' . NumberFormat::FORMAT_SYSDATE_F800 . 'goodbye');
67+
NumberFormat::setShortDateFormat('yyyy/mm/dd');
68+
NumberFormat::setDateTimeFormat('yyyy/mm/dd hh:mm AM/PM');
69+
NumberFormat::setLongDateFormat('dddd d mmm yyyy');
70+
NumberFormat::setTimeFormat('h:mm');
71+
self::assertSame('2024/02/29', $sheet->getCell('A2')->getformattedValue());
72+
self::assertSame('2024/02/29 08:12 AM', $sheet->getCell('A4')->getformattedValue());
73+
self::assertSame('Thursday 29 Feb 2024', $sheet->getCell('A5')->getformattedValue());
74+
self::assertSame('8:12', $sheet->getCell('A6')->getformattedValue());
75+
self::assertSame('Thursday 29 Feb 2024', $sheet->getCell('A7')->getformattedValue());
76+
self::assertSame('8:12', $sheet->getCell('A8')->getformattedValue());
77+
self::assertSame('Thursday 29 Feb 2024', $sheet->getCell('A9')->getformattedValue());
78+
$spreadsheet->disconnectWorksheets();
79+
}
80+
81+
public function testDefaults(): void
82+
{
83+
$spreadsheet = new Spreadsheet();
84+
$sheet = $spreadsheet->getActiveSheet();
85+
$formula = '=DATEVALUE("2024-02-29")+TIMEVALUE("8:12:15 AM")';
86+
$sheet->getCell('A1')->setValue($formula);
87+
$sheet->getCell('A2')->setValue($formula);
88+
$sheet->getStyle('A2')->getNumberFormat()
89+
->setBuiltinFormatCode(14);
90+
$sheet->getCell('A3')->setValue($formula);
91+
$sheet->getStyle('A3')->getNumberFormat()
92+
->setBuiltinFormatCode(15);
93+
$sheet->getCell('A4')->setValue($formula);
94+
$sheet->getStyle('A4')->getNumberFormat()
95+
->setBuiltinFormatCode(22);
96+
$sheet->getCell('A5')->setValue($formula);
97+
$sheet->getStyle('A5')->getNumberFormat()
98+
->setFormatCode('[$-F800]');
99+
$sheet->getCell('A6')->setValue($formula);
100+
$sheet->getStyle('A6')->getNumberFormat()
101+
->setFormatCode('[$-F400]');
102+
$sheet->getCell('A7')->setValue($formula);
103+
$sheet->getStyle('A7')->getNumberFormat()
104+
->setFormatCode('[$-x-sysdate]');
105+
$sheet->getCell('A8')->setValue($formula);
106+
$sheet->getStyle('A8')->getNumberFormat()
107+
->setFormatCode('[$-x-systime]');
108+
$sheet->getCell('A9')->setValue($formula);
109+
$sheet->getStyle('A9')->getNumberFormat()
110+
->setFormatCode('hello' . NumberFormat::FORMAT_SYSDATE_F800 . 'goodbye');
111+
self::assertSame('2/29/2024', $sheet->getCell('A2')->getformattedValue());
112+
self::assertSame('2/29/2024 8:12', $sheet->getCell('A4')->getformattedValue());
113+
self::assertSame('Thursday, February 29, 2024', $sheet->getCell('A5')->getformattedValue());
114+
self::assertSame('8:12:15 AM', $sheet->getCell('A6')->getformattedValue());
115+
self::assertSame('Thursday, February 29, 2024', $sheet->getCell('A7')->getformattedValue());
116+
self::assertSame('8:12:15 AM', $sheet->getCell('A8')->getformattedValue());
117+
self::assertSame('Thursday, February 29, 2024', $sheet->getCell('A9')->getformattedValue());
118+
$spreadsheet->disconnectWorksheets();
119+
}
120+
}

tests/data/Calculation/TextData/TEXT.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@
8181
'no arguments' => ['exception'],
8282
'one argument' => ['exception', 1.75],
8383
'boolean in lieu of string' => ['TRUE', true, '@'],
84+
'system long date format' => ['Sunday, January 1, 2012', '1-Jan-2012', '[$-x-sysdate]'],
8485
];

tests/data/Shared/Date/FormatCodes.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,10 @@
160160
false,
161161
'\D-00000',
162162
],
163+
[true, '[$-F800]'],
164+
[true, 'hello[$-F400]goodbye'],
165+
[false, '[$-F401]'],
166+
[true, '[$-x-sysdate]'],
167+
[true, '[$-x-systime]'],
168+
[false, '[$-x-systim]'],
163169
];

0 commit comments

Comments
 (0)