From 745499cc55e9f2dbeba3045b7457e7a240a6f134 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Sat, 13 Aug 2016 11:49:01 +0100 Subject: [PATCH] Changes to Date/Time conversion functions to use DateTime objects internally rather than unix timestamps; Changes to Date/Time methods to make them more intuitive and correct (eg `excelToTimestamp` rather than `excelToPHP`) TODO - Still need to write unit tests, and convert Examples to use the new names --- src/PhpSpreadsheet/Calculation/DateTime.php | 48 ++--- src/PhpSpreadsheet/Calculation/Financial.php | 2 +- src/PhpSpreadsheet/Shared/Date.php | 174 +++++++++---------- src/PhpSpreadsheet/Spreadsheet.php | 8 +- src/PhpSpreadsheet/Style/NumberFormat.php | 2 +- src/PhpSpreadsheet/Worksheet/AutoFilter.php | 4 +- 6 files changed, 116 insertions(+), 122 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/DateTime.php b/src/PhpSpreadsheet/Calculation/DateTime.php index cf01600f..e6c25d97 100644 --- a/src/PhpSpreadsheet/Calculation/DateTime.php +++ b/src/PhpSpreadsheet/Calculation/DateTime.php @@ -123,7 +123,7 @@ class DateTime private static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) { // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); $oMonth = (int) $PHPDateObject->format('m'); $oYear = (int) $PHPDateObject->format('Y'); @@ -218,10 +218,10 @@ class DateTime $retValue = (float) $excelDateTime; break; case Functions::RETURNDATE_PHP_NUMERIC: - $retValue = (integer) \PHPExcel\Shared\Date::excelToPHP($excelDateTime); + $retValue = (integer) \PHPExcel\Shared\Date::excelToTimestamp($excelDateTime); break; case Functions::RETURNDATE_PHP_OBJECT: - $retValue = \PHPExcel\Shared\Date::excelToPHPObject($excelDateTime); + $retValue = \PHPExcel\Shared\Date::excelToDateTimeObject($excelDateTime); break; } date_default_timezone_set($saveTimeZone); @@ -341,9 +341,9 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) $excelDateValue; case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP($excelDateValue); + return (integer) \PHPExcel\Shared\Date::excelToTimestamp($excelDateValue); case Functions::RETURNDATE_PHP_OBJECT: - return \PHPExcel\Shared\Date::excelToPHPObject($excelDateValue); + return \PHPExcel\Shared\Date::excelToDateTimeObject($excelDateValue); } } @@ -435,7 +435,7 @@ class DateTime } return (float) \PHPExcel\Shared\Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP(\PHPExcel\Shared\Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 + return (integer) \PHPExcel\Shared\Date::excelToTimestamp(\PHPExcel\Shared\Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 case Functions::RETURNDATE_PHP_OBJECT: $dayAdjust = 0; if ($hour < 0) { @@ -584,7 +584,7 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) $excelDateValue; case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP($excelDateValue); + return (integer) \PHPExcel\Shared\Date::excelToTimestamp($excelDateValue); case Functions::RETURNDATE_PHP_OBJECT: return new \DateTime($PHPDateArray['year'].'-'.$PHPDateArray['month'].'-'.$PHPDateArray['day'].' 00:00:00'); } @@ -645,7 +645,7 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) $excelDateValue; case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) $phpDateValue = \PHPExcel\Shared\Date::excelToPHP($excelDateValue+25569) - 3600; + return (integer) $phpDateValue = \PHPExcel\Shared\Date::excelToTimestamp($excelDateValue+25569) - 3600; case Functions::RETURNDATE_PHP_OBJECT: return new \DateTime('1900-01-01 '.$PHPDateArray['hour'].':'.$PHPDateArray['minute'].':'.$PHPDateArray['second']); } @@ -685,12 +685,12 @@ class DateTime // Execute function $difference = $endDate - $startDate; - $PHPStartDateObject = \PHPExcel\Shared\Date::excelToPHPObject($startDate); + $PHPStartDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($startDate); $startDays = $PHPStartDateObject->format('j'); $startMonths = $PHPStartDateObject->format('n'); $startYears = $PHPStartDateObject->format('Y'); - $PHPEndDateObject = \PHPExcel\Shared\Date::excelToPHPObject($endDate); + $PHPEndDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($endDate); $endDays = $PHPEndDateObject->format('j'); $endMonths = $PHPEndDateObject->format('n'); $endYears = $PHPEndDateObject->format('Y'); @@ -805,12 +805,12 @@ class DateTime } // Execute function - $PHPStartDateObject = \PHPExcel\Shared\Date::excelToPHPObject($startDate); + $PHPStartDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($startDate); $startDay = $PHPStartDateObject->format('j'); $startMonth = $PHPStartDateObject->format('n'); $startYear = $PHPStartDateObject->format('Y'); - $PHPEndDateObject = \PHPExcel\Shared\Date::excelToPHPObject($endDate); + $PHPEndDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($endDate); $endDay = $PHPEndDateObject->format('j'); $endMonth = $PHPEndDateObject->format('n'); $endYear = $PHPEndDateObject->format('Y'); @@ -1112,9 +1112,9 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) $endDate; case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP($endDate); + return (integer) \PHPExcel\Shared\Date::excelToTimestamp($endDate); case Functions::RETURNDATE_PHP_OBJECT: - return \PHPExcel\Shared\Date::excelToPHPObject($endDate); + return \PHPExcel\Shared\Date::excelToDateTimeObject($endDate); } } @@ -1147,7 +1147,7 @@ class DateTime } // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); return (int) $PHPDateObject->format('j'); } @@ -1191,7 +1191,7 @@ class DateTime } // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); $DoW = $PHPDateObject->format('w'); $firstDay = 1; @@ -1267,7 +1267,7 @@ class DateTime } // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); $dayOfYear = $PHPDateObject->format('z'); $PHPDateObject->modify('-' . $dayOfYear . ' days'); $dow = $PHPDateObject->format('w'); @@ -1306,7 +1306,7 @@ class DateTime } // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); return (int) $PHPDateObject->format('n'); } @@ -1338,7 +1338,7 @@ class DateTime } // Execute function - $PHPDateObject = \PHPExcel\Shared\Date::excelToPHPObject($dateValue); + $PHPDateObject = \PHPExcel\Shared\Date::excelToDateTimeObject($dateValue); return (int) $PHPDateObject->format('Y'); } @@ -1379,7 +1379,7 @@ class DateTime } elseif ($timeValue < 0.0) { return Functions::NAN(); } - $timeValue = \PHPExcel\Shared\Date::excelToPHP($timeValue); + $timeValue = \PHPExcel\Shared\Date::excelToTimestamp($timeValue); return (int) gmdate('G', $timeValue); } @@ -1420,7 +1420,7 @@ class DateTime } elseif ($timeValue < 0.0) { return Functions::NAN(); } - $timeValue = \PHPExcel\Shared\Date::excelToPHP($timeValue); + $timeValue = \PHPExcel\Shared\Date::excelToTimestamp($timeValue); return (int) gmdate('i', $timeValue); } @@ -1461,7 +1461,7 @@ class DateTime } elseif ($timeValue < 0.0) { return Functions::NAN(); } - $timeValue = \PHPExcel\Shared\Date::excelToPHP($timeValue); + $timeValue = \PHPExcel\Shared\Date::excelToTimestamp($timeValue); return (int) gmdate('s', $timeValue); } @@ -1507,7 +1507,7 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) \PHPExcel\Shared\Date::PHPToExcel($PHPDateObject); case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP(\PHPExcel\Shared\Date::PHPToExcel($PHPDateObject)); + return (integer) \PHPExcel\Shared\Date::excelToTimestamp(\PHPExcel\Shared\Date::PHPToExcel($PHPDateObject)); case Functions::RETURNDATE_PHP_OBJECT: return $PHPDateObject; } @@ -1556,7 +1556,7 @@ class DateTime case Functions::RETURNDATE_EXCEL: return (float) \PHPExcel\Shared\Date::PHPToExcel($PHPDateObject); case Functions::RETURNDATE_PHP_NUMERIC: - return (integer) \PHPExcel\Shared\Date::excelToPHP(\PHPExcel\Shared\Date::PHPToExcel($PHPDateObject)); + return (integer) \PHPExcel\Shared\Date::excelToTimestamp(\PHPExcel\Shared\Date::PHPToExcel($PHPDateObject)); case Functions::RETURNDATE_PHP_OBJECT: return $PHPDateObject; } diff --git a/src/PhpSpreadsheet/Calculation/Financial.php b/src/PhpSpreadsheet/Calculation/Financial.php index 65401087..9111761c 100644 --- a/src/PhpSpreadsheet/Calculation/Financial.php +++ b/src/PhpSpreadsheet/Calculation/Financial.php @@ -67,7 +67,7 @@ class Financial { $months = 12 / $frequency; - $result = \PHPExcel\Shared\Date::excelToPHPObject($maturity); + $result = \PHPExcel\Shared\Date::excelToDateTimeObject($maturity); $eom = self::isLastDayOfMonth($result); while ($settlement < \PHPExcel\Shared\Date::PHPToExcel($result)) { diff --git a/src/PhpSpreadsheet/Shared/Date.php b/src/PhpSpreadsheet/Shared/Date.php index 3c1cdeba..a6189d86 100644 --- a/src/PhpSpreadsheet/Shared/Date.php +++ b/src/PhpSpreadsheet/Shared/Date.php @@ -33,50 +33,23 @@ class Date const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0 const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0 - /* - * Names of the months of the year, indexed by shortname - * Planned usage for locale settings - * - * @public - * @var string[] - */ - public static $monthNames = [ - 'Jan' => 'January', - 'Feb' => 'February', - 'Mar' => 'March', - 'Apr' => 'April', - 'May' => 'May', - 'Jun' => 'June', - 'Jul' => 'July', - 'Aug' => 'August', - 'Sep' => 'September', - 'Oct' => 'October', - 'Nov' => 'November', - 'Dec' => 'December', - ]; - - /* - * Names of the months of the year, indexed by shortname - * Planned usage for locale settings - * - * @public - * @var string[] - */ - public static $numberSuffixes = [ - 'st', - 'nd', - 'rd', - 'th', - ]; - /* * Base calendar year to use for calculations + * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904) * * @private * @var int */ protected static $excelBaseDate = self::CALENDAR_WINDOWS_1900; + /* + * Default timezone to use for DateTime objects + * + * @private + * @var null|\DateTimeZone + */ + protected static $defaultTimeZone; + /** * Set the Excel calendar (Windows 1900 or Mac 1904) * @@ -87,13 +60,12 @@ class Date { if (($baseDate == self::CALENDAR_WINDOWS_1900) || ($baseDate == self::CALENDAR_MAC_1904)) { - self::$excelBaseDate = $baseDate; + self::$excelCalendar = $baseDate; return true; } return false; } - /** * Return the Excel calendar (Windows 1900 or Mac 1904) * @@ -101,73 +73,95 @@ class Date */ public static function getExcelCalendar() { - return self::$excelBaseDate; + return self::$excelCalendar; } + /** + * Set the Default timezone to use for dates + * + * @param string|\DateTimeZone $timezone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions + * @return boolean Success or failure + * @throws \Exception + */ + public static function setDefaultTimezone($timeZone) + { + if ($timeZone = self::validateTimeZone($timeZone)) { + self::$defaultTimeZone = $timeZone; + return true; + } + return false; + } /** - * Convert a date from Excel to PHP + * Return the Default timezone being used for dates * - * @param integer $dateValue Excel date/time value - * @param boolean $adjustToTimezone Flag indicating whether $dateValue should be treated as - * a UST timestamp, or adjusted to UST - * @param string $timezone The timezone for finding the adjustment from UST - * @return integer PHP serialized date/time + * @return \DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object */ - public static function excelToPHP($dateValue = 0, $adjustToTimezone = false, $timezone = null) + public static function getDefaultTimezone() { + if (self::$defaultTimeZone === null) { + self::$defaultTimeZone = new \DateTimeZone('UTC'); + } + return self::$defaultTimeZone; + } + + /** + * Validate a timezone + * + * @param string|\DateTimeZone $timezone The timezone to validate, either as a timezone string or object + * @return \DateTimeZone The timezone as a timezone object + * @throws \Exception + */ + protected static function validateTimeZone($timeZone) { + if (is_object($timeZone) && $timeZone instanceof \DateTimeZone) { + return $timeZone; + } elseif (is_string($timeZone)) { + return new \DateTimeZone($timeZone); + } + throw new \Exception('Invalid timezone'); + } + + /** + * Convert a MS serialized datetime value from Excel to a PHP Date/Time object + * + * @param integer|float $dateValue Excel date/time value + * @param \DateTimeZone|string|null $timezone The timezone to assume for the Excel timestamp, + * if you don't want to treat it as a UTC value + * @return \DateTime PHP date/time object + * @throws \Exception + */ + public static function excelToDateTimeObject($excelTimestamp = 0, $timeZone = null) { + $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone); if (self::$excelBaseDate == self::CALENDAR_WINDOWS_1900) { - $myexcelBaseDate = 25569; - // Adjust for the spurious 29-Feb-1900 (Day 60) - if ($dateValue < 60) { - --$myexcelBaseDate; - } + $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone); } else { - $myexcelBaseDate = 24107; + $baseDate = new \DateTime('1904-01-01', $timeZone); } + $days = floor($excelTimestamp); + $partDay = $excelTimestamp - $days; + $hours = floor($partDay * 24); + $partDay = $partDay * 24 - $hours; + $minutes = floor($partDay * 60); + $partDay = $partDay * 60 - $minutes; + $seconds = floor($partDay * 60); +// $fraction = $partDay - $seconds; - // Perform conversion - if ($dateValue >= 1) { - $utcDays = $dateValue - $myexcelBaseDate; - $returnValue = round($utcDays * 86400); - if (($returnValue <= PHP_INT_MAX) && ($returnValue >= -PHP_INT_MAX)) { - $returnValue = (integer) $returnValue; - } - } else { - $hours = round($dateValue * 24); - $mins = round($dateValue * 1440) - round($hours * 60); - $secs = round($dateValue * 86400) - round($hours * 3600) - round($mins * 60); - $returnValue = (integer) gmmktime($hours, $mins, $secs); - } - - $timezoneAdjustment = ($adjustToTimezone) ? - TimeZone::getTimezoneAdjustment($timezone, $returnValue) : - 0; - - return $returnValue + $timezoneAdjustment; - } - + $interval = '+' . $days . ' days'; + return $baseDate->modify($interval) + ->setTime($hours, $minutes, $seconds); + } /** - * Convert a date from Excel to a PHP Date/Time object + * Convert a MS serialized datetime value from Excel to a unix timestamp * - * @param integer $dateValue Excel date/time value - * @return \DateTime PHP date/time object + * @param integer|float $dateValue Excel date/time value + * @return integer Unix timetamp for this date/time + * @throws \Exception */ - public static function excelToPHPObject($dateValue = 0) - { - $dateTime = self::excelToPHP($dateValue); - $days = floor($dateTime / 86400); - $time = round((($dateTime / 86400) - $days) * 86400); - $hours = round($time / 3600); - $minutes = round($time / 60) - ($hours * 60); - $seconds = round($time) - ($hours * 3600) - ($minutes * 60); - - $dateObj = date_create('1-Jan-1970+'.$days.' days'); - $dateObj->setTime($hours, $minutes, $seconds); - - return $dateObj; - } + public static function excelToTimestamp($excelTimestampexcelTimestamp = 0, $timeZone = null) { + return self::excelToTimestamp($excelTimestamp, $timeZone) + ->format('U'); + } /** diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index eee9245e..46d32a61 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -1,4 +1,4 @@ -format($format); } diff --git a/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/src/PhpSpreadsheet/Worksheet/AutoFilter.php index 2b832afd..8ef80021 100644 --- a/src/PhpSpreadsheet/Worksheet/AutoFilter.php +++ b/src/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -327,7 +327,7 @@ class AutoFilter } if (is_numeric($cellValue)) { - $dateValue = \PHPExcel\Shared\Date::excelToPHP($cellValue); + $dateValue = \PHPExcel\Shared\Date::excelToTimestamp($cellValue); if ($cellValue < 1) { // Just the time part $dtVal = date('His', $dateValue); @@ -444,7 +444,7 @@ class AutoFilter } if (is_numeric($cellValue)) { - $dateValue = date('m', \PHPExcel\Shared\Date::excelToPHP($cellValue)); + $dateValue = date('m', \PHPExcel\Shared\Date::excelToTimestamp($cellValue)); if (in_array($dateValue, $monthSet)) { return true; }