Skip to content

Permit Date/Time Entered on Spreadsheet to be Calculated as Float #3121

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 4 commits into from
Oct 19, 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
13 changes: 13 additions & 0 deletions docs/topics/calculation-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ and false is failure (e.g. an invalid DateTimeZone value was passed.)
These functions support a timezone as an optional second parameter.
This applies a specific timezone to that function call without affecting the default PhpSpreadsheet Timezone.

### Calculating Value of Date/Time Read From Spreadsheet

Nothing special needs to be done to interpret Date/Time values entered directly into a spreadsheet. They will have been stored as numbers with an appropriate number format set for the cell. However, depending on their value, they may have been stored as either integer or float values. If that is a problem, you can force `getCalculatedValue` to return float rather than int depending on the number format used for the cell.

```php
// All fields with Date, Time, or DateTime styles returned as float.
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_DATE_TIME_FLOAT);
// All fields with Time or DateTime styles returned as float.
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_TIME_FLOAT);
// Default - fields with Date, Time, or DateTime styles returned as they had been stored.
\PhpOffice\PhpSpreadsheet\Cell\Cell::setCalculateDateTimeType(\PhpOffice\PhpSpreadsheet\Cell\Cell::CALCULATE_DATE_TIME_ASIS);
```

## Function Reference

### Database Functions
Expand Down
36 changes: 0 additions & 36 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -684,26 +684,6 @@ parameters:
message: "#^Variable \\$value on left side of \\?\\? always exists and is not nullable\\.$#"
count: 1
path: src/PhpSpreadsheet/Calculation/TextData/Text.php
-
message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:getFormulaAttributes\\(\\) has no return type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Cell/Cell.php
-
message: "#^Parameter \\#2 \\$format of static method PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\NumberFormat\\:\\:toFormattedString\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Cell/Cell.php
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:\\$formulaAttributes has no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Cell/Cell.php
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Cell\\:\\:\\$parent \\(PhpOffice\\\\PhpSpreadsheet\\\\Collection\\\\Cells\\) in isset\\(\\) is not nullable\\.$#"
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 Expand Up @@ -1078,22 +1058,6 @@ parameters:
message: "#^Strict comparison using \\=\\=\\= between int and null will always evaluate to false\\.$#"
count: 1
path: src/PhpSpreadsheet/Settings.php
-
message: "#^Parameter \\#1 \\$excelFormatCode of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:isDateTimeFormatCode\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/PhpSpreadsheet/Shared/Date.php
-
message: "#^Parameter \\#1 \\$string of function substr expects string, int given\\.$#"
count: 2
path: src/PhpSpreadsheet/Shared/Date.php
-
message: "#^Parameter \\#1 \\$unixTimestamp of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:timestampToExcel\\(\\) expects int, float\\|int\\|string given\\.$#"
count: 1
path: src/PhpSpreadsheet/Shared/Date.php
-
message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:\\$possibleDateFormatCharacters has no type specified\\.$#"
count: 1
path: src/PhpSpreadsheet/Shared/Date.php
-
message: "#^Cannot access offset 1 on array\\|false\\.$#"
count: 1
Expand Down
2 changes: 1 addition & 1 deletion samples/Autofilter/10_Autofilter_selection_1.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true);
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5);
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE);
$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14);
$spreadsheet->getActiveSheet()->freezePane('A2');
Expand Down
2 changes: 1 addition & 1 deletion samples/Autofilter/10_Autofilter_selection_2.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true);
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5);
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE);
$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14);
$spreadsheet->getActiveSheet()->freezePane('A2');
Expand Down
2 changes: 1 addition & 1 deletion samples/Autofilter/10_Autofilter_selection_display.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
$spreadsheet->getActiveSheet()->getStyle('A1:F1')->getAlignment()->setWrapText(true);
$spreadsheet->getActiveSheet()->getColumnDimension('C')->setWidth(12.5);
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(10.5);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
$spreadsheet->getActiveSheet()->getStyle('D2:D' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);
$spreadsheet->getActiveSheet()->getStyle('E2:F' . $row)->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_CURRENCY_USD_SIMPLE);
$spreadsheet->getActiveSheet()->getColumnDimension('F')->setWidth(14);
$spreadsheet->getActiveSheet()->freezePane('A2');
Expand Down
2 changes: 1 addition & 1 deletion samples/Basic/02_Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
$spreadsheet->getActiveSheet()
->getStyle('C9')
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD2);
->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDD);

$spreadsheet->getActiveSheet()
->setCellValue('A10', 'Date/Time')
Expand Down
114 changes: 96 additions & 18 deletions src/PhpSpreadsheet/Cell/Cell.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Throwable;

class Cell
{
Expand Down Expand Up @@ -52,7 +51,7 @@ class Cell
/**
* The collection of cells that this cell belongs to (i.e. The Cell Collection for the parent Worksheet).
*
* @var Cells
* @var ?Cells
*/
private $parent;

Expand All @@ -65,6 +64,8 @@ class Cell

/**
* Attributes of the formula.
*
* @var mixed
*/
private $formulaAttributes;

Expand All @@ -75,14 +76,17 @@ class Cell
*/
public function updateInCollection(): self
{
$this->parent->update($this);
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot update when cell is not bound to a worksheet');
}
$parent->update($this);

return $this;
}

public function detach(): void
{
// @phpstan-ignore-next-line
$this->parent = null;
}

Expand Down Expand Up @@ -122,7 +126,12 @@ public function __construct($value, ?string $dataType, Worksheet $worksheet)
*/
public function getColumn()
{
return $this->parent->getCurrentColumn();
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot get column when cell is not bound to a worksheet');
}

return $parent->getCurrentColumn();
}

/**
Expand All @@ -132,7 +141,12 @@ public function getColumn()
*/
public function getRow()
{
return $this->parent->getCurrentRow();
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot get row when cell is not bound to a worksheet');
}

return $parent->getCurrentRow();
}

/**
Expand All @@ -142,9 +156,10 @@ public function getRow()
*/
public function getCoordinate()
{
try {
$coordinate = $this->parent->getCurrentCoordinate();
} catch (Throwable $e) {
$parent = $this->parent;
if ($parent !== null) {
$coordinate = $parent->getCurrentCoordinate();
} else {
$coordinate = null;
}
if ($coordinate === null) {
Expand All @@ -171,8 +186,7 @@ public function getFormattedValue(): string
{
return (string) NumberFormat::toFormattedString(
$this->getCalculatedValue(),
$this->getStyle()
->getNumberFormat()->getFormatCode()
(string) $this->getStyle()->getNumberFormat()->getFormatCode()
);
}

Expand Down Expand Up @@ -251,8 +265,6 @@ public function setValueExplicit($value, $dataType)
break;
default:
throw new Exception('Invalid datatype: ' . $dataType);

break;
}

// set the datatype
Expand All @@ -261,6 +273,56 @@ public function setValueExplicit($value, $dataType)
return $this->updateInCollection();
}

public const CALCULATE_DATE_TIME_ASIS = 0;
public const CALCULATE_DATE_TIME_FLOAT = 1;
public const CALCULATE_TIME_FLOAT = 2;

/** @var int */
private static $calculateDateTimeType = self::CALCULATE_DATE_TIME_ASIS;

public static function getCalculateDateTimeType(): int
{
return self::$calculateDateTimeType;
}

public static function setCalculateDateTimeType(int $calculateDateTimeType): void
{
switch ($calculateDateTimeType) {
case self::CALCULATE_DATE_TIME_ASIS:
case self::CALCULATE_DATE_TIME_FLOAT:
case self::CALCULATE_TIME_FLOAT:
self::$calculateDateTimeType = $calculateDateTimeType;

break;
default:
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception("Invalid value $calculateDateTimeType for calculated date time type");
}
}

/**
* Convert date, time, or datetime from int to float if desired.
*
* @param mixed $result
*
* @return mixed
*/
private function convertDateTimeInt($result)
{
if (is_int($result)) {
if (self::$calculateDateTimeType === self::CALCULATE_TIME_FLOAT) {
if (SharedDate::isDateTime($this, $result, false)) {
$result = (float) $result;
}
} elseif (self::$calculateDateTimeType === self::CALCULATE_DATE_TIME_FLOAT) {
if (SharedDate::isDateTime($this, $result, true)) {
$result = (float) $result;
}
}
}

return $result;
}

/**
* Get calculated cell value.
*
Expand All @@ -277,6 +339,7 @@ public function getCalculatedValue(bool $resetLog = true)
$result = Calculation::getInstance(
$this->getWorksheet()->getParent()
)->calculateCellValue($this, $resetLog);
$result = $this->convertDateTimeInt($result);
$this->getWorksheet()->setSelectedCells($selected);
$this->getWorksheet()->getParent()->setActiveSheetIndex($index);
// We don't yet handle array returns
Expand Down Expand Up @@ -306,7 +369,7 @@ public function getCalculatedValue(bool $resetLog = true)
return $this->value->getPlainText();
}

return $this->value;
return $this->convertDateTimeInt($this->value);
}

/**
Expand Down Expand Up @@ -458,7 +521,7 @@ public function setHyperlink(?Hyperlink $hyperlink = null): self
/**
* Get cell collection.
*
* @return Cells
* @return ?Cells
*/
public function getParent()
{
Expand All @@ -470,9 +533,10 @@ public function getParent()
*/
public function getWorksheet(): Worksheet
{
try {
$worksheet = $this->parent->getParent();
} catch (Throwable $e) {
$parent = $this->parent;
if ($parent !== null) {
$worksheet = $parent->getParent();
} else {
$worksheet = null;
}

Expand All @@ -483,6 +547,18 @@ public function getWorksheet(): Worksheet
return $worksheet;
}

public function getWorksheetOrNull(): ?Worksheet
{
$parent = $this->parent;
if ($parent !== null) {
$worksheet = $parent->getParent();
} else {
$worksheet = null;
}

return $worksheet;
}

/**
* Is this cell in a merge range.
*/
Expand Down Expand Up @@ -666,6 +742,8 @@ public function setFormulaAttributes($attributes): self

/**
* Get the formula attributes.
*
* @return mixed
*/
public function getFormulaAttributes()
{
Expand Down
Loading