Add support for 1904 dates
This commit adds support for dates using the 1904 calendar (starting 1904-01-01 00:00:00). It also fixes some issues with the dates in 1900 calendar (which now correctly start at 1899-12-30 00:00:00). Finally, it is now possible to have negative timestamps, representing dates before the base date (and up to 0000-01-01 00:00:00), as per the SpreadsheetML specs. Note that some versions of Excel don't support negative dates...
This commit is contained in:
parent
e1ae3c8a81
commit
00f0484b3f
@ -19,4 +19,5 @@ abstract class Options
|
||||
|
||||
// XLSX specific options
|
||||
const TEMP_FOLDER = 'tempFolder';
|
||||
const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
|
||||
}
|
||||
|
@ -85,7 +85,14 @@ class EntityFactory implements EntityFactoryInterface
|
||||
|
||||
$styleManager = $this->managerFactory->createStyleManager($filePath, $this);
|
||||
$shouldFormatDates = $optionsManager->getOption(Options::SHOULD_FORMAT_DATES);
|
||||
$cellValueFormatter = $this->helperFactory->createCellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates);
|
||||
$shouldUse1904Dates = $optionsManager->getOption(Options::SHOULD_USE_1904_DATES);
|
||||
|
||||
$cellValueFormatter = $this->helperFactory->createCellValueFormatter(
|
||||
$sharedStringsManager,
|
||||
$styleManager,
|
||||
$shouldFormatDates,
|
||||
$shouldUse1904Dates
|
||||
);
|
||||
|
||||
$shouldPreserveEmptyRows = $optionsManager->getOption(Options::SHOULD_PRESERVE_EMPTY_ROWS);
|
||||
|
||||
|
@ -17,13 +17,14 @@ class HelperFactory extends \Box\Spout\Common\Creator\HelperFactory
|
||||
* @param SharedStringsManager $sharedStringsManager Manages shared strings
|
||||
* @param StyleManager $styleManager Manages styles
|
||||
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
||||
* @param bool $shouldUse1904Dates Whether date/time values should use a calendar starting in 1904 instead of 1900
|
||||
* @return CellValueFormatter
|
||||
*/
|
||||
public function createCellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates)
|
||||
public function createCellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates, $shouldUse1904Dates)
|
||||
{
|
||||
$escaper = $this->createStringsEscaper();
|
||||
|
||||
return new CellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates, $escaper);
|
||||
return new CellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates, $shouldUse1904Dates, $escaper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,6 +48,9 @@ class CellValueFormatter
|
||||
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
||||
protected $shouldFormatDates;
|
||||
|
||||
/** @var bool Whether date/time values should use a calendar starting in 1904 instead of 1900 */
|
||||
protected $shouldUse1904Dates;
|
||||
|
||||
/** @var \Box\Spout\Common\Helper\Escaper\XLSX Used to unescape XML data */
|
||||
protected $escaper;
|
||||
|
||||
@ -55,13 +58,15 @@ class CellValueFormatter
|
||||
* @param SharedStringsManager $sharedStringsManager Manages shared strings
|
||||
* @param StyleManager $styleManager Manages styles
|
||||
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
||||
* @param bool $shouldUse1904Dates Whether date/time values should use a calendar starting in 1904 instead of 1900
|
||||
* @param \Box\Spout\Common\Helper\Escaper\XLSX $escaper Used to unescape XML data
|
||||
*/
|
||||
public function __construct($sharedStringsManager, $styleManager, $shouldFormatDates, $escaper)
|
||||
public function __construct($sharedStringsManager, $styleManager, $shouldFormatDates, $shouldUse1904Dates, $escaper)
|
||||
{
|
||||
$this->sharedStringsManager = $sharedStringsManager;
|
||||
$this->styleManager = $styleManager;
|
||||
$this->shouldFormatDates = $shouldFormatDates;
|
||||
$this->shouldUse1904Dates = $shouldUse1904Dates;
|
||||
$this->escaper = $escaper;
|
||||
}
|
||||
|
||||
@ -189,26 +194,20 @@ class CellValueFormatter
|
||||
|
||||
/**
|
||||
* Returns a cell's PHP Date value, associated to the given timestamp.
|
||||
* NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
|
||||
* NOTE: The timestamp is a float representing the number of days since the base Excel date:
|
||||
* Dec 30th 1899, 1900 or Jan 1st, 1904, depending on the Workbook setting.
|
||||
* NOTE: The timestamp can also represent a time, if it is a value between 0 and 1.
|
||||
*
|
||||
* @see ECMA-376 Part 1 - §18.17.4
|
||||
*
|
||||
* @param float $nodeValue
|
||||
* @param int $cellStyleId 0 being the default style
|
||||
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
|
||||
*/
|
||||
protected function formatExcelTimestampValue($nodeValue, $cellStyleId)
|
||||
{
|
||||
// Fix for the erroneous leap year in Excel
|
||||
if (ceil($nodeValue) > self::ERRONEOUS_EXCEL_LEAP_YEAR_DAY) {
|
||||
$nodeValue--;
|
||||
}
|
||||
|
||||
if ($nodeValue >= 1) {
|
||||
// Values greater than 1 represent "dates". The value 1.0 representing the "base" date: 1900-01-01.
|
||||
$cellValue = $this->formatExcelTimestampValueAsDateValue($nodeValue, $cellStyleId);
|
||||
} elseif ($nodeValue >= 0) {
|
||||
// Values between 0 and 1 represent "times".
|
||||
$cellValue = $this->formatExcelTimestampValueAsTimeValue($nodeValue, $cellStyleId);
|
||||
if ($this->isValidTimestampValue($nodeValue)) {
|
||||
$cellValue = $this->formatExcelTimestampValueAsDateTimeValue($nodeValue, $cellStyleId);
|
||||
} else {
|
||||
// invalid date
|
||||
$cellValue = null;
|
||||
@ -217,24 +216,42 @@ class CellValueFormatter
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given timestamp is supported by SpreadsheetML
|
||||
* @see ECMA-376 Part 1 - §18.17.4 - this specifies the timestamp boundaries.
|
||||
*
|
||||
* @param float $timestampValue
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidTimestampValue($timestampValue)
|
||||
{
|
||||
// @NOTE: some versions of Excel don't support negative dates (e.g. Excel for Mac 2011)
|
||||
return (
|
||||
$this->shouldUse1904Dates && $timestampValue >= -695055 && $timestampValue <= 2957003.9999884 ||
|
||||
!$this->shouldUse1904Dates && $timestampValue >= -693593 && $timestampValue <= 2958465.9999884
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cell's PHP DateTime value, associated to the given timestamp.
|
||||
* Only the time value matters. The date part is set to Jan 1st, 1900 (base Excel date).
|
||||
* Only the time value matters. The date part is set to the base Excel date:
|
||||
* Dec 30th 1899, 1900 or Jan 1st, 1904, depending on the Workbook setting.
|
||||
*
|
||||
* @param float $nodeValue
|
||||
* @param int $cellStyleId 0 being the default style
|
||||
* @return \DateTime|string The value associated with the cell
|
||||
*/
|
||||
protected function formatExcelTimestampValueAsTimeValue($nodeValue, $cellStyleId)
|
||||
protected function formatExcelTimestampValueAsDateTimeValue($nodeValue, $cellStyleId)
|
||||
{
|
||||
$time = round($nodeValue * self::NUM_SECONDS_IN_ONE_DAY);
|
||||
$hours = floor($time / self::NUM_SECONDS_IN_ONE_HOUR);
|
||||
$minutes = floor($time / self::NUM_SECONDS_IN_ONE_MINUTE) - ($hours * self::NUM_SECONDS_IN_ONE_MINUTE);
|
||||
$seconds = $time - ($hours * self::NUM_SECONDS_IN_ONE_HOUR) - ($minutes * self::NUM_SECONDS_IN_ONE_MINUTE);
|
||||
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
|
||||
|
||||
// using the base Excel date (Jan 1st, 1900) - not relevant here
|
||||
$dateObj = new \DateTime('1900-01-01');
|
||||
$dateObj->setTime($hours, $minutes, $seconds);
|
||||
$daysSinceBaseDate = (int) $nodeValue;
|
||||
$timeRemainder = fmod($nodeValue, 1);
|
||||
$secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
|
||||
|
||||
$dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
|
||||
$dateObj->modify('+' . $daysSinceBaseDate . 'days');
|
||||
$dateObj->modify('+' . $secondsRemainder . 'seconds');
|
||||
|
||||
if ($this->shouldFormatDates) {
|
||||
$styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
|
||||
@ -247,40 +264,6 @@ class CellValueFormatter
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cell's PHP Date value, associated to the given timestamp.
|
||||
* NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
|
||||
*
|
||||
* @param float $nodeValue
|
||||
* @param int $cellStyleId 0 being the default style
|
||||
* @return \DateTime|string|null The value associated with the cell or NULL if invalid date value
|
||||
*/
|
||||
protected function formatExcelTimestampValueAsDateValue($nodeValue, $cellStyleId)
|
||||
{
|
||||
// Do not use any unix timestamps for calculation to prevent
|
||||
// issues with numbers exceeding 2^31.
|
||||
$secondsRemainder = fmod($nodeValue, 1) * self::NUM_SECONDS_IN_ONE_DAY;
|
||||
$secondsRemainder = round($secondsRemainder, 0);
|
||||
|
||||
try {
|
||||
$dateObj = \DateTime::createFromFormat('|Y-m-d', '1899-12-31');
|
||||
$dateObj->modify('+' . (int) $nodeValue . 'days');
|
||||
$dateObj->modify('+' . $secondsRemainder . 'seconds');
|
||||
|
||||
if ($this->shouldFormatDates) {
|
||||
$styleNumberFormatCode = $this->styleManager->getNumberFormatCode($cellStyleId);
|
||||
$phpDateFormat = DateFormatHelper::toPHPDateFormat($styleNumberFormatCode);
|
||||
$cellValue = $dateObj->format($phpDateFormat);
|
||||
} else {
|
||||
$cellValue = $dateObj;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$cellValue = null;
|
||||
}
|
||||
|
||||
return $cellValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell Boolean value from a specific node's Value.
|
||||
*
|
||||
|
@ -20,6 +20,7 @@ class OptionsManager extends OptionsManagerAbstract
|
||||
Options::TEMP_FOLDER,
|
||||
Options::SHOULD_FORMAT_DATES,
|
||||
Options::SHOULD_PRESERVE_EMPTY_ROWS,
|
||||
Options::SHOULD_USE_1904_DATES,
|
||||
];
|
||||
}
|
||||
|
||||
@ -31,5 +32,6 @@ class OptionsManager extends OptionsManagerAbstract
|
||||
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir());
|
||||
$this->setOption(Options::SHOULD_FORMAT_DATES, false);
|
||||
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
|
||||
$this->setOption(Options::SHOULD_USE_1904_DATES, false);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Box\Spout\Reader\XLSX\Manager;
|
||||
|
||||
use Box\Spout\Reader\Common\Entity\Options;
|
||||
use Box\Spout\Reader\Common\XMLProcessor;
|
||||
use Box\Spout\Reader\XLSX\Creator\EntityFactory;
|
||||
use Box\Spout\Reader\XLSX\Sheet;
|
||||
|
||||
@ -16,12 +18,14 @@ class SheetManager
|
||||
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
||||
|
||||
/** Definition of XML node names used to parse data */
|
||||
const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
|
||||
const XML_NODE_WORKBOOK_VIEW = 'workbookView';
|
||||
const XML_NODE_SHEET = 'sheet';
|
||||
const XML_NODE_SHEETS = 'sheets';
|
||||
const XML_NODE_RELATIONSHIP = 'Relationship';
|
||||
|
||||
/** Definition of XML attributes used to parse data */
|
||||
const XML_ATTRIBUTE_DATE_1904 = 'date1904';
|
||||
const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
|
||||
const XML_ATTRIBUTE_R_ID = 'r:id';
|
||||
const XML_ATTRIBUTE_NAME = 'name';
|
||||
@ -46,6 +50,15 @@ class SheetManager
|
||||
/** @var \Box\Spout\Common\Helper\Escaper\XLSX Used to unescape XML data */
|
||||
protected $escaper;
|
||||
|
||||
/** @var array List of sheets */
|
||||
protected $sheets;
|
||||
|
||||
/** @var int Index of the sheet currently read */
|
||||
protected $currentSheetIndex;
|
||||
|
||||
/** @var int Index of the active sheet (0 by default) */
|
||||
protected $activeSheetIndex;
|
||||
|
||||
/**
|
||||
* @param string $filePath Path of the XLSX file being read
|
||||
* @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
|
||||
@ -71,32 +84,70 @@ class SheetManager
|
||||
*/
|
||||
public function getSheets()
|
||||
{
|
||||
$sheets = [];
|
||||
$sheetIndex = 0;
|
||||
$activeSheetIndex = 0; // By default, the first sheet is active
|
||||
$this->sheets = [];
|
||||
$this->currentSheetIndex = 0;
|
||||
$this->activeSheetIndex = 0; // By default, the first sheet is active
|
||||
|
||||
$xmlReader = $this->entityFactory->createXMLReader();
|
||||
$xmlProcessor = $this->entityFactory->createXMLProcessor($xmlReader);
|
||||
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_WORKBOOK_PROPERTIES, XMLProcessor::NODE_TYPE_START, [$this, 'processWorkbookPropertiesStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_WORKBOOK_VIEW, XMLProcessor::NODE_TYPE_START, [$this, 'processWorkbookViewStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_SHEET, XMLProcessor::NODE_TYPE_START, [$this, 'processSheetStartingNode']);
|
||||
$xmlProcessor->registerCallback(self::XML_NODE_SHEETS, XMLProcessor::NODE_TYPE_END, [$this, 'processSheetsEndingNode']);
|
||||
|
||||
if ($xmlReader->openFileInZip($this->filePath, self::WORKBOOK_XML_FILE_PATH)) {
|
||||
while ($xmlReader->read()) {
|
||||
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_WORKBOOK_VIEW)) {
|
||||
// The "workbookView" node is located before "sheet" nodes, ensuring that
|
||||
// the active sheet is known before parsing sheets data.
|
||||
$activeSheetIndex = (int) $xmlReader->getAttribute(self::XML_ATTRIBUTE_ACTIVE_TAB);
|
||||
} elseif ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_SHEET)) {
|
||||
$isSheetActive = ($sheetIndex === $activeSheetIndex);
|
||||
$sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $sheetIndex, $isSheetActive);
|
||||
$sheetIndex++;
|
||||
} elseif ($xmlReader->isPositionedOnEndingNode(self::XML_NODE_SHEETS)) {
|
||||
// stop reading once all sheets have been read
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$xmlProcessor->readUntilStopped();
|
||||
$xmlReader->close();
|
||||
}
|
||||
|
||||
return $sheets;
|
||||
return $this->sheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookPr>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processWorkbookPropertiesStartingNode($xmlReader)
|
||||
{
|
||||
$shouldUse1904Dates = (bool) $xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904);
|
||||
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<workbookView>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processWorkbookViewStartingNode($xmlReader)
|
||||
{
|
||||
// The "workbookView" node is located before "sheet" nodes, ensuring that
|
||||
// the active sheet is known before parsing sheets data.
|
||||
$this->activeSheetIndex = (int) $xmlReader->getAttribute(self::XML_ATTRIBUTE_ACTIVE_TAB);
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object, positioned on a "<sheet>" starting node
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processSheetStartingNode($xmlReader)
|
||||
{
|
||||
$isSheetActive = ($this->currentSheetIndex === $this->activeSheetIndex);
|
||||
$this->sheets[] = $this->getSheetFromSheetXMLNode($xmlReader, $this->currentSheetIndex, $isSheetActive);
|
||||
$this->currentSheetIndex++;
|
||||
|
||||
return XMLProcessor::PROCESSING_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int A return code that indicates what action should the processor take next
|
||||
*/
|
||||
protected function processSheetsEndingNode()
|
||||
{
|
||||
return XMLProcessor::PROCESSING_STOP;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,29 +15,51 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
public function dataProviderForTestExcelDate()
|
||||
{
|
||||
return [
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 42429, '2016-02-29 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, '146098', '2299-12-31 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, -700, null],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0, '1900-01-01 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.25, '1900-01-01 06:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.5, '1900-01-01 12:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.75, '1900-01-01 18:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.99999, '1900-01-01 23:59:59'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 1, '1900-01-01 00:00:00'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 59.999988425926, '1900-02-28 23:59:59'],
|
||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 60.458333333333, '1900-02-28 11:00:00'],
|
||||
// use 1904 dates, node value, expected date as string
|
||||
|
||||
// 1900 calendar
|
||||
[false, 3687.4207639, '1910-02-03 10:05:54'],
|
||||
[false, 2.5000000, '1900-01-01 12:00:00'],
|
||||
[false, 2958465.9999884, '9999-12-31 23:59:59'],
|
||||
[false, 2958465.9999885, null],
|
||||
[false, -2337.999989, '1893-08-05 00:00:01'],
|
||||
[false, -693593, '0001-01-01 00:00:00'],
|
||||
[false, -693593.0000001, null],
|
||||
[false, 0, '1899-12-30 00:00:00'],
|
||||
[false, 0.25, '1899-12-30 06:00:00'],
|
||||
[false, 0.5, '1899-12-30 12:00:00'],
|
||||
[false, 0.75, '1899-12-30 18:00:00'],
|
||||
[false, 0.99999, '1899-12-30 23:59:59'],
|
||||
[false, 1, '1899-12-31 00:00:00'],
|
||||
[false, '3687.4207639', '1910-02-03 10:05:54'],
|
||||
|
||||
// 1904 calendar
|
||||
[true, 2225.4207639, '1910-02-03 10:05:54'],
|
||||
[true, 2.5000000, '1904-01-03 12:00:00'],
|
||||
[true, 2957003.9999884, '9999-12-31 23:59:59'],
|
||||
[true, 2957003.9999885, null],
|
||||
[true, -3799.999989, '1893-08-05 00:00:01'],
|
||||
[true, -695055, '0001-01-01 00:00:00'],
|
||||
[true, -695055.0000001, null],
|
||||
[true, 0, '1904-01-01 00:00:00'],
|
||||
[true, 0.25, '1904-01-01 06:00:00'],
|
||||
[true, 0.5, '1904-01-01 12:00:00'],
|
||||
[true, 0.75, '1904-01-01 18:00:00'],
|
||||
[true, 0.99999, '1904-01-01 23:59:59'],
|
||||
[true, 1, '1904-01-02 00:00:00'],
|
||||
[true, '2225.4207639', '1910-02-03 10:05:54'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderForTestExcelDate
|
||||
*
|
||||
* @param string $cellType
|
||||
* @param bool $shouldUse1904Dates
|
||||
* @param int|float|string $nodeValue
|
||||
* @param string|null $expectedDateAsString
|
||||
* @return void
|
||||
*/
|
||||
public function testExcelDate($cellType, $nodeValue, $expectedDateAsString)
|
||||
public function testExcelDate($shouldUse1904Dates, $nodeValue, $expectedDateAsString)
|
||||
{
|
||||
$nodeListMock = $this->getMockBuilder('DOMNodeList')->disableOriginalConstructor()->getMock();
|
||||
|
||||
@ -53,7 +75,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->expects($this->atLeastOnce())
|
||||
->method('getAttribute')
|
||||
->will($this->returnValueMap([
|
||||
[CellValueFormatter::XML_ATTRIBUTE_TYPE, $cellType],
|
||||
[CellValueFormatter::XML_ATTRIBUTE_TYPE, CellValueFormatter::CELL_TYPE_NUMERIC],
|
||||
[CellValueFormatter::XML_ATTRIBUTE_STYLE_ID, 123],
|
||||
]));
|
||||
|
||||
@ -72,7 +94,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->with(123)
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$formatter = new CellValueFormatter(null, $styleManagerMock, false, new Escaper\XLSX());
|
||||
$formatter = new CellValueFormatter(null, $styleManagerMock, false, $shouldUse1904Dates, new Escaper\XLSX());
|
||||
$result = $formatter->extractAndFormatNodeValue($nodeMock);
|
||||
|
||||
if ($expectedDateAsString === null) {
|
||||
@ -126,7 +148,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->method('shouldFormatNumericValueAsDate')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$formatter = new CellValueFormatter(null, $styleManagerMock, false, new Escaper\XLSX());
|
||||
$formatter = new CellValueFormatter(null, $styleManagerMock, false, false, new Escaper\XLSX());
|
||||
$formattedValue = \ReflectionHelper::callMethodOnObject($formatter, 'formatNumericCellValue', $value, 0);
|
||||
|
||||
$this->assertEquals($expectedFormattedValue, $formattedValue);
|
||||
@ -169,7 +191,7 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
->with(CellValueFormatter::XML_NODE_INLINE_STRING_VALUE)
|
||||
->will($this->returnValue($nodeListMock));
|
||||
|
||||
$formatter = new CellValueFormatter(null, null, false, new Escaper\XLSX());
|
||||
$formatter = new CellValueFormatter(null, null, false, false, new Escaper\XLSX());
|
||||
$formattedValue = \ReflectionHelper::callMethodOnObject($formatter, 'formatInlineStringCellValue', $nodeMock);
|
||||
|
||||
$this->assertEquals($expectedFormattedValue, $formattedValue);
|
||||
|
@ -231,9 +231,34 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-01 22:23:00'),
|
||||
],
|
||||
[
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 23:59:59'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-27 23:59:59'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-03-01 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 11:00:00'), // 1900-02-29 should be converted to 1900-02-28
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 11:00:00'),
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testReadShouldSupportDifferentDatesAsNumericTimestampWith1904Calendar()
|
||||
{
|
||||
// make sure dates are always created with the same timezone
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
$allRows = $this->getAllRowsForFile('sheet_with_different_numeric_value_dates_1904_calendar.xlsx');
|
||||
|
||||
$expectedRows = [
|
||||
[
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2019-09-02 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2019-09-03 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '2019-09-02 22:23:00'),
|
||||
],
|
||||
[
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1904-02-29 23:59:59'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1904-03-02 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1904-03-01 11:00:00'),
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
@ -251,11 +276,11 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$expectedRows = [
|
||||
[
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-01-01 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-01-01 11:29:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-01-01 23:29:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-01-01 01:42:25'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-01-01 13:42:25'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1899-12-30 00:00:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1899-12-30 11:29:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1899-12-30 23:29:00'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1899-12-30 01:42:25'),
|
||||
\DateTime::createFromFormat('Y-m-d H:i:s', '1899-12-30 13:42:25'),
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user