From 53a32ce7f7f8db60c223fba601652ab321f60fa5 Mon Sep 17 00:00:00 2001 From: Mark Baker Date: Fri, 2 Nov 2012 23:07:01 +0000 Subject: [PATCH] Providing support for timezone adjustments in date/time conversion methods --- Classes/PHPExcel/Shared/Date.php | 137 +++++++++++++++--- .../Classes/PHPExcel/Shared/DateTest.php | 48 ++++++ .../DateTimeExcelToPHP1900Timezone.data | 23 +++ .../rawTestData/Shared/PasswordHashes.data | 2 +- 4 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 unitTests/rawTestData/Shared/DateTimeExcelToPHP1900Timezone.data diff --git a/Classes/PHPExcel/Shared/Date.php b/Classes/PHPExcel/Shared/Date.php index 6043bd37..70a3094c 100644 --- a/Classes/PHPExcel/Shared/Date.php +++ b/Classes/PHPExcel/Shared/Date.php @@ -67,18 +67,26 @@ class PHPExcel_Shared_Date * @private * @var int */ - private static $ExcelBaseDate = self::CALENDAR_WINDOWS_1900; + private static $_excelBaseDate = self::CALENDAR_WINDOWS_1900; + + /* + * Default Timezone used for date/time conversions + * + * @private + * @var string + */ + private static $_timezone = 'UTC'; /** * Set the Excel calendar (Windows 1900 or Mac 1904) * - * @param integer $baseDate Excel base date + * @param integer $baseDate Excel base date (1900 or 1904) * @return boolean Success or failure */ public static function setExcelCalendar($baseDate) { if (($baseDate == self::CALENDAR_WINDOWS_1900) || ($baseDate == self::CALENDAR_MAC_1904)) { - self::$ExcelBaseDate = $baseDate; + self::$_excelBaseDate = $baseDate; return TRUE; } return FALSE; @@ -88,33 +96,116 @@ class PHPExcel_Shared_Date /** * Return the Excel calendar (Windows 1900 or Mac 1904) * - * @return integer $baseDate Excel base date + * @return integer Excel base date (1900 or 1904) */ public static function getExcelCalendar() { - return self::$ExcelBaseDate; + return self::$_excelBaseDate; } // function getExcelCalendar() /** - * Convert a date from Excel to PHP + * Validate a Timezone value * - * @param long $dateValue Excel date/time value - * @return long PHP serialized date/time + * @param string $timezone Time zone (e.g. 'Europe/London') + * @return boolean Success or failure */ - public static function ExcelToPHP($dateValue = 0) { - if (self::$ExcelBaseDate == self::CALENDAR_WINDOWS_1900) { - $myExcelBaseDate = 25569; - // Adjust for the spurious 29-Feb-1900 (Day 60) - if ($dateValue < 60) { - --$myExcelBaseDate; + private static function _validateTimezone($timezone) { + if (in_array($timezone, DateTimeZone::listIdentifiers())) { + return TRUE; + } + return FALSE; + } + + /** + * Set the Default Timezone used for date/time conversions + * + * @param string $timezone Time zone (e.g. 'Europe/London') + * @return boolean Success or failure + */ + public static function setTimezone($timezone) { + if (self::_validateTimezone($timezone)) { + self::$_timezone = $timezone; + return TRUE; + } + return FALSE; + } // function setTimezone() + + + /** + * Return the Default Timezone used for date/time conversions + * + * @return string Timezone (e.g. 'Europe/London') + */ + public static function getTimezone() { + return self::$_timezone; + } // 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 integer $timestamp PHP date/time value + * @return integer Number of seconds for timezone adjustment + * @throws PHPExcel_Exception + */ + private static function _getTimezoneAdjustment($timezone, $timestamp) { + if ($timezone !== NULL) { + if (!self::_validateTimezone($timezone)) { + throw new PHPExcel_Exception("Invalid timezone " . $timezone); } } else { - $myExcelBaseDate = 24107; + $timezone = self::$_timezone; + } + + if ($timezone == 'UST') { + return 0; + } + + $objTimezone = new DateTimeZone($timezone); + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + $transitions = $objTimezone->getTransitions($timestamp,$timestamp); + } else { + $allTransitions = $objTimezone->getTransitions(); + $transitions = array(); + foreach($allTransitions as $key => $transition) { + if ($transition['ts'] > $timestamp) { + $transitions[] = ($key > 0) ? $allTransitions[$key - 1] : $transitions1[] = $transition; + break; + } + if (empty($transitions)) { + $transitions[] = end($allTransitions); + } + } + } + + return (count($transitions) > 0) ? $transitions[0]['offset'] : 0; + } + + /** + * Convert a date from Excel to PHP + * + * @param long $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 long PHP serialized date/time + */ + public static function ExcelToPHP($dateValue = 0, $adjustToTimezone = FALSE, $timezone = NULL) { + if (self::$_excelBaseDate == self::CALENDAR_WINDOWS_1900) { + $my_excelBaseDate = 25569; + // Adjust for the spurious 29-Feb-1900 (Day 60) + if ($dateValue < 60) { + --$my_excelBaseDate; + } + } else { + $my_excelBaseDate = 24107; } // Perform conversion if ($dateValue >= 1) { - $utcDays = $dateValue - $myExcelBaseDate; + $utcDays = $dateValue - $my_excelBaseDate; $returnValue = round($utcDays * 86400); if (($returnValue <= PHP_INT_MAX) && ($returnValue >= -PHP_INT_MAX)) { $returnValue = (integer) $returnValue; @@ -126,8 +217,10 @@ class PHPExcel_Shared_Date $returnValue = (integer) gmmktime($hours, $mins, $secs); } + $timezoneAdjustment = ($adjustToTimezone) ? self::_getTimezoneAdjustment($timezone, $returnValue) : 0; + // Return - return $returnValue; + return $returnValue + $timezoneAdjustment; } // function ExcelToPHP() @@ -159,7 +252,7 @@ class PHPExcel_Shared_Date * @return mixed Excel date/time value * or boolean FALSE on failure */ - public static function PHPToExcel($dateValue = 0) { + public static function PHPToExcel($dateValue = 0, $adjustToTimezone = FALSE, $timezone = NULL) { $saveTimeZone = date_default_timezone_get(); date_default_timezone_set('UTC'); $retValue = FALSE; @@ -190,16 +283,16 @@ class PHPExcel_Shared_Date * @return long Excel date/time value */ public static function FormattedPHPToExcel($year, $month, $day, $hours=0, $minutes=0, $seconds=0) { - if (self::$ExcelBaseDate == self::CALENDAR_WINDOWS_1900) { + if (self::$_excelBaseDate == self::CALENDAR_WINDOWS_1900) { // // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel // This affects every date following 28th February 1900 // $excel1900isLeapYear = TRUE; if (($year == 1900) && ($month <= 2)) { $excel1900isLeapYear = FALSE; } - $myExcelBaseDate = 2415020; + $my_excelBaseDate = 2415020; } else { - $myExcelBaseDate = 2416481; + $my_excelBaseDate = 2416481; $excel1900isLeapYear = FALSE; } @@ -214,7 +307,7 @@ class PHPExcel_Shared_Date // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) $century = substr($year,0,2); $decade = substr($year,2,2); - $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myExcelBaseDate + $excel1900isLeapYear; + $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $my_excelBaseDate + $excel1900isLeapYear; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; diff --git a/unitTests/Classes/PHPExcel/Shared/DateTest.php b/unitTests/Classes/PHPExcel/Shared/DateTest.php index ea6991e6..72c33589 100644 --- a/unitTests/Classes/PHPExcel/Shared/DateTest.php +++ b/unitTests/Classes/PHPExcel/Shared/DateTest.php @@ -34,6 +34,30 @@ class DateTest extends PHPUnit_Framework_TestCase $this->assertFalse($result); } + public function testSetTimezone() + { + $timezoneValues = array( + 'Europe/Prague', + 'Asia/Tokyo', + 'America/Indiana/Indianapolis', + 'Pacific/Honolulu', + 'Atlantic/St_Helena', + ); + + foreach($timezoneValues as $timezoneValue) { + $result = call_user_func(array('PHPExcel_Shared_Date','setTimezone'),$timezoneValue); + $this->assertTrue($result); + } + + } + + public function testSetTimezoneWithInvalidValue() + { + $unsupportedTimezone = 'Etc/GMT+10'; + $result = call_user_func(array('PHPExcel_Shared_Date','setTimezone'),$unsupportedTimezone); + $this->assertFalse($result); + } + /** * @dataProvider providerDateTimeExcelToPHP1900 */ @@ -161,4 +185,28 @@ class DateTest extends PHPUnit_Framework_TestCase return new testDataFileIterator('rawTestData/Shared/DateTimeFormatCodes.data'); } + /** + * @dataProvider providerDateTimeExcelToPHP1900Timezone + */ + public function testDateTimeExcelToPHP1900Timezone() + { + $result = call_user_func( + array('PHPExcel_Shared_Date','setExcelCalendar'), + PHPExcel_Shared_Date::CALENDAR_WINDOWS_1900 + ); + + $args = func_get_args(); + $expectedResult = array_pop($args); + if ($args[0] < 1) { + $expectedResult += gmmktime(0,0,0); + } + $result = call_user_func_array(array('PHPExcel_Shared_Date','ExcelToPHP'),$args); + $this->assertEquals($expectedResult, $result); + } + + public function providerDateTimeExcelToPHP1900Timezone() + { + return new testDataFileIterator('rawTestData/Shared/DateTimeExcelToPHP1900Timezone.data'); + } + } diff --git a/unitTests/rawTestData/Shared/DateTimeExcelToPHP1900Timezone.data b/unitTests/rawTestData/Shared/DateTimeExcelToPHP1900Timezone.data new file mode 100644 index 00000000..3cf01000 --- /dev/null +++ b/unitTests/rawTestData/Shared/DateTimeExcelToPHP1900Timezone.data @@ -0,0 +1,23 @@ +#Excel DateTimeStamp Adjust Timezone Result Comments +22269, TRUE, 'America/New_York', -285138000 // 19-Dec-1960 00:00:00 UST +25569, TRUE, 'America/New_York', -18000 // PHP Base Date 01-Jan-1970 00:00:00 UST +30292, TRUE, 'America/New_York', 408049200 // 07-Dec-1982 00:00:00 UST +39611, TRUE, 'America/New_York', 1213214400 // 12-Jun-2008 00:00:00 UST +50424, TRUE, 'America/New_York', 2147454000 // PHP 32-bit Latest Date 19-Jan-2038 00:00:00 UST +22345.56789, TRUE, 'America/New_York', -278522534 // 18-May-1903 13:37:46 UST +22345.6789, TRUE, 'America/New_York', -278512943 // 18-Oct-1933 16:17:37 UST +0.5, TRUE, 'America/New_York', 28800 // 12:00:00 UST +0.75, TRUE, 'America/New_York', 50400 // 18:00.00 UST +0.12345, TRUE, 'America/New_York', -3734 // 02:57:46 UST +41215, TRUE, 'America/New_York', 1351800000 // 02-Nov-2012 00:00:00 UST +22269, TRUE, 'Pacific/Auckland', -285076800 // 19-Dec-1960 00:00:00 UST +25569, TRUE, 'Pacific/Auckland', 43200 // PHP Base Date 01-Jan-1970 00:00:00 UST +30292, TRUE, 'Pacific/Auckland', 408114000 // 07-Dec-1982 00:00:00 UST +39611, TRUE, 'Pacific/Auckland', 1213272000 // 12-Jun-2008 00:00:00 UST +50423.5, TRUE, 'Pacific/Auckland', 2147475600 // PHP 32-bit Latest Date 19-Jan-2038 00:00:00 UST +22345.56789, TRUE, 'Pacific/Auckland', -278461334 // 18-May-1903 13:37:46 UST +22345.6789, TRUE, 'Pacific/Auckland', -278451743 // 18-Oct-1933 16:17:37 UST +0.5, TRUE, 'Pacific/Auckland', 90000 // 12:00:00 UST +0.75, TRUE, 'Pacific/Auckland', 111600 // 18:00.00 UST +0.12345, TRUE, 'Pacific/Auckland', 57466 // 02:57:46 UST +41215, TRUE, 'Pacific/Auckland', 1351861200 // 02-Nov-2012 00:00:00 UST diff --git a/unitTests/rawTestData/Shared/PasswordHashes.data b/unitTests/rawTestData/Shared/PasswordHashes.data index 8ceffa45..c6846812 100644 --- a/unitTests/rawTestData/Shared/PasswordHashes.data +++ b/unitTests/rawTestData/Shared/PasswordHashes.data @@ -1,6 +1,6 @@ "PHPExcel", "8053" "Mark Baker", "877D" -"I<3Am3l1a/*", "E3C8" +"!+&=()~§±æþ", "C0EA" "μυστικό κωδικό πρόσβασης", "FFFF26DD" "গোপন পাসওয়ার্ড", "E858" "Секретный пароль", "EA5F"