Providing support for timezone adjustments in date/time conversion methods

This commit is contained in:
Mark Baker 2012-11-02 23:07:01 +00:00
parent 63c5cbc98e
commit 53a32ce7f7
4 changed files with 187 additions and 23 deletions

View File

@ -67,18 +67,26 @@ class PHPExcel_Shared_Date
* @private * @private
* @var int * @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) * 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 * @return boolean Success or failure
*/ */
public static function setExcelCalendar($baseDate) { public static function setExcelCalendar($baseDate) {
if (($baseDate == self::CALENDAR_WINDOWS_1900) || if (($baseDate == self::CALENDAR_WINDOWS_1900) ||
($baseDate == self::CALENDAR_MAC_1904)) { ($baseDate == self::CALENDAR_MAC_1904)) {
self::$ExcelBaseDate = $baseDate; self::$_excelBaseDate = $baseDate;
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
@ -88,33 +96,116 @@ class PHPExcel_Shared_Date
/** /**
* Return the Excel calendar (Windows 1900 or Mac 1904) * 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() { public static function getExcelCalendar() {
return self::$ExcelBaseDate; return self::$_excelBaseDate;
} // function getExcelCalendar() } // function getExcelCalendar()
/**
* Validate a Timezone value
*
* @param string $timezone Time zone (e.g. 'Europe/London')
* @return boolean Success or failure
*/
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 {
$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 * Convert a date from Excel to PHP
* *
* @param long $dateValue Excel date/time value * @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 * @return long PHP serialized date/time
*/ */
public static function ExcelToPHP($dateValue = 0) { public static function ExcelToPHP($dateValue = 0, $adjustToTimezone = FALSE, $timezone = NULL) {
if (self::$ExcelBaseDate == self::CALENDAR_WINDOWS_1900) { if (self::$_excelBaseDate == self::CALENDAR_WINDOWS_1900) {
$myExcelBaseDate = 25569; $my_excelBaseDate = 25569;
// Adjust for the spurious 29-Feb-1900 (Day 60) // Adjust for the spurious 29-Feb-1900 (Day 60)
if ($dateValue < 60) { if ($dateValue < 60) {
--$myExcelBaseDate; --$my_excelBaseDate;
} }
} else { } else {
$myExcelBaseDate = 24107; $my_excelBaseDate = 24107;
} }
// Perform conversion // Perform conversion
if ($dateValue >= 1) { if ($dateValue >= 1) {
$utcDays = $dateValue - $myExcelBaseDate; $utcDays = $dateValue - $my_excelBaseDate;
$returnValue = round($utcDays * 86400); $returnValue = round($utcDays * 86400);
if (($returnValue <= PHP_INT_MAX) && ($returnValue >= -PHP_INT_MAX)) { if (($returnValue <= PHP_INT_MAX) && ($returnValue >= -PHP_INT_MAX)) {
$returnValue = (integer) $returnValue; $returnValue = (integer) $returnValue;
@ -126,8 +217,10 @@ class PHPExcel_Shared_Date
$returnValue = (integer) gmmktime($hours, $mins, $secs); $returnValue = (integer) gmmktime($hours, $mins, $secs);
} }
$timezoneAdjustment = ($adjustToTimezone) ? self::_getTimezoneAdjustment($timezone, $returnValue) : 0;
// Return // Return
return $returnValue; return $returnValue + $timezoneAdjustment;
} // function ExcelToPHP() } // function ExcelToPHP()
@ -159,7 +252,7 @@ class PHPExcel_Shared_Date
* @return mixed Excel date/time value * @return mixed Excel date/time value
* or boolean FALSE on failure * 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(); $saveTimeZone = date_default_timezone_get();
date_default_timezone_set('UTC'); date_default_timezone_set('UTC');
$retValue = FALSE; $retValue = FALSE;
@ -190,16 +283,16 @@ class PHPExcel_Shared_Date
* @return long Excel date/time value * @return long Excel date/time value
*/ */
public static function FormattedPHPToExcel($year, $month, $day, $hours=0, $minutes=0, $seconds=0) { 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 // 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 // This affects every date following 28th February 1900
// //
$excel1900isLeapYear = TRUE; $excel1900isLeapYear = TRUE;
if (($year == 1900) && ($month <= 2)) { $excel1900isLeapYear = FALSE; } if (($year == 1900) && ($month <= 2)) { $excel1900isLeapYear = FALSE; }
$myExcelBaseDate = 2415020; $my_excelBaseDate = 2415020;
} else { } else {
$myExcelBaseDate = 2416481; $my_excelBaseDate = 2416481;
$excel1900isLeapYear = FALSE; $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) // 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); $century = substr($year,0,2);
$decade = substr($year,2,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; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;

View File

@ -34,6 +34,30 @@ class DateTest extends PHPUnit_Framework_TestCase
$this->assertFalse($result); $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 * @dataProvider providerDateTimeExcelToPHP1900
*/ */
@ -161,4 +185,28 @@ class DateTest extends PHPUnit_Framework_TestCase
return new testDataFileIterator('rawTestData/Shared/DateTimeFormatCodes.data'); 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');
}
} }

View File

@ -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

View File

@ -1,6 +1,6 @@
"PHPExcel", "8053" "PHPExcel", "8053"
"Mark Baker", "877D" "Mark Baker", "877D"
"I<3Am3l1a/*", "E3C8" "!+&=()~§±æþ", "C0EA"
"μυστικό κωδικό πρόσβασης", "FFFF26DD" "μυστικό κωδικό πρόσβασης", "FFFF26DD"
"গোপন পাসওয়ার্ড", "E858" "গোপন পাসওয়ার্ড", "E858"
"Секретный пароль", "EA5F" "Секретный пароль", "EA5F"