diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6390e0fc21..ee3b762daf 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1270,11 +1270,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php - - - message: "#^Parameter \\#1 \\$number of function base_convert expects string, int\\<0, 9007199254740991\\> given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Calculation/MathTrig/Base.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\MathTrig\\\\Helpers\\:\\:validateNumericNullBool\\(\\) should return float\\|int but returns float\\|int\\|string\\.$#" count: 1 @@ -2490,36 +2485,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/DefinedName.php - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:\\$created \\(int\\) does not accept int\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:\\$modified \\(int\\) does not accept int\\|false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:identifyPropertyType\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:identifyPropertyType\\(\\) has parameter \\$propertyValue with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:convertProperty\\(\\) has no return typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:convertProperty\\(\\) has parameter \\$propertyValue with no typehint specified\\.$#" - count: 1 - path: src/PhpSpreadsheet/Document/Properties.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\DocumentGenerator\\:\\:getPhpSpreadsheetFunctionText\\(\\) has parameter \\$functionCall with no typehint specified\\.$#" count: 1 @@ -3180,16 +3145,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Ods/Properties.php - - - message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreated\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Reader/Ods/Properties.php - - - - message: "#^Parameter \\#1 \\$timestamp of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setModified\\(\\) expects int\\|string\\|null, int\\|false given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Ods/Properties.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\Properties\\:\\:setMetaProperties\\(\\) has parameter \\$namespacesMeta with no typehint specified\\.$#" count: 1 @@ -3405,61 +3360,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Reader/Xls.php - - - message: "#^Parameter \\#1 \\$codePage of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:numberToName\\(\\) expects int, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$title of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setTitle\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$subject of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setSubject\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$creator of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCreator\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$keywords of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setKeywords\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$description of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setDescription\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$modifier of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setLastModifiedBy\\(\\) expects string, int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$codePage of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\CodePage\\:\\:numberToName\\(\\) expects int, bool\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$category of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCategory\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$manager of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setManager\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - - - message: "#^Parameter \\#1 \\$company of method PhpOffice\\\\PhpSpreadsheet\\\\Document\\\\Properties\\:\\:setCompany\\(\\) expects string, bool\\|int\\|string\\|null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Reader/Xls.php - - message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, int\\|string\\|null given\\.$#" count: 1 @@ -5250,16 +5150,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/OLE.php - - - message: "#^Parameter \\#7 \\$time_1st of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/OLE.php - - - - message: "#^Parameter \\#8 \\$time_2nd of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects int, null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/OLE.php - - message: "#^Parameter \\#9 \\$data of class PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS constructor expects string, null given\\.$#" count: 1 @@ -5330,16 +5220,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php - - - message: "#^Parameter \\#7 \\$time_1st of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php - - - - message: "#^Parameter \\#8 \\$time_2nd of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/OLE/PPS/File.php - - message: "#^Parameter \\#1 \\$No of method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\OLE\\\\PPS\\:\\:__construct\\(\\) expects int, null given\\.$#" count: 1 @@ -5485,11 +5365,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Shared/StringHelper.php - - - message: "#^Else branch is unreachable because previous condition is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Shared/TimeZone.php - - message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Trend\\\\BestFit\\:\\:\\$goodnessOfFit has no typehint specified\\.$#" count: 1 @@ -8470,31 +8345,11 @@ parameters: count: 1 path: tests/PhpSpreadsheetTests/Reader/Xml/XmlTest.php - - - message: "#^Parameter \\#1 \\$timeZone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:setDefaultTimezone\\(\\) expects DateTimeZone\\|string, DateTimeZone\\|null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Shared/DateTest.php - - message: "#^Parameter \\#1 \\$pValue of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:setCurrencyCode\\(\\) expects string, null given\\.$#" count: 1 path: tests/PhpSpreadsheetTests/Shared/StringHelperTest.php - - - message: "#^Parameter \\#1 \\$timeZone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\Date\\:\\:setDefaultTimezone\\(\\) expects DateTimeZone\\|string, DateTimeZone\\|null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php - - - - message: "#^Cannot call method getTimestamp\\(\\) on DateTime\\|false\\.$#" - count: 2 - path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php - - - - message: "#^Parameter \\#1 \\$timezone of static method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\TimeZone\\:\\:getTimeZoneAdjustment\\(\\) expects string, null given\\.$#" - count: 1 - path: tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php - - message: "#^Method PhpOffice\\\\PhpSpreadsheetTests\\\\SpreadsheetTest\\:\\:testGetSheetByName\\(\\) has parameter \\$index with no typehint specified\\.$#" count: 1 diff --git a/samples/Basic/01_Simple.php b/samples/Basic/01_Simple.php index 69309794c0..1ab182e16e 100644 --- a/samples/Basic/01_Simple.php +++ b/samples/Basic/01_Simple.php @@ -61,4 +61,4 @@ ->setTitle('Simple'); // Save -$helper->write($spreadsheet, __FILE__); +$helper->write($spreadsheet, __FILE__, ['Xlsx', 'Xls', 'Ods']); diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index ec85df69af..981e17e212 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4562,7 +4562,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null) } else { $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack); } - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) { + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) { $cellRef = null; if (isset($matches[8])) { if ($pCell === null) { @@ -4637,7 +4637,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null) } // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token, $matches)) { + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) { if ($pCellParent) { $pCell->attach($pCellParent); } @@ -4727,7 +4727,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null) } } else { // if the token is a number, boolean, string or an Excel error, push it onto the stack - if (isset(self::$excelConstants[strtoupper($token)])) { + if (isset(self::$excelConstants[strtoupper($token ?? '')])) { $excelConstant = strtoupper($token); $stack->push('Constant Value', self::$excelConstants[$excelConstant]); if (isset($storeKey)) { diff --git a/src/PhpSpreadsheet/Calculation/MathTrig/Base.php b/src/PhpSpreadsheet/Calculation/MathTrig/Base.php index 6f44a12299..c7d6073159 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig/Base.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig/Base.php @@ -24,7 +24,7 @@ class Base public static function evaluate($number, $radix, $minLength = null) { try { - $number = (int) Helpers::validateNumericNullBool($number); + $number = (float) floor(Helpers::validateNumericNullBool($number)); $radix = (int) Helpers::validateNumericNullBool($radix); } catch (Exception $e) { return $e->getMessage(); @@ -36,7 +36,7 @@ public static function evaluate($number, $radix, $minLength = null) return Functions::NAN(); // Numeric range constraints } - $outcome = strtoupper((string) base_convert($number, 10, $radix)); + $outcome = strtoupper((string) base_convert("$number", 10, $radix)); if ($minLength !== null) { $outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding } diff --git a/src/PhpSpreadsheet/Calculation/Statistical.php b/src/PhpSpreadsheet/Calculation/Statistical.php index 0f5355b967..651ee8058e 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical.php +++ b/src/PhpSpreadsheet/Calculation/Statistical.php @@ -1402,7 +1402,7 @@ public static function PERCENTRANK($valueSet, $value, $significance = 3) * @param int $numObjs Number of different objects * @param int $numInSet Number of objects in each permutation * - * @return int|string Number of permutations, or a string containing an error + * @return float|int|string Number of permutations, or a string containing an error */ public static function PERMUT($numObjs, $numInSet) { diff --git a/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php b/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php index ab2e14ddab..83c0458d59 100644 --- a/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php +++ b/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php @@ -5,6 +5,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; +use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat; class Permutations { @@ -20,7 +21,7 @@ class Permutations * @param mixed $numObjs Integer number of different objects * @param mixed $numInSet Integer number of objects in each permutation * - * @return int|string Number of permutations, or a string containing an error + * @return float|int|string Number of permutations, or a string containing an error */ public static function PERMUT($numObjs, $numInSet) { @@ -38,7 +39,9 @@ public static function PERMUT($numObjs, $numInSet) return Functions::NAN(); } - return (int) round(MathTrig\Fact::evaluate($numObjs) / MathTrig\Fact::evaluate($numObjs - $numInSet)); + $result = round(MathTrig\Fact::evaluate($numObjs) / MathTrig\Fact::evaluate($numObjs - $numInSet)); + + return IntOrFloat::evaluate($result); } /** @@ -50,7 +53,7 @@ public static function PERMUT($numObjs, $numInSet) * @param mixed $numObjs Integer number of different objects * @param mixed $numInSet Integer number of objects in each permutation * - * @return int|string Number of permutations, or a string containing an error + * @return float|int|string Number of permutations, or a string containing an error */ public static function PERMUTATIONA($numObjs, $numInSet) { @@ -68,6 +71,8 @@ public static function PERMUTATIONA($numObjs, $numInSet) return Functions::NAN(); } - return (int) ($numObjs ** $numInSet); + $result = $numObjs ** $numInSet; + + return IntOrFloat::evaluate($result); } } diff --git a/src/PhpSpreadsheet/Document/Properties.php b/src/PhpSpreadsheet/Document/Properties.php index c099bccc4d..1030d98cc9 100644 --- a/src/PhpSpreadsheet/Document/Properties.php +++ b/src/PhpSpreadsheet/Document/Properties.php @@ -2,6 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Document; +use DateTime; +use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat; + class Properties { /** constants */ @@ -37,14 +40,14 @@ class Properties /** * Created. * - * @var int + * @var float|int */ private $created; /** * Modified. * - * @var int + * @var float|int */ private $modified; @@ -95,7 +98,7 @@ class Properties * * @var string */ - private $company = 'Microsoft Corporation'; + private $company = ''; /** * Custom Properties. @@ -111,8 +114,8 @@ public function __construct() { // Initialise values $this->lastModifiedBy = $this->creator; - $this->created = time(); - $this->modified = time(); + $this->created = self::intOrFloatTimestamp(null); + $this->modified = self::intOrFloatTimestamp(null); } /** @@ -155,10 +158,33 @@ public function setLastModifiedBy(string $modifier): self return $this; } + /** + * @param null|float|int|string $timestamp + * + * @return float|int + */ + private static function intOrFloatTimestamp($timestamp) + { + if ($timestamp === null) { + $timestamp = (float) (new DateTime())->format('U'); + } elseif (is_string($timestamp)) { + if (is_numeric($timestamp)) { + $timestamp = (float) $timestamp; + } else { + $timestamp = preg_replace('/[.][0-9]*$/', '', $timestamp) ?? ''; + $timestamp = (float) (new DateTime($timestamp))->format('U'); + } + } + + return IntOrFloat::evaluate($timestamp); + } + /** * Get Created. + * + * @return float|int */ - public function getCreated(): int + public function getCreated() { return $this->created; } @@ -166,31 +192,23 @@ public function getCreated(): int /** * Set Created. * - * @param null|int|string $timestamp + * @param null|float|int|string $timestamp * * @return $this */ public function setCreated($timestamp): self { - if ($timestamp === null) { - $timestamp = time(); - } elseif (is_string($timestamp)) { - if (is_numeric($timestamp)) { - $timestamp = (int) $timestamp; - } else { - $timestamp = strtotime($timestamp); - } - } - - $this->created = $timestamp; + $this->created = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Modified. + * + * @return float|int */ - public function getModified(): int + public function getModified() { return $this->modified; } @@ -198,23 +216,13 @@ public function getModified(): int /** * Set Modified. * - * @param null|int|string $timestamp + * @param null|float|int|string $timestamp * * @return $this */ public function setModified($timestamp): self { - if ($timestamp === null) { - $timestamp = time(); - } elseif (is_string($timestamp)) { - if (is_numeric($timestamp)) { - $timestamp = (int) $timestamp; - } else { - $timestamp = strtotime($timestamp); - } - } - - $this->modified = $timestamp; + $this->modified = self::intOrFloatTimestamp($timestamp); return $this; } @@ -387,6 +395,8 @@ public function getCustomPropertyValue(string $propertyName) if (isset($this->customProperties[$propertyName])) { return $this->customProperties[$propertyName]['value']; } + + return null; } /** @@ -399,15 +409,18 @@ public function getCustomPropertyType(string $propertyName) return $this->customProperties[$propertyName]['type'] ?? null; } - private function identifyPropertyType($propertyValue) + /** + * @param mixed $propertyValue + */ + private function identifyPropertyType($propertyValue): string { - if ($propertyValue === null) { - return self::PROPERTY_TYPE_STRING; - } elseif (is_float($propertyValue)) { + if (is_float($propertyValue)) { return self::PROPERTY_TYPE_FLOAT; - } elseif (is_int($propertyValue)) { + } + if (is_int($propertyValue)) { return self::PROPERTY_TYPE_INTEGER; - } elseif (is_bool($propertyValue)) { + } + if (is_bool($propertyValue)) { return self::PROPERTY_TYPE_BOOLEAN; } @@ -433,125 +446,90 @@ public function setCustomProperty(string $propertyName, $propertyValue = '', $pr $propertyType = $this->identifyPropertyType($propertyValue); } - $this->customProperties[$propertyName] = [ - 'value' => $propertyValue, - 'type' => $propertyType, - ]; + if (!is_object($propertyValue)) { + $this->customProperties[$propertyName] = [ + 'value' => self::convertProperty($propertyValue, $propertyType), + 'type' => $propertyType, + ]; + } return $this; } + private const PROPERTY_TYPE_ARRAY = [ + 'i' => self::PROPERTY_TYPE_INTEGER, // Integer + 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer + 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer + 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer + 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer + 'int' => self::PROPERTY_TYPE_INTEGER, // Integer + 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer + 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer + 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer + 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer + 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer + 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number + 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number + 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number + 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal + 's' => self::PROPERTY_TYPE_STRING, // String + 'empty' => self::PROPERTY_TYPE_STRING, // Empty + 'null' => self::PROPERTY_TYPE_STRING, // Null + 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR + 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR + 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String + 'd' => self::PROPERTY_TYPE_DATE, // Date and Time + 'date' => self::PROPERTY_TYPE_DATE, // Date and Time + 'filetime' => self::PROPERTY_TYPE_DATE, // File Time + 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + ]; + + private const SPECIAL_TYPES = [ + 'empty' => '', + 'null' => null, + ]; + /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed */ - public function __clone() + public static function convertProperty($propertyValue, string $propertyType) { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } + return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType); } - public static function convertProperty($propertyValue, string $propertyType) + /** + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed + */ + private static function convertProperty2($propertyValue, string $type) { + $propertyType = self::convertPropertyType($type); switch ($propertyType) { - case 'empty': // Empty - return ''; - case 'null': // Null - return null; - case 'i1': // 1-Byte Signed Integer - case 'i2': // 2-Byte Signed Integer - case 'i4': // 4-Byte Signed Integer - case 'i8': // 8-Byte Signed Integer - case 'int': // Integer - return (int) $propertyValue; - case 'ui1': // 1-Byte Unsigned Integer - case 'ui2': // 2-Byte Unsigned Integer - case 'ui4': // 4-Byte Unsigned Integer - case 'ui8': // 8-Byte Unsigned Integer - case 'uint': // Unsigned Integer - return abs((int) $propertyValue); - case 'r4': // 4-Byte Real Number - case 'r8': // 8-Byte Real Number - case 'decimal': // Decimal + case self::PROPERTY_TYPE_INTEGER: + $intValue = (int) $propertyValue; + + return ($type[0] === 'u') ? abs($intValue) : $intValue; + case self::PROPERTY_TYPE_FLOAT: return (float) $propertyValue; - case 'lpstr': // LPSTR - case 'lpwstr': // LPWSTR - case 'bstr': // Basic String - return $propertyValue; - case 'date': // Date and Time - case 'filetime': // File Time - return strtotime($propertyValue); - case 'bool': // Boolean - return $propertyValue == 'true'; - case 'cy': // Currency - case 'error': // Error Status Code - case 'vector': // Vector - case 'array': // Array - case 'blob': // Binary Blob - case 'oblob': // Binary Blob Object - case 'stream': // Binary Stream - case 'ostream': // Binary Stream Object - case 'storage': // Binary Storage - case 'ostorage': // Binary Storage Object - case 'vstream': // Binary Versioned Stream - case 'clsid': // Class ID - case 'cf': // Clipboard Data + case self::PROPERTY_TYPE_DATE: + return self::intOrFloatTimestamp($propertyValue); + case self::PROPERTY_TYPE_BOOLEAN: + return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true'); + default: // includes string return $propertyValue; } - - return $propertyValue; } public static function convertPropertyType(string $propertyType): string { - switch ($propertyType) { - case 'i1': // 1-Byte Signed Integer - case 'i2': // 2-Byte Signed Integer - case 'i4': // 4-Byte Signed Integer - case 'i8': // 8-Byte Signed Integer - case 'int': // Integer - case 'ui1': // 1-Byte Unsigned Integer - case 'ui2': // 2-Byte Unsigned Integer - case 'ui4': // 4-Byte Unsigned Integer - case 'ui8': // 8-Byte Unsigned Integer - case 'uint': // Unsigned Integer - return self::PROPERTY_TYPE_INTEGER; - case 'r4': // 4-Byte Real Number - case 'r8': // 8-Byte Real Number - case 'decimal': // Decimal - return self::PROPERTY_TYPE_FLOAT; - case 'empty': // Empty - case 'null': // Null - case 'lpstr': // LPSTR - case 'lpwstr': // LPWSTR - case 'bstr': // Basic String - return self::PROPERTY_TYPE_STRING; - case 'date': // Date and Time - case 'filetime': // File Time - return self::PROPERTY_TYPE_DATE; - case 'bool': // Boolean - return self::PROPERTY_TYPE_BOOLEAN; - case 'cy': // Currency - case 'error': // Error Status Code - case 'vector': // Vector - case 'array': // Array - case 'blob': // Binary Blob - case 'oblob': // Binary Blob Object - case 'stream': // Binary Stream - case 'ostream': // Binary Stream Object - case 'storage': // Binary Storage - case 'ostorage': // Binary Storage Object - case 'vstream': // Binary Versioned Stream - case 'clsid': // Class ID - case 'cf': // Clipboard Data - return self::PROPERTY_TYPE_UNKNOWN; - } - - return self::PROPERTY_TYPE_UNKNOWN; + return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; } } diff --git a/src/PhpSpreadsheet/Reader/Ods.php b/src/PhpSpreadsheet/Reader/Ods.php index 1a4d7ca3c1..76e4b8e574 100644 --- a/src/PhpSpreadsheet/Reader/Ods.php +++ b/src/PhpSpreadsheet/Reader/Ods.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use DateTime; -use DateTimeZone; use DOMAttr; use DOMDocument; use DOMElement; @@ -256,9 +255,6 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) { File::assertFile($pFilename); - $timezoneObj = new DateTimeZone('Europe/London'); - $GMT = new DateTimeZone('UTC'); - $zip = new ZipArchive(); if ($zip->open($pFilename) !== true) { throw new Exception("Could not open {$pFilename} for reading! Error opening file."); @@ -500,8 +496,7 @@ public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) $type = DataType::TYPE_NUMERIC; $value = $cellData->getAttributeNS($officeNs, 'date-value'); - $dateObj = new DateTime($value, $GMT); - $dateObj->setTimeZone($timezoneObj); + $dateObj = new DateTime($value); [$year, $month, $day, $hour, $minute, $second] = explode( ' ', $dateObj->format('Y m d H i s') diff --git a/src/PhpSpreadsheet/Reader/Ods/Properties.php b/src/PhpSpreadsheet/Reader/Ods/Properties.php index d0a45e6adb..b94d1ef681 100644 --- a/src/PhpSpreadsheet/Reader/Ods/Properties.php +++ b/src/PhpSpreadsheet/Reader/Ods/Properties.php @@ -55,9 +55,7 @@ private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElemen break; case 'date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); + $docProps->setModified($propertyValue); break; case 'description': @@ -86,8 +84,7 @@ private function setMetaProperties( break; case 'creation-date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); + $docProps->setCreated($propertyValue); break; case 'user-defined': diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index c389105bba..b1c6b13f75 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -1454,34 +1454,34 @@ private function readSummaryInformation(): void switch ($id) { case 0x01: // Code Page - $codePage = CodePage::numberToName($value); + $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Title - $this->spreadsheet->getProperties()->setTitle($value); + $this->spreadsheet->getProperties()->setTitle("$value"); break; case 0x03: // Subject - $this->spreadsheet->getProperties()->setSubject($value); + $this->spreadsheet->getProperties()->setSubject("$value"); break; case 0x04: // Author (Creator) - $this->spreadsheet->getProperties()->setCreator($value); + $this->spreadsheet->getProperties()->setCreator("$value"); break; case 0x05: // Keywords - $this->spreadsheet->getProperties()->setKeywords($value); + $this->spreadsheet->getProperties()->setKeywords("$value"); break; case 0x06: // Comments (Description) - $this->spreadsheet->getProperties()->setDescription($value); + $this->spreadsheet->getProperties()->setDescription("$value"); break; case 0x07: // Template // Not supported by PhpSpreadsheet break; case 0x08: // Last Saved By (LastModifiedBy) - $this->spreadsheet->getProperties()->setLastModifiedBy($value); + $this->spreadsheet->getProperties()->setLastModifiedBy("$value"); break; case 0x09: // Revision @@ -1606,11 +1606,11 @@ private function readDocumentSummaryInformation(): void switch ($id) { case 0x01: // Code Page - $codePage = CodePage::numberToName($value); + $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Category - $this->spreadsheet->getProperties()->setCategory($value); + $this->spreadsheet->getProperties()->setCategory("$value"); break; case 0x03: // Presentation Target @@ -1647,11 +1647,11 @@ private function readDocumentSummaryInformation(): void // Not supported by PhpSpreadsheet break; case 0x0E: // Manager - $this->spreadsheet->getProperties()->setManager($value); + $this->spreadsheet->getProperties()->setManager("$value"); break; case 0x0F: // Company - $this->spreadsheet->getProperties()->setCompany($value); + $this->spreadsheet->getProperties()->setCompany("$value"); break; case 0x10: // Links up-to-date diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php index b6f3c61fc3..ffc7ec452c 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -39,8 +39,10 @@ public function readCoreProperties($propertyData): void $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); - $this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type - $this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type + $created = (string) self::getArrayItem($xmlCore->xpath('dcterms:created')); //! respect xsi:type + $this->docProps->setCreated($created); + $modified = (string) self::getArrayItem($xmlCore->xpath('dcterms:modified')); //! respect xsi:type + $this->docProps->setModified($modified); //! respect xsi:type $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index 898dd52379..32e290dffd 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -97,7 +97,7 @@ public static function getExcelCalendar() /** * Set the Default timezone to use for dates. * - * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions + * @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions * * @return bool Success or failure */ @@ -115,29 +115,39 @@ public static function setDefaultTimezone($timeZone) } /** - * Return the Default timezone being used for dates. - * - * @return DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object + * Return the Default timezone, or UTC if default not set. */ - public static function getDefaultTimezone() + public static function getDefaultTimezone(): DateTimeZone { - if (self::$defaultTimeZone === null) { - self::$defaultTimeZone = new DateTimeZone('UTC'); - } + return self::$defaultTimeZone ?? new DateTimeZone('UTC'); + } + /** + * Return the Default timezone, or local timezone if default is not set. + */ + public static function getDefaultOrLocalTimezone(): DateTimeZone + { + return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get()); + } + + /** + * Return the Default timezone even if null. + */ + public static function getDefaultTimezoneOrNull(): ?DateTimeZone + { return self::$defaultTimeZone; } /** * Validate a timezone. * - * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object + * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object * - * @return DateTimeZone The timezone as a timezone object + * @return ?DateTimeZone The timezone as a timezone object */ private static function validateTimeZone($timeZone) { - if ($timeZone instanceof DateTimeZone) { + if ($timeZone instanceof DateTimeZone || $timeZone === null) { return $timeZone; } if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) { @@ -490,4 +500,16 @@ public static function dayStringToNumber($day) return $day; } + + public static function dateTimeFromTimestamp(string $date): DateTime + { + return DateTime::createFromFormat('U', $date) ?: new DateTime(); + } + + public static function formattedDateTimeFromTimestamp(string $date, string $format): string + { + $dtobj = self::dateTimeFromTimestamp($date); + + return $dtobj->format($format); + } } diff --git a/src/PhpSpreadsheet/Shared/IntOrFloat.php b/src/PhpSpreadsheet/Shared/IntOrFloat.php new file mode 100644 index 0000000000..060f09c883 --- /dev/null +++ b/src/PhpSpreadsheet/Shared/IntOrFloat.php @@ -0,0 +1,21 @@ +format('U'); // multiply just to make MS happy $big_date *= 10000000; @@ -537,7 +538,7 @@ public static function localDateToOLE($date) * * @param string $oleTimestamp A binary string with the encoded date * - * @return int The Unix timestamp corresponding to the string + * @return float|int The Unix timestamp corresponding to the string */ public static function OLE2LocalDate($oleTimestamp) { @@ -560,9 +561,6 @@ public static function OLE2LocalDate($oleTimestamp) // translate to seconds since 1970: $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5); - $iTimestamp = (int) $unixTimestamp; - - // Overflow conditions can't happen on 64-bit system - return ($iTimestamp == $unixTimestamp) ? $iTimestamp : ($unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN); + return IntOrFloat::evaluate($unixTimestamp); } } diff --git a/src/PhpSpreadsheet/Shared/OLE/PPS.php b/src/PhpSpreadsheet/Shared/OLE/PPS.php index 104b0d6a5d..8b9e92a881 100644 --- a/src/PhpSpreadsheet/Shared/OLE/PPS.php +++ b/src/PhpSpreadsheet/Shared/OLE/PPS.php @@ -74,14 +74,14 @@ class PPS /** * A timestamp. * - * @var int + * @var float|int */ public $Time1st; /** * A timestamp. * - * @var int + * @var float|int */ public $Time2nd; @@ -129,8 +129,8 @@ class PPS * @param int $prev The index of the previous PPS * @param int $next The index of the next PPS * @param int $dir The index of it's first child if this is a Dir or Root PPS - * @param int $time_1st A timestamp - * @param int $time_2nd A timestamp + * @param null|float|int $time_1st A timestamp + * @param null|float|int $time_2nd A timestamp * @param string $data The (usually binary) source data of the PPS * @param array $children Array containing children PPS for this PPS */ @@ -142,8 +142,8 @@ public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $t $this->PrevPps = $prev; $this->NextPps = $next; $this->DirPps = $dir; - $this->Time1st = $time_1st; - $this->Time2nd = $time_2nd; + $this->Time1st = $time_1st ?? 0; + $this->Time2nd = $time_2nd ?? 0; $this->_data = $data; $this->children = $children; if ($data != '') { diff --git a/src/PhpSpreadsheet/Shared/TimeZone.php b/src/PhpSpreadsheet/Shared/TimeZone.php index 43fd365369..a6f9850757 100644 --- a/src/PhpSpreadsheet/Shared/TimeZone.php +++ b/src/PhpSpreadsheet/Shared/TimeZone.php @@ -58,24 +58,20 @@ public static function getTimeZone() * Return the Timezone offset used for date/time conversions to/from UST * This requires both the timezone and the calculated date/time to allow for local DST. * - * @param string $timezone The timezone for finding the adjustment to UST - * @param int $timestamp PHP date/time value + * @param ?string $timezone The timezone for finding the adjustment to UST + * @param float|int $timestamp PHP date/time value * * @return int Number of seconds for timezone adjustment */ public static function getTimeZoneAdjustment($timezone, $timestamp) { - if ($timezone !== null) { - if (!self::validateTimezone($timezone)) { - throw new PhpSpreadsheetException('Invalid timezone ' . $timezone); - } - } else { - $timezone = self::$timezone; + $timezone = $timezone ?? self::$timezone; + $dtobj = Date::dateTimeFromTimestamp("$timestamp"); + if (!self::validateTimezone($timezone)) { + throw new PhpSpreadsheetException("Invalid timezone $timezone"); } + $dtobj->setTimeZone(new DateTimeZone($timezone)); - $objTimezone = new DateTimeZone($timezone); - $transitions = $objTimezone->getTransitions($timestamp, $timestamp); - - return (count($transitions) > 0) ? $transitions[0]['offset'] : 0; + return $dtobj->getOffset(); } } diff --git a/src/PhpSpreadsheet/Writer/Ods/Meta.php b/src/PhpSpreadsheet/Writer/Ods/Meta.php index cd3054c06f..9504ed7ce7 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Meta.php +++ b/src/PhpSpreadsheet/Writer/Ods/Meta.php @@ -2,7 +2,10 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; +use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; +use PhpOffice\PhpSpreadsheet\Spreadsheet; class Meta extends WriterPart { @@ -40,15 +43,24 @@ public function write(): string $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator()); $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator()); - $objWriter->writeElement('meta:creation-date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); - $objWriter->writeElement('dc:date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('meta:creation-date', $date->format(DATE_W3C)); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('dc:date', $date->format(DATE_W3C)); $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle()); $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription()); $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject()); - $keywords = explode(' ', $spreadsheet->getProperties()->getKeywords()); - foreach ($keywords as $keyword) { - $objWriter->writeElement('meta:keyword', $keyword); - } + $objWriter->writeElement('meta:keyword', $spreadsheet->getProperties()->getKeywords()); + // Don't know if this changed over time, but the keywords are all + // in a single declaration now. + //$keywords = explode(' ', $spreadsheet->getProperties()->getKeywords()); + //foreach ($keywords as $keyword) { + // $objWriter->writeElement('meta:keyword', $keyword); + //} // $objWriter->startElement('meta:user-defined'); @@ -61,10 +73,50 @@ public function write(): string $objWriter->writeRaw($spreadsheet->getProperties()->getCategory()); $objWriter->endElement(); + self::writeDocPropsCustom($objWriter, $spreadsheet); + $objWriter->endElement(); $objWriter->endElement(); return $objWriter->getData(); } + + private function writeDocPropsCustom(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + foreach ($customPropertyList as $key => $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', $customProperty); + + switch ($propertyType) { + case Properties::PROPERTY_TYPE_INTEGER: + case Properties::PROPERTY_TYPE_FLOAT: + $objWriter->writeAttribute('meta:value-type', 'float'); + $objWriter->writeRawData($propertyValue); + + break; + case Properties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeAttribute('meta:value-type', 'boolean'); + $objWriter->writeRawData($propertyValue ? 'true' : 'false'); + + break; + case Properties::PROPERTY_TYPE_DATE: + $objWriter->writeAttribute('meta:value-type', 'date'); + $dtobj = Date::dateTimeFromTimestamp($propertyValue); + $objWriter->writeRawData($dtobj->format(DATE_W3C)); + + break; + default: + $objWriter->writeRawData($propertyValue); + + break; + } + + $objWriter->endElement(); + } + } } diff --git a/src/PhpSpreadsheet/Writer/Xls.php b/src/PhpSpreadsheet/Writer/Xls.php index a1b477bf90..fea216cc54 100644 --- a/src/PhpSpreadsheet/Writer/Xls.php +++ b/src/PhpSpreadsheet/Writer/Xls.php @@ -765,7 +765,10 @@ private function writeDocumentSummaryInformation() return $data; } - private function writeSummaryPropOle(int $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + /** + * @param float|int $dataProp + */ + private function writeSummaryPropOle($dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void { if ($dataProp) { $dataSection[] = [ diff --git a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php index 6332a34df3..43ce442fee 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -137,13 +139,17 @@ public function writeDocPropsCore(Spreadsheet $spreadsheet) // dcterms:created $objWriter->startElement('dcterms:created'); $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); // dcterms:modified $objWriter->startElement('dcterms:modified'); $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getModified())); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); // dc:title @@ -205,21 +211,22 @@ public function writeDocPropsCustom(Spreadsheet $spreadsheet) $objWriter->writeAttribute('name', $customProperty); switch ($propertyType) { - case 'i': + case Properties::PROPERTY_TYPE_INTEGER: $objWriter->writeElement('vt:i4', $propertyValue); break; - case 'f': + case Properties::PROPERTY_TYPE_FLOAT: $objWriter->writeElement('vt:r8', $propertyValue); break; - case 'b': + case Properties::PROPERTY_TYPE_BOOLEAN: $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); break; - case 'd': + case Properties::PROPERTY_TYPE_DATE: $objWriter->startElement('vt:filetime'); - $objWriter->writeRawData(date(DATE_W3C, $propertyValue)); + $date = Date::dateTimeFromTimestamp("$propertyValue"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); break; diff --git a/tests/PhpSpreadsheetTests/Document/EpochTest.php b/tests/PhpSpreadsheetTests/Document/EpochTest.php new file mode 100644 index 0000000000..63c51a59d0 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Document/EpochTest.php @@ -0,0 +1,94 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $spreadsheet->getProperties()->setCreated($timestamp); + $timestamp2 = preg_replace('/1-/', '2-', $timestamp); + self::AssertNotEquals($timestamp, $timestamp2); + $spreadsheet->getProperties()->setModified($timestamp2); + $timestamp3 = preg_replace('/1-/', '3-', $timestamp); + self::AssertNotEquals($timestamp, $timestamp3); + self::AssertNotEquals($timestamp2, $timestamp3); + $spreadsheet->getProperties()->setCustomProperty('cprop', $timestamp3, 'd'); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format); + $created = $reloadedSpreadsheet->getProperties()->getCreated(); + $dt1 = DateTime::createFromFormat('U', "$created"); + $modified = $reloadedSpreadsheet->getProperties()->getModified(); + $dt2 = DateTime::createFromFormat('U', "$modified"); + if ($dt1 === false || $dt2 === false) { + self::fail('Invalid timestamp for created or modified'); + } else { + self::assertSame($timestamp, $dt1->format('Y-m-d H:i:s') . 'Z'); + self::assertSame($timestamp2, $dt2->format('Y-m-d H:i:s') . 'Z'); + } + if ($format === 'Xlsx' || $format === 'Ods') { + // No custom property support in Xls + $cprop = $reloadedSpreadsheet->getProperties()->getCustomPropertyValue('cprop'); + if (!is_numeric($cprop)) { + self::fail('Cannot find custom property'); + } else { + $dt3 = DateTime::createFromFormat('U', "$cprop"); + if ($dt3 === false) { + self::fail('Invalid timestamp for custom property'); + } else { + self::assertSame($timestamp3, $dt3->format('Y-m-d H:i:s') . 'Z'); + } + } + } + } + + public function providerFormats2(): array + { + return [ + ['Ods'], + ['Xls'], + ['Xlsx'], + ]; + } + + /** + * @dataProvider providerFormats2 + */ + public function testConsistentTimeStamp(string $format): void + { + $pgmstart = (float) (new DateTime())->format('U'); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format); + $pgmend = (float) (new DateTime())->format('U'); + self::assertLessThanOrEqual($pgmend, $pgmstart); + $created = $reloadedSpreadsheet->getProperties()->getCreated(); + $modified = $reloadedSpreadsheet->getProperties()->getModified(); + self::assertLessThanOrEqual($pgmend, $created); + self::assertLessThanOrEqual($pgmend, $modified); + self::assertLessThanOrEqual($created, $pgmstart); + self::assertLessThanOrEqual($modified, $pgmstart); + } +} diff --git a/tests/PhpSpreadsheetTests/Document/PropertiesTest.php b/tests/PhpSpreadsheetTests/Document/PropertiesTest.php index 54932bb841..4583e2402c 100644 --- a/tests/PhpSpreadsheetTests/Document/PropertiesTest.php +++ b/tests/PhpSpreadsheetTests/Document/PropertiesTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Document; use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PHPUnit\Framework\TestCase; class PropertiesTest extends TestCase @@ -23,7 +24,7 @@ public function testNewInstance(): void self::assertSame('Unknown Creator', $this->properties->getCreator()); self::assertSame('Unknown Creator', $this->properties->getLastModifiedBy()); self::assertSame('Untitled Spreadsheet', $this->properties->getTitle()); - self::assertSame('Microsoft Corporation', $this->properties->getCompany()); + self::assertSame('', $this->properties->getCompany()); self::assertSame($createdTime, $this->properties->getCreated()); self::assertSame($modifiedTime, $this->properties->getModified()); } @@ -159,8 +160,12 @@ public function testSetCustomProperties($expectedType, $expectedValue, $property { $this->properties->setCustomProperty($propertyName, ...$args); self::assertTrue($this->properties->isCustomPropertySet($propertyName)); - self::assertSame($expectedValue, $this->properties->getCustomPropertyValue($propertyName)); self::assertSame($expectedType, $this->properties->getCustomPropertyType($propertyName)); + $result = $this->properties->getCustomPropertyValue($propertyName); + if ($expectedType === Properties::PROPERTY_TYPE_DATE) { + $result = Date::formattedDateTimeFromTimestamp("$result", 'Y-m-d'); + } + self::assertSame($expectedValue, $result); } public function providerCustomProperties(): array diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/OdsPropertiesTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/OdsPropertiesTest.php new file mode 100644 index 0000000000..577b422111 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/OdsPropertiesTest.php @@ -0,0 +1,112 @@ +timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + } + + protected function tearDown(): void + { + date_default_timezone_set($this->timeZone); + } + + public function testLoadOdsWorkbookProperties(): void + { + $customPropertySet = [ + 'Owner' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 10.0], + 'TestDate' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + 'HereAndNow' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + ]; + + $filename = 'tests/data/Reader/Ods/propertyTest.ods'; + $reader = new Ods(); + $spreadsheet = $reader->load($filename); + + $properties = $spreadsheet->getProperties(); + // Core Properties +// self::assertSame('Mark Baker', $properties->getCreator()); + self::assertSame('Property Test File', $properties->getTitle()); + self::assertSame('Testing for Properties', $properties->getSubject()); + self::assertSame('TEST ODS PHPSpreadsheet', $properties->getKeywords()); + + // Extended Properties +// self::assertSame('PHPOffice', $properties->getCompany()); +// self::assertSame('The Big Boss', $properties->getManager()); + + // Custom Properties + $customProperties = $properties->getCustomProperties(); + self::assertIsArray($customProperties); + $customProperties = array_flip($customProperties); + self::assertArrayHasKey('TestDate', $customProperties); + + foreach ($customPropertySet as $propertyName => $testData) { + self::assertTrue($properties->isCustomPropertySet($propertyName)); + self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + $result = $properties->getCustomPropertyValue($propertyName); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $result = Date::formattedDateTimeFromTimestamp("$result", 'Y-m-d'); + } + self::assertSame($testData['value'], $result); + } + } + + public function testReloadOdsWorkbookProperties(): void + { + $customPropertySet = [ + 'Owner' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 10.0], + 'TestDate' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + 'HereAndNow' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], + ]; + + $filename = 'tests/data/Reader/Ods/propertyTest.ods'; + $reader = new Ods(); + $spreadsheetOld = $reader->load($filename); + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods'); + + $properties = $spreadsheet->getProperties(); + // Core Properties +// self::assertSame('Mark Baker', $properties->getCreator()); + self::assertSame('Property Test File', $properties->getTitle()); + self::assertSame('Testing for Properties', $properties->getSubject()); + self::assertSame('TEST ODS PHPSpreadsheet', $properties->getKeywords()); + + // Extended Properties +// self::assertSame('PHPOffice', $properties->getCompany()); +// self::assertSame('The Big Boss', $properties->getManager()); + + // Custom Properties + $customProperties = $properties->getCustomProperties(); + self::assertIsArray($customProperties); + $customProperties = array_flip($customProperties); + self::assertArrayHasKey('TestDate', $customProperties); + + foreach ($customPropertySet as $propertyName => $testData) { + self::assertTrue($properties->isCustomPropertySet($propertyName)); + self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + $result = $properties->getCustomPropertyValue($propertyName); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $result = Date::formattedDateTimeFromTimestamp("$result", 'Y-m-d'); + } + self::assertSame($testData['value'], $result); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php index 1091020727..a7f1466c98 100644 --- a/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Ods/OdsTest.php @@ -290,46 +290,4 @@ public function testReadBoldItalicUnderline(): void self::assertTrue($style->getFont()->getBold()); self::assertTrue($style->getFont()->getItalic()); } - - public function testLoadOdsWorkbookProperties(): void - { - $customPropertySet = [ - 'Owner' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice'], - 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], - 'Counter' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 10.0], - 'TestDate' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], - 'HereAndNow' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-30'], - ]; - - $filename = 'tests/data/Reader/Ods/propertyTest.ods'; - $reader = new Ods(); - $spreadsheet = $reader->load($filename); - - $properties = $spreadsheet->getProperties(); - // Core Properties -// self::assertSame('Mark Baker', $properties->getCreator()); - self::assertSame('Property Test File', $properties->getTitle()); - self::assertSame('Testing for Properties', $properties->getSubject()); - self::assertSame('TEST ODS PHPSpreadsheet', $properties->getKeywords()); - - // Extended Properties -// self::assertSame('PHPOffice', $properties->getCompany()); -// self::assertSame('The Big Boss', $properties->getManager()); - - // Custom Properties - $customProperties = $properties->getCustomProperties(); - self::assertIsArray($customProperties); - $customProperties = array_flip($customProperties); - self::assertArrayHasKey('TestDate', $customProperties); - - foreach ($customPropertySet as $propertyName => $testData) { - self::assertTrue($properties->isCustomPropertySet($propertyName)); - self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); - if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { - self::assertSame($testData['value'], date('Y-m-d', $properties->getCustomPropertyValue($propertyName))); - } else { - self::assertSame($testData['value'], $properties->getCustomPropertyValue($propertyName)); - } - } - } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/PropertiesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/PropertiesTest.php new file mode 100644 index 0000000000..1e2bd84142 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/PropertiesTest.php @@ -0,0 +1,94 @@ + ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice Suite'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_INTEGER, 'value' => 15], + 'Rate' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 1.15], + 'Refactor Date' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-10'], + ]; + + $filename = 'tests/data/Reader/XLSX/propertyTest.xlsx'; + $reader = new Xlsx(); + $spreadsheet = $reader->load($filename); + + $properties = $spreadsheet->getProperties(); + // Core Properties + self::assertSame('Mark Baker', $properties->getCreator()); + self::assertSame('Unit Testing', $properties->getTitle()); + self::assertSame('Property Test', $properties->getSubject()); + + // Extended Properties + self::assertSame('PHPOffice', $properties->getCompany()); + self::assertSame('The Big Boss', $properties->getManager()); + + // Custom Properties + $customProperties = $properties->getCustomProperties(); + self::assertIsArray($customProperties); + $customProperties = array_flip($customProperties); + self::assertArrayHasKey('Publisher', $customProperties); + + foreach ($customPropertySet as $propertyName => $testData) { + self::assertTrue($properties->isCustomPropertySet($propertyName)); + self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + $result = $properties->getCustomPropertyValue($propertyName); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $result = Date::formattedDateTimeFromTimestamp("$result", 'Y-m-d'); + } + self::assertSame($testData['value'], $result); + } + } + + public function testReloadXlsxWorkbookProperties(): void + { + $customPropertySet = [ + 'Publisher' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice Suite'], + 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], + 'Counter' => ['type' => Properties::PROPERTY_TYPE_INTEGER, 'value' => 15], + 'Rate' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 1.15], + 'Refactor Date' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-10'], + ]; + + $filename = 'tests/data/Reader/XLSX/propertyTest.xlsx'; + $reader = new Xlsx(); + $spreadsheetOld = $reader->load($filename); + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Xlsx'); + + $properties = $spreadsheet->getProperties(); + // Core Properties + self::assertSame('Mark Baker', $properties->getCreator()); + self::assertSame('Unit Testing', $properties->getTitle()); + self::assertSame('Property Test', $properties->getSubject()); + + // Extended Properties + self::assertSame('PHPOffice', $properties->getCompany()); + self::assertSame('The Big Boss', $properties->getManager()); + + // Custom Properties + $customProperties = $properties->getCustomProperties(); + self::assertIsArray($customProperties); + $customProperties = array_flip($customProperties); + self::assertArrayHasKey('Publisher', $customProperties); + + foreach ($customPropertySet as $propertyName => $testData) { + self::assertTrue($properties->isCustomPropertySet($propertyName)); + self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); + $result = $properties->getCustomPropertyValue($propertyName); + if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { + $result = Date::formattedDateTimeFromTimestamp("$result", 'Y-m-d'); + } + self::assertSame($testData['value'], $result); + } + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php index 738a25b967..b5b2671231 100644 --- a/tests/PhpSpreadsheetTests/Reader/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/XlsxTest.php @@ -14,47 +14,6 @@ class XlsxTest extends TestCase { - public function testLoadXlsxWorkbookProperties(): void - { - $customPropertySet = [ - 'Publisher' => ['type' => Properties::PROPERTY_TYPE_STRING, 'value' => 'PHPOffice Suite'], - 'Tested' => ['type' => Properties::PROPERTY_TYPE_BOOLEAN, 'value' => true], - 'Counter' => ['type' => Properties::PROPERTY_TYPE_INTEGER, 'value' => 15], - 'Rate' => ['type' => Properties::PROPERTY_TYPE_FLOAT, 'value' => 1.15], - 'Refactor Date' => ['type' => Properties::PROPERTY_TYPE_DATE, 'value' => '2019-06-10'], - ]; - - $filename = 'tests/data/Reader/XLSX/propertyTest.xlsx'; - $reader = new Xlsx(); - $spreadsheet = $reader->load($filename); - - $properties = $spreadsheet->getProperties(); - // Core Properties - self::assertSame('Mark Baker', $properties->getCreator()); - self::assertSame('Unit Testing', $properties->getTitle()); - self::assertSame('Property Test', $properties->getSubject()); - - // Extended Properties - self::assertSame('PHPOffice', $properties->getCompany()); - self::assertSame('The Big Boss', $properties->getManager()); - - // Custom Properties - $customProperties = $properties->getCustomProperties(); - self::assertIsArray($customProperties); - $customProperties = array_flip($customProperties); - self::assertArrayHasKey('Publisher', $customProperties); - - foreach ($customPropertySet as $propertyName => $testData) { - self::assertTrue($properties->isCustomPropertySet($propertyName)); - self::assertSame($testData['type'], $properties->getCustomPropertyType($propertyName)); - if ($properties->getCustomPropertyType($propertyName) == Properties::PROPERTY_TYPE_DATE) { - self::assertSame($testData['value'], date('Y-m-d', $properties->getCustomPropertyValue($propertyName))); - } else { - self::assertSame($testData['value'], $properties->getCustomPropertyValue($propertyName)); - } - } - } - public function testListWorksheetInfo(): void { $filename = 'tests/data/Reader/XLSX/rowColumnAttributeTest.xlsx'; diff --git a/tests/PhpSpreadsheetTests/Shared/DateTest.php b/tests/PhpSpreadsheetTests/Shared/DateTest.php index 1ca4c674a8..e4bf68385a 100644 --- a/tests/PhpSpreadsheetTests/Shared/DateTest.php +++ b/tests/PhpSpreadsheetTests/Shared/DateTest.php @@ -21,7 +21,7 @@ class DateTest extends TestCase protected function setUp(): void { - $this->dttimezone = Date::getDefaultTimeZone(); + $this->dttimezone = Date::getDefaultTimeZoneOrNull(); $this->excelCalendar = Date::getExcelCalendar(); } diff --git a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php index edad6e6bd2..0efdc05fb3 100644 --- a/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php +++ b/tests/PhpSpreadsheetTests/Shared/TimeZoneTest.php @@ -23,7 +23,7 @@ class TimeZoneTest extends TestCase protected function setUp(): void { $this->tztimezone = TimeZone::getTimeZone(); - $this->dttimezone = Date::getDefaultTimeZone(); + $this->dttimezone = Date::getDefaultTimeZoneOrNull(); } protected function tearDown(): void @@ -72,24 +72,32 @@ public function testTimeZoneAdjustmentsInvalidTz(): void { $this->expectException(\PhpOffice\PhpSpreadsheet\Exception::class); $dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-09-22 00:00:00'); - $tstmp = $dtobj->getTimestamp(); - $unsupportedTimeZone = 'XEtc/GMT+10'; - TimeZone::getTimeZoneAdjustment($unsupportedTimeZone, $tstmp); + if ($dtobj === false) { + self::fail('DateTime createFromFormat failed'); + } else { + $tstmp = $dtobj->getTimestamp(); + $unsupportedTimeZone = 'XEtc/GMT+10'; + TimeZone::getTimeZoneAdjustment($unsupportedTimeZone, $tstmp); + } } public function testTimeZoneAdjustments(): void { $dtobj = DateTime::createFromFormat('Y-m-d H:i:s', '2008-01-01 00:00:00'); - $tstmp = $dtobj->getTimestamp(); - $supportedTimeZone = 'UTC'; - $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); - self::assertEquals(0, $adj); - $supportedTimeZone = 'America/Toronto'; - $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); - self::assertEquals(-18000, $adj); - $supportedTimeZone = 'America/Chicago'; - TimeZone::setTimeZone($supportedTimeZone); - $adj = TimeZone::getTimeZoneAdjustment(null, $tstmp); - self::assertEquals(-21600, $adj); + if ($dtobj === false) { + self::fail('DateTime createFromFormat failed'); + } else { + $tstmp = $dtobj->getTimestamp(); + $supportedTimeZone = 'UTC'; + $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); + self::assertEquals(0, $adj); + $supportedTimeZone = 'America/Toronto'; + $adj = TimeZone::getTimeZoneAdjustment($supportedTimeZone, $tstmp); + self::assertEquals(-18000, $adj); + $supportedTimeZone = 'America/Chicago'; + TimeZone::setTimeZone($supportedTimeZone); + $adj = TimeZone::getTimeZoneAdjustment(null, $tstmp); + self::assertEquals(-21600, $adj); + } } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 03f201beb6..dea4752281 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls; +use DateTime; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; @@ -24,7 +25,7 @@ protected function tearDown(): void public function testBmp(): void { - $pgmstart = time(); + $pgmstart = (float) (new DateTime())->format('U'); $spreadsheet = new Spreadsheet(); $filstart = $spreadsheet->getProperties()->getModified(); self::assertLessThanOrEqual($filstart, $pgmstart); @@ -49,7 +50,7 @@ public function testBmp(): void $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; self::assertEquals('image/png', $mimeType); } - $pgmend = time(); + $pgmend = (float) (new DateTime())->format('U'); self::assertLessThanOrEqual($pgmend, $pgmstart); self::assertLessThanOrEqual($pgmend, $filstart);