Add support for cells formatted as time (#224)
Cells formatted as "time" have values between 0 and 1. These values used to be considered as invalid. Note: this uses what was started in #202
This commit is contained in:
parent
bb20d2e6bb
commit
b4724906c4
@ -29,6 +29,8 @@ class CellValueFormatter
|
|||||||
|
|
||||||
/** Constants used for date formatting */
|
/** Constants used for date formatting */
|
||||||
const NUM_SECONDS_IN_ONE_DAY = 86400;
|
const NUM_SECONDS_IN_ONE_DAY = 86400;
|
||||||
|
const NUM_SECONDS_IN_ONE_HOUR = 3600;
|
||||||
|
const NUM_SECONDS_IN_ONE_MINUTE = 60;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
|
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
|
||||||
@ -176,6 +178,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns a cell's PHP Date value, associated to the given timestamp.
|
* 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 January 1st, 1900.
|
||||||
|
* NOTE: The timestamp can also represent a time, if it is a value between 0 and 1.
|
||||||
*
|
*
|
||||||
* @param float $nodeValue
|
* @param float $nodeValue
|
||||||
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
|
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
|
||||||
@ -187,22 +190,59 @@ class CellValueFormatter
|
|||||||
--$nodeValue;
|
--$nodeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value 1.0 represents 1900-01-01. Numbers below 1.0 are not valid Excel dates.
|
if ($nodeValue >= 1) {
|
||||||
if ($nodeValue < 1.0) {
|
// Values greater than 1 represent "dates". The value 1.0 representing the "base" date: 1900-01-01.
|
||||||
|
return $this->formatExcelTimestampValueAsDateValue($nodeValue);
|
||||||
|
} else if ($nodeValue >= 0) {
|
||||||
|
// Values between 0 and 1 represent "times".
|
||||||
|
return $this->formatExcelTimestampValueAsTimeValue($nodeValue);
|
||||||
|
} else {
|
||||||
|
// invalid date
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*
|
||||||
|
* @param float $nodeValue
|
||||||
|
* @return \DateTime The value associated with the cell
|
||||||
|
*/
|
||||||
|
protected function formatExcelTimestampValueAsTimeValue($nodeValue)
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
|
||||||
|
// using the base Excel date (Jan 1st, 1900) - not relevant here
|
||||||
|
$dateObj = new \DateTime('1900-01-01');
|
||||||
|
$dateObj->setTime($hours, $minutes, $seconds);
|
||||||
|
|
||||||
|
return $dateObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
|
||||||
|
*/
|
||||||
|
protected function formatExcelTimestampValueAsDateValue($nodeValue)
|
||||||
|
{
|
||||||
// Do not use any unix timestamps for calculation to prevent
|
// Do not use any unix timestamps for calculation to prevent
|
||||||
// issues with numbers exceeding 2^31.
|
// issues with numbers exceeding 2^31.
|
||||||
$secondsRemainder = fmod($nodeValue, 1) * self::NUM_SECONDS_IN_ONE_DAY;
|
$secondsRemainder = fmod($nodeValue, 1) * self::NUM_SECONDS_IN_ONE_DAY;
|
||||||
$secondsRemainder = round($secondsRemainder, 0);
|
$secondsRemainder = round($secondsRemainder, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$cellValue = \DateTime::createFromFormat('|Y-m-d', '1899-12-31');
|
$dateObj = \DateTime::createFromFormat('|Y-m-d', '1899-12-31');
|
||||||
$cellValue->modify('+' . intval($nodeValue) . 'days');
|
$dateObj->modify('+' . intval($nodeValue) . 'days');
|
||||||
$cellValue->modify('+' . $secondsRemainder . 'seconds');
|
$dateObj->modify('+' . $secondsRemainder . 'seconds');
|
||||||
|
|
||||||
return $cellValue;
|
return $dateObj;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,11 @@ class CellValueFormatterTest extends \PHPUnit_Framework_TestCase
|
|||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 42429, '2016-02-29 00:00:00'],
|
[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, '146098', '2299-12-31 00:00:00'],
|
||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, -700, null],
|
[CellValueFormatter::CELL_TYPE_NUMERIC, -700, null],
|
||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0, null],
|
[CellValueFormatter::CELL_TYPE_NUMERIC, 0, '1900-01-01 00:00:00'],
|
||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 0.5, null],
|
[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, 1, '1900-01-01 00:00:00'],
|
||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 59.999988425926, '1900-02-28 23:59:59'],
|
[CellValueFormatter::CELL_TYPE_NUMERIC, 59.999988425926, '1900-02-28 23:59:59'],
|
||||||
[CellValueFormatter::CELL_TYPE_NUMERIC, 60.458333333333, '1900-02-28 11:00:00'],
|
[CellValueFormatter::CELL_TYPE_NUMERIC, 60.458333333333, '1900-02-28 11:00:00'],
|
||||||
|
@ -181,6 +181,28 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expectedRows, $allRows);
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testReadShouldSupportDifferentTimesAsNumericTimestamp()
|
||||||
|
{
|
||||||
|
// make sure dates are always created with the same timezone
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
|
$allRows = $this->getAllRowsForFile('sheet_with_different_numeric_value_times.xlsx');
|
||||||
|
|
||||||
|
$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'),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user