Skip to content

2.x calendar changes #2937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -990,11 +990,6 @@ parameters:
count: 6
path: src/PhpSpreadsheet/Cell/Cell.php

-
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 1
path: src/PhpSpreadsheet/Cell/Cell.php

-
message: "#^Call to an undefined method object\\:\\:getHashCode\\(\\)\\.$#"
count: 1
Expand Down
20 changes: 14 additions & 6 deletions src/PhpSpreadsheet/Cell/Cell.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,21 @@ public function getValue()

/**
* Get cell value with formatting.
*
* @return string
*/
public function getFormattedValue()
public function getFormattedValue(): string
{
return (string) NumberFormat::toFormattedString(
$currentCalendar = SharedDate::getExcelCalendar();
SharedDate::setExcelCalendar($this->getWorksheet()->getParent()->getExcelCalendar());

$formattedValue = (string) NumberFormat::toFormattedString(
$this->getCalculatedValue(),
$this->getStyle()
->getNumberFormat()->getFormatCode()
);

SharedDate::setExcelCalendar($currentCalendar);

return $formattedValue;
}

/**
Expand Down Expand Up @@ -342,8 +347,6 @@ public function setValueExplicit($value, $dataType, bool $isArrayFormula = false
break;
default:
throw new Exception('Invalid datatype: ' . $dataType);

break;
}

// set the datatype
Expand Down Expand Up @@ -402,6 +405,9 @@ private function processArrayResult(
public function getCalculatedValue(bool $asArray = false, bool $resetLog = true)
{
if ($this->dataType === DataType::TYPE_FORMULA) {
$currentCalendar = SharedDate::getExcelCalendar();
SharedDate::setExcelCalendar($this->getWorksheet()->getParent()->getExcelCalendar());

try {
$coordinate = $this->getCoordinate();
$worksheet = $this->getWorksheet();
Expand Down Expand Up @@ -430,6 +436,7 @@ public function getCalculatedValue(bool $asArray = false, bool $resetLog = true)
$this->getWorksheet()->setSelectedCells($selected);
$this->getWorksheet()->getParent()->setActiveSheetIndex($index);
} catch (Exception $ex) {
SharedDate::setExcelCalendar($currentCalendar);
if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
return $this->calculatedValue; // Fallback for calculations referencing external files.
} elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) {
Expand All @@ -441,6 +448,7 @@ public function getCalculatedValue(bool $asArray = false, bool $resetLog = true)
);
}

SharedDate::setExcelCalendar($currentCalendar);
if ($result === '#Not Yet Implemented') {
return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
}
Expand Down
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Reader/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -2026,9 +2026,9 @@ private function readDateMode(): void
$this->pos += 4 + $length;

// offset: 0; size: 2; 0 = base 1900, 1 = base 1904
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
$this->spreadsheet->setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
if (ord($recordData[0]) == 1) {
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
$this->spreadsheet->setExcelCalendar(Date::CALENDAR_MAC_1904);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -681,11 +681,11 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// Set base date
if ($xmlWorkbookNS->workbookPr) {
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
$excel->setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
$attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr);
if (isset($attrs1904['date1904'])) {
if (self::boolean((string) $attrs1904['date1904'])) {
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
$excel->setExcelCalendar(Date::CALENDAR_MAC_1904);
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/PhpSpreadsheet/Shared/Date.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class Date
protected static $defaultTimeZone;

/**
* Set the Excel calendar (Windows 1900 or Mac 1904).
* Set the Excel Date Calendar (Windows 1900 or Mac 1904) used for calculations and formatting.
*
* @param int $baseYear Excel base date (1900 or 1904)
*
Expand All @@ -85,7 +85,8 @@ public static function setExcelCalendar($baseYear)
}

/**
* Return the Excel calendar (Windows 1900 or Mac 1904).
* Return the Excel Date Calendar (Windows 1900 or Mac 1904)
* to be used for current calculations and formatting.
*
* @return int Excel base date (1900 or 1904)
*/
Expand Down
39 changes: 38 additions & 1 deletion src/PhpSpreadsheet/Spreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\Style;
Expand All @@ -13,7 +14,7 @@

class Spreadsheet
{
// Allowable values for workbook window visilbity
// Allowable values for workbook window visibility
const VISIBILITY_VISIBLE = 'visible';
const VISIBILITY_HIDDEN = 'hidden';
const VISIBILITY_VERY_HIDDEN = 'veryHidden';
Expand Down Expand Up @@ -55,6 +56,14 @@ class Spreadsheet
*/
private $workSheetCollection = [];

/**
* Base calendar year to use for calculations
* Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904).
*
* @var int
*/
protected $excelCalendar = Date::CALENDAR_WINDOWS_1900;

/**
* Calculation Engine.
*
Expand Down Expand Up @@ -202,6 +211,34 @@ class Spreadsheet
*/
private $tabRatio = 600;

/**
* Set the Excel Calendar (Windows 1900 or Mac 1904).
*
* @param int $baseYear Excel base date (1900 or 1904)
*
* @return bool Success or failure
*/
public function setExcelCalendar(int $baseYear): bool
{
if (($baseYear == Date::CALENDAR_WINDOWS_1900) || ($baseYear == Date::CALENDAR_MAC_1904)) {
$this->excelCalendar = $baseYear;

return true;
}

return false;
}

/**
* Return the Excel Calendar (Windows 1900 or Mac 1904).
*
* @return int Excel base date (1900 or 1904)
*/
public function getExcelCalendar(): int
{
return $this->excelCalendar;
}

/**
* The workbook has macros ?
*
Expand Down
6 changes: 3 additions & 3 deletions src/PhpSpreadsheet/Writer/Xls/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -960,9 +960,9 @@ private function writeDateMode(): void
$record = 0x0022; // Record identifier
$length = 0x0002; // Bytes to follow

$f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904)
? 1
: 0; // Flag for 1904 date system
$f1904 = ($this->spreadsheet->getExcelCalendar() === Date::CALENDAR_MAC_1904)
? 1 // Flag for 1904 date system
: 0; // Flag for 1900 date system

$header = pack('vv', $record, $length);
$data = pack('v', $f1904);
Expand Down
6 changes: 3 additions & 3 deletions src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function writeWorkbook(Spreadsheet $spreadsheet, $recalcRequired = false)
$this->writeFileVersion($objWriter);

// workbookPr
$this->writeWorkbookPr($objWriter);
$this->writeWorkbookPr($objWriter, $spreadsheet);

// workbookProtection
$this->writeWorkbookProtection($objWriter, $spreadsheet);
Expand Down Expand Up @@ -80,11 +80,11 @@ private function writeFileVersion(XMLWriter $objWriter): void
/**
* Write WorkbookPr.
*/
private function writeWorkbookPr(XMLWriter $objWriter): void
private function writeWorkbookPr(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
{
$objWriter->startElement('workbookPr');

if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
if ($spreadsheet->getExcelCalendar() === Date::CALENDAR_MAC_1904) {
$objWriter->writeAttribute('date1904', '1');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ protected function tearDown(): void
}
}

protected static function setMac1904(): void
protected static function setMac1904(?Worksheet $worksheet = null): void
{
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
if ($worksheet === null) {
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
} else {
$worksheet->getParent()->setExcelCalendar(Date::CALENDAR_MAC_1904);
}
}

protected static function setUnixReturn(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public function providerISOWEEKNUM(): array
public function testISOWEEKNUM1904($expectedResult, $dateValue): void
{
$this->mightHaveException($expectedResult);
self::setMac1904();
$sheet = $this->getSheet();
self::setMac1904($sheet);
$sheet->getCell('A1')->setValue("=ISOWEEKNUM($dateValue)");
$sheet->getCell('B1')->setValue('1954-11-23');
self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public function providerWEEKNUM(): array
public function testWEEKNUM1904($expectedResult, string $formula): void
{
$this->mightHaveException($expectedResult);
self::setMac1904();
$sheet = $this->getSheet();
self::setMac1904($sheet);
$sheet->getCell('B1')->setValue('1954-11-23');
$sheet->getCell('A1')->setValue("=WEEKNUM($formula)");
self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue());
Expand Down
117 changes: 117 additions & 0 deletions tests/PhpSpreadsheetTests/Reader/Xls/DateReaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;

use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PHPUnit\Framework\TestCase;

class DateReaderTest extends TestCase
{
protected function tearDown(): void
{
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
}

public function testReadExcel1900Spreadsheet(): void
{
$filename = 'tests/data/Reader/XLS/1900_Calendar.xls';
$reader = new Xls();
$spreadsheet = $reader->load($filename);

self::assertSame(Date::CALENDAR_WINDOWS_1900, $spreadsheet->getExcelCalendar());

$worksheet = $spreadsheet->getActiveSheet();
self::assertSame(44562, $worksheet->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue());
self::assertSame(44926, $worksheet->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue());
}

public function testReadExcel1904Spreadsheet(): void
{
$filename = 'tests/data/Reader/XLS/1904_Calendar.xls';
$reader = new Xls();
$spreadsheet = $reader->load($filename);

self::assertSame(Date::CALENDAR_MAC_1904, $spreadsheet->getExcelCalendar());

$worksheet = $spreadsheet->getActiveSheet();
self::assertSame(43100, $worksheet->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet->getCell('A1')->getFormattedValue());
self::assertSame(43464, $worksheet->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet->getCell('A2')->getFormattedValue());
}

public function testNewDateInLoadedExcel1900Spreadsheet(): void
{
$filename = 'tests/data/Reader/XLS/1900_Calendar.xls';
$reader = new Xls();
$spreadsheet = $reader->load($filename);

$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A4')->setValue('=DATE(2023,1,1)');
self::assertEquals(44927, $worksheet->getCell('A4')->getCalculatedValue());
}

public function testNewDateInLoadedExcel1904Spreadsheet(): void
{
$filename = 'tests/data/Reader/XLS/1904_Calendar.xls';
$reader = new Xls();
$spreadsheet = $reader->load($filename);

$worksheet = $spreadsheet->getActiveSheet();
$worksheet->getCell('A4')->setValue('=DATE(2023,1,1)');
self::assertEquals(43465, $worksheet->getCell('A4')->getCalculatedValue());
}

public function testSwitchCalendars(): void
{
$filename1900 = 'tests/data/Reader/XLS/1900_Calendar.xls';
$reader1900 = new Xls();
$spreadsheet1900 = $reader1900->load($filename1900);
$worksheet1900 = $spreadsheet1900->getActiveSheet();

$filename1904 = 'tests/data/Reader/XLS/1904_Calendar.xls';
$reader1904 = new Xls();
$spreadsheet1904 = $reader1904->load($filename1904);
$worksheet1904 = $spreadsheet1904->getActiveSheet();

self::assertSame(44562, $worksheet1900->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue());
self::assertSame(44926, $worksheet1900->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue());
self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue());
self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue());
self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue());
self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue());

self::assertSame(43100, $worksheet1904->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue());
self::assertSame(43464, $worksheet1904->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue());
self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue());
self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue());
self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue());
self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue());

// Check that accessing date values from one spreadsheet doesn't break accessing correct values from another
self::assertSame(44561, $worksheet1900->getCell('B1')->getCalculatedValue());
self::assertSame('2021-12-31', $worksheet1900->getCell('B1')->getFormattedValue());
self::assertSame(44927, $worksheet1900->getCell('B2')->getCalculatedValue());
self::assertSame('2023-01-01', $worksheet1900->getCell('B2')->getFormattedValue());
self::assertSame(44562, $worksheet1900->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet1900->getCell('A1')->getFormattedValue());
self::assertSame(44926, $worksheet1900->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet1900->getCell('A2')->getFormattedValue());

self::assertSame(43099, $worksheet1904->getCell('B1')->getCalculatedValue());
self::assertSame('2021-12-31', $worksheet1904->getCell('B1')->getFormattedValue());
self::assertSame(43465, $worksheet1904->getCell('B2')->getCalculatedValue());
self::assertSame('2023-01-01', $worksheet1904->getCell('B2')->getFormattedValue());
self::assertSame(43100, $worksheet1904->getCell('A1')->getValue());
self::assertSame('2022-01-01', $worksheet1904->getCell('A1')->getFormattedValue());
self::assertSame(43464, $worksheet1904->getCell('A2')->getValue());
self::assertSame('2022-12-31', $worksheet1904->getCell('A2')->getFormattedValue());
}
}
Loading