Skip to content

Commit 6b3cb42

Browse files
authored
Merge branch 'master' into issue4522
2 parents 170b058 + 09442da commit 6b3cb42

32 files changed

+1771
-647
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12-
- Nothing yet.
12+
- Address Excel Inappropriate Number Format Substitution. [PR #4532](https://github.com/PHPOffice/PhpSpreadsheet/pull/4532)
1313

1414
### Removed
1515

@@ -29,7 +29,13 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2929

3030
### Fixed
3131

32-
- Nothing yet.
32+
- Writer Html/Pdf support RTL alignment of tables. [Issue #1104](https://github.com/PHPOffice/PhpSpreadsheet/issues/1104) [PR #4535](https://github.com/PHPOffice/PhpSpreadsheet/pull/4535)
33+
- Xlsx Reader use dynamic arrays if spreadsheet did so. [PR #4533](https://github.com/PHPOffice/PhpSpreadsheet/pull/4533)
34+
- Ods Reader Nested table-row. [Issue #4528](https://github.com/PHPOffice/PhpSpreadsheet/issues/4528) [Issue #2507](https://github.com/PHPOffice/PhpSpreadsheet/issues/2507) [PR #4531](https://github.com/PHPOffice/PhpSpreadsheet/pull/4531)
35+
- Recognize application/x-empty mimetype. [Issue #4521](https://github.com/PHPOffice/PhpSpreadsheet/issues/4521) [PR #4524](https://github.com/PHPOffice/PhpSpreadsheet/pull/4524)
36+
- Micro-optimization in getSheetByName. [PR #4499](https://github.com/PHPOffice/PhpSpreadsheet/pull/4499)
37+
- Bug in resizeMatricesExtend. [Issue #4451](https://github.com/PHPOffice/PhpSpreadsheet/issues/4451) [PR #4474](https://github.com/PHPOffice/PhpSpreadsheet/pull/4474)
38+
- Preserve 0x0a in Strings if Desired. [Issue #347](https://github.com/PHPOffice/PhpSpreadsheet/issues/347) [PR #4536](https://github.com/PHPOffice/PhpSpreadsheet/pull/4536)
3339

3440
## 2025-06-22 - 4.4.0
3541

composer.lock

Lines changed: 87 additions & 85 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/topics/Excel Anomalies.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Excel Anomalies
2+
3+
This is documentation for some behavior in Excel itself which we
4+
just do not understand, or which may come as a surprise to the user.
5+
6+
## Date Number Format
7+
8+
My system short date format is set to `yyyy-mm-dd`. Excel, for a very long time, did not include that amongst its formatting choices for dates, so it needed to be added as a custom format - no big deal. It has recently been added to the list of date formats, but ...
9+
10+
I used Excel to create a spreadsheet, and included some dates, specifying `yyyy-mm-dd` formatting. When I looked at the resulting spreadsheet, I was surprised to see that Excel had stored the style not as `yyyy-mm-dd`, but rather as builtin style 14 (system short date format). Apparently the fact that the Excel styling matched my system choice was sufficient for it to override my choice! This is an astonishingly user-hostile implementation. Even though there are formats which, by design, "respond to changes in regional date and time settings", and even though the format I selected was not among those, Excel decided it was appropriate to vary the display even when I said I wanted an unvarying format. I assume, but have not confirmed, that this applies to formats other than `yyyy-mm-dd`.
11+
12+
Note that this is not a problem when using PhpSpreadsheet to set the style, only when you let Excel do it. And, in that case, after a little experimentation, I figured out a format that Excel doesn't sabotage `[Black]yyyy-mm-dd`.
13+
14+
If you have a spreadsheet that has been altered in this way, it can be fixed with the following PhpSpreadsheet code:
15+
```php
16+
foreach ($spreadsheet->getCellXfCollection() as $style) {
17+
$numberFormat = $style->getNumberFormat();
18+
// okay to use NumberFormat::SHORT_DATE_INDEX below
19+
if ($numberFormat->getBuiltInFormatCode() === 14) {
20+
$numberFormat->setFormatCode('yyyy-mm-dd');
21+
}
22+
}
23+
```
24+
Starting with PhpSpreadsheet 4.5.0, this can be simplified to:
25+
```php
26+
$spreadsheet->replaceBuiltinNumberFormat(
27+
\PhpOffice\PhpSpreadsheet\Style\NumberFormat::SHORT_DATE_INDEX,
28+
'yyyy-mm-dd'
29+
);
30+
```
31+
32+
## Negative Time Intervals
33+
34+
You have a time in one cell, and a time in another, and you want to subtract and display the result in `h:mm` format. No problem if the result is positive. But, if it's negative, Excel just fills the cell with `#`. There is a solution of sorts. If you use a 1904 base date (default on Mac), the negative interval will work just fine. Alas, no dice if you use a 1900 base data (default on Windows). No idea why they can't fix that - the existing implementation can't really be something that anybody actually wants. Note that it is *not* safe to change the base date for an existing spreadsheet, so, if this is something you want to do, make sure you change the base date before populating any data.
35+
36+
## Long-ago Dates
37+
38+
Excel does not support dates before either 1900-01-01 (Windows default) or 1904-01-01 (Mac default). For the 1900 base year, there is the additional problem that non-existent date 1900-02-29 is squeezed between 1900-02-28 and 1900-03-01.
39+
40+
## Weird Fractions
41+
42+
Similar fraction formats have inconsistent results in Excel. For example, if a cell contains the value 1 and the cell's format is `0 0/0`, it will display as `1 0/1`. But, if the cell's format is `? ??/???`, it will display as `1`. See [this issue](https://github.com/PHPOffice/PhpSpreadsheet/issues/3625), which remains open because, in the absence of usable documentation, we aren't sure how to handle things.
43+
44+
## COUNTIF and Text Cells
45+
46+
In Excel, COUNTIF appears to ignore text cells, behavior which doesn't seem to be documented anywhere. See [this issue](https://github.com/PHPOffice/PhpSpreadsheet/issues/3802), which remains open because, in the absence of usable documentation, we aren't sure how to handle things.

docs/topics/The Dating Game.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ Open/Libre Office and Gnumeric don't have this limitation, and negative date/tim
1414
To write a date in a cell using PhpSpreadsheet, we need to calculate the serialized Excel datestamp for that date. Methods to do this are available in the Shared\Date class, which provides a number of methods for conversion between different date options typically used in PHP applications (Unix timestamp, PHP DateTime objects and some recognisable formatted strings) and the Excel serialized value; and vice versa.
1515

1616
- Shared\Date::convertIsoDate()
17-
- Converts a date/time in [ISO-8601 standard format](https://en.wikipedia.org/wiki/ISO_8601) to an Excel serialized timestamp
17+
- Converts a date/time in [ISO-8601 standard format](https://en.wikipedia.org/wiki/ISO_8601) to an Excel serialized timestamp
1818
- Shared\Date::PHPToExcel()
19-
- Converts a Unix timestamp, a PHP DateTime object, or a recognisable formatted string to an Excel serialized timestamp
19+
- Converts a Unix timestamp, a PHP DateTime object, or a recognisable formatted string to an Excel serialized timestamp
2020
- Shared\Date::dateTimeToExcel()
21-
- Converts a Unix timestamp to an Excel serialized timestamp
21+
- Converts a Unix timestamp to an Excel serialized timestamp
2222
- Shared\Date::timestampToExcel()
23-
- Converts a PHP DateTime object to an Excel serialized timestamp
23+
- Converts a PHP DateTime object to an Excel serialized timestamp
2424
- Shared\Date::formattedPHPToExcel()
25-
- Converts year, month, day, hour, minute, and second to an Excel serialized timestamp
25+
- Converts year, month, day, hour, minute, and second to an Excel serialized timestamp
2626
- Shared\Date::excelToDateTimeObject()
27-
- Converts an Excel serialized timestamp to a PHP DateTime object
27+
- Converts an Excel serialized timestamp to a PHP DateTime object
2828
- Shared\Date::excelToTimestamp()
29-
- Converts an Excel serialized timestamp to a Unix timestamp.
30-
- The use of Unix timestamps, and therefore this function, is discouraged: they are not Y2038-safe on a 32-bit system, and have no timezone info.
29+
- Converts an Excel serialized timestamp to a Unix timestamp.
30+
- The use of Unix timestamps, and therefore this function, is discouraged: they are not Y2038-safe on a 32-bit system, and have no timezone info.
3131

3232
We probably also want to set the number format mask for the cell so that it will be displayed as a human-readable date.
3333
```php
@@ -184,7 +184,7 @@ MS Excel allows any separator character between hours/minutes/seconds; PhpSpread
184184

185185
### Duration (Elapsed Time)
186186

187-
Excel also supports formatting a value as a duration; a total number of hours, minutes or seconds rather than a time of day.
187+
Excel also supports formatting a value as a duration; a total number of hours, minutes or seconds rather than a time of day. However, please note that negative durations are supported only if using base year 1904 (Mac default).
188188

189189
| Code | Description | Displays as |
190190
|---------|----------------------------------------------------------------|-------------|

docs/topics/defined-names.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -545,17 +545,18 @@ $this->spreadsheet->addDefinedName(
545545
### Naming Names
546546

547547
The names that you assign to Defined Name must follow the following set of rules:
548+
548549
- The first character of a name must be one of the following characters:
549-
- letter (including UTF-8 letters)
550-
- underscore (`_`)
550+
- letter (including UTF-8 letters)
551+
- underscore (`_`)
551552
- Remaining characters in the name can be
552-
- letters (including UTF-8 letters)
553-
- numbers (including UTF-8 numbers)
554-
- periods (`.`)
555-
- underscore characters (`_`)
553+
- letters (including UTF-8 letters)
554+
- numbers (including UTF-8 numbers)
555+
- periods (`.`)
556+
- underscore characters (`_`)
556557
- The following are not allowed:
557-
- Space characters are not allowed as part of a name.
558-
- Names can't look like cell addresses, such as A35 or R2C2
558+
- Space characters are not allowed as part of a name.
559+
- Names can't look like cell addresses, such as A35 or R2C2
559560
- Names are not case sensitive. For example, `North` and `NORTH` are treated as the same name.
560561

561562
### Limitations

samples/Pdf/21f_Drawing_mpdf.php renamed to samples/Pdf/21f_Drawing.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22

33
use PhpOffice\PhpSpreadsheet\Spreadsheet;
44
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
5-
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf;
65

76
require __DIR__ . '/../Header.php';
8-
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
9-
require_once __DIR__ . '/Mpdf2.php';
107

8+
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
119
$spreadsheet = new Spreadsheet();
1210
$sheet = $spreadsheet->getActiveSheet();
1311

@@ -41,11 +39,6 @@
4139
$spreadsheet->mergeDrawingCellsForPdf();
4240

4341
$helper->log('Write to Mpdf');
44-
$writer = new Mpdf($spreadsheet);
45-
$filename = $helper->getFileName(__FILE__, 'pdf');
46-
$writer->save($filename);
47-
$helper->log("Saved $filename");
48-
if (PHP_SAPI !== 'cli') {
49-
echo '<a href="/download.php?type=pdf&name=' . basename($filename) . '">Download ' . basename($filename) . '</a><br />';
50-
}
42+
$helper->write($spreadsheet, __FILE__, ['Mpdf']);
43+
5144
$spreadsheet->disconnectWorksheets();

samples/Pdf/21g_Direction.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
5+
require __DIR__ . '/../Header.php';
6+
7+
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
8+
$spreadsheet = new Spreadsheet();
9+
$sheet = $spreadsheet->getActiveSheet();
10+
11+
$sheet1 = $spreadsheet->getActiveSheet();
12+
$sheet2 = $spreadsheet->createSheet();
13+
$sheet3 = $spreadsheet->createSheet();
14+
$cells = [
15+
['a1', 'b1', 'c1'],
16+
['a2', 'b2', 'c2'],
17+
];
18+
$sheet1->fromArray($cells);
19+
$sheet1->setRightToLeft(true);
20+
$sheet2->fromArray($cells);
21+
$sheet3->fromArray($cells);
22+
$sheet3->setRightToLeft(true);
23+
24+
$helper->log('Write to html, mpdf, tcpdf');
25+
// Save
26+
$helper->write($spreadsheet, __FILE__, ['Html', 'Mpdf', 'Tcpdf']);
27+
28+
$spreadsheet->disconnectWorksheets();

samples/Pdf/21h_DirectionMultiple.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
5+
6+
require __DIR__ . '/../Header.php';
7+
8+
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
9+
$spreadsheet = new Spreadsheet();
10+
$sheet = $spreadsheet->getActiveSheet();
11+
12+
$sheet1 = $spreadsheet->getActiveSheet();
13+
$sheet2 = $spreadsheet->createSheet();
14+
$sheet3 = $spreadsheet->createSheet();
15+
$cells = [
16+
['a1', 'b1', 'c1'],
17+
['a2', 'b2', 'c2'],
18+
];
19+
$sheet1->fromArray($cells);
20+
$sheet1->setRightToLeft(true);
21+
$sheet2->fromArray($cells);
22+
$sheet3->fromArray($cells);
23+
$sheet3->setRightToLeft(true);
24+
25+
$helper->log('Write to html, mpdf');
26+
// Save
27+
$helper->write(
28+
$spreadsheet,
29+
__FILE__,
30+
['Html', 'Mpdf'],
31+
writerCallback: function (HtmlWriter $writer): void {
32+
$writer->writeAllSheets();
33+
}
34+
);
35+
36+
$spreadsheet->disconnectWorksheets();

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -852,15 +852,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i
852852
if ($matrix2Columns < $matrix1Columns) {
853853
for ($i = 0; $i < $matrix2Rows; ++$i) {
854854
/** @var mixed[][] $matrix2 */
855-
$x = $matrix2[$i][$matrix2Columns - 1];
855+
$x = ($matrix2Columns === 1) ? $matrix2[$i][0] : null;
856856
for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
857857
$matrix2[$i][$j] = $x;
858858
}
859859
}
860860
}
861861
if ($matrix2Rows < $matrix1Rows) {
862-
$x = $matrix2[$matrix2Rows - 1];
863-
for ($i = 0; $i < $matrix1Rows; ++$i) {
862+
$x = ($matrix2Rows === 1) ? $matrix2[0] : array_fill(0, $matrix2Columns, null);
863+
for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
864864
$matrix2[$i] = $x;
865865
}
866866
}
@@ -870,15 +870,15 @@ private static function resizeMatricesExtend(array &$matrix1, array &$matrix2, i
870870
if ($matrix1Columns < $matrix2Columns) {
871871
for ($i = 0; $i < $matrix1Rows; ++$i) {
872872
/** @var mixed[][] $matrix1 */
873-
$x = $matrix1[$i][$matrix1Columns - 1];
873+
$x = ($matrix1Columns === 1) ? $matrix1[$i][0] : null;
874874
for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
875875
$matrix1[$i][$j] = $x;
876876
}
877877
}
878878
}
879879
if ($matrix1Rows < $matrix2Rows) {
880-
$x = $matrix1[$matrix1Rows - 1];
881-
for ($i = 0; $i < $matrix2Rows; ++$i) {
880+
$x = ($matrix1Rows === 1) ? $matrix1[0] : array_fill(0, $matrix1Columns, null);
881+
for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
882882
$matrix1[$i] = $x;
883883
}
884884
}
@@ -2375,15 +2375,15 @@ private function executeNumericBinaryOperation(mixed $operand1, mixed $operand2,
23752375
for ($row = 0; $row < $rows; ++$row) {
23762376
for ($column = 0; $column < $columns; ++$column) {
23772377
/** @var mixed[][] $operand1 */
2378-
if ($operand1[$row][$column] === null) {
2378+
if (($operand1[$row][$column] ?? null) === null) {
23792379
$operand1[$row][$column] = 0;
23802380
} elseif (!self::isNumericOrBool($operand1[$row][$column])) {
23812381
$operand1[$row][$column] = self::makeError($operand1[$row][$column]);
23822382

23832383
continue;
23842384
}
23852385
/** @var mixed[][] $operand2 */
2386-
if ($operand2[$row][$column] === null) {
2386+
if (($operand2[$row][$column] ?? null) === null) {
23872387
$operand2[$row][$column] = 0;
23882388
} elseif (!self::isNumericOrBool($operand2[$row][$column])) {
23892389
$operand1[$row][$column] = self::makeError($operand2[$row][$column]);

src/PhpSpreadsheet/Calculation/LookupRef/Filter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ private static function filterByRow(array $lookupArray, array $matchArray): arra
6262

6363
return array_filter(
6464
array_values($lookupArray),
65-
fn ($index): bool => (bool) $matchArray[$index],
65+
fn ($index): bool => (bool) ($matchArray[$index] ?? null),
6666
ARRAY_FILTER_USE_KEY
6767
);
6868
}

0 commit comments

Comments
 (0)