* @package Mapi_Utils
*/
class Horde_Mapi_Timezone
{
/**
* Date to use as start date when iterating through offsets looking for a
* transition.
*
* @var Horde_Date
*/
protected $_startDate;
/**
* Convert a timezone from the MAPI base64 structure to a TZ offset
* hash.
*
* @param base64 encoded timezone structure defined by MS as:
*
* typedef struct TIME_ZONE_INFORMATION {
* LONG Bias;
* WCHAR StandardName[32];
* SYSTEMTIME StandardDate;
* LONG StandardBias;
* WCHAR DaylightName[32];
* SYSTEMTIME DaylightDate;
* LONG DaylightBias;};
*
*
* With the SYSTEMTIME format being:
*
* typedef struct _SYSTEMTIME {
* WORD wYear;
* WORD wMonth;
* WORD wDayOfWeek;
* WORD wDay;
* WORD wHour;
* WORD wMinute;
* WORD wSecond;
* WORD wMilliseconds;
* } SYSTEMTIME, *PSYSTEMTIME;
*
*
* See: http://msdn.microsoft.com/en-us/library/ms724950%28VS.85%29.aspx
* and: http://msdn.microsoft.com/en-us/library/ms725481%28VS.85%29.aspx
*
* @return array Hash of offset information
*/
static public function getOffsetsFromSyncTZ($data)
{
if (version_compare(PHP_VERSION, '5.5.0-dev', '>=')) {
$format = 'lbias/Z64stdname/vstdyear/vstdmonth/vstdday/vstdweek/vstdhour/vstdminute/vstdsecond/vstdmillis/'
. 'lstdbias/Z64dstname/vdstyear/vdstmonth/vdstday/vdstweek/vdsthour/vdstminute/vdstsecond/vdstmillis/'
. 'ldstbias';
} else {
$format = 'lbias/a64stdname/vstdyear/vstdmonth/vstdday/vstdweek/vstdhour/vstdminute/vstdsecond/vstdmillis/'
. 'lstdbias/a64dstname/vdstyear/vdstmonth/vdstday/vdstweek/vdsthour/vdstminute/vdstsecond/vdstmillis/'
. 'ldstbias';
}
$tz = unpack($format, base64_decode($data));
$tz['timezone'] = $tz['bias'];
$tz['timezonedst'] = $tz['dstbias'];
if (!Horde_Mapi::isLittleEndian()) {
$tz['bias'] = Horde_Mapi::chbo($tz['bias']);
$tz['stdbias'] = Horde_Mapi::chbo($tz['stdbias']);
$tz['dstbias'] = Horde_Mapi::chbo($tz['dstbias']);
}
return $tz;
}
/**
* Build an MAPI TZ blob given a TZ Offset hash.
*
* @param array $offsets A TZ offset hash
*
* @return string A base64_encoded MAPI Timezone structure suitable
* for transmitting via wbxml.
*/
static public function getSyncTZFromOffsets(array $offsets)
{
if (!Horde_Mapi::isLittleEndian()) {
$offsets['bias'] = Horde_Mapi::chbo($offsets['bias']);
$offsets['stdbias'] = Horde_Mapi::chbo($offsets['stdbias']);
$offsets['dstbias'] = Horde_Mapi::chbo($offsets['dstbias']);
}
$packed = pack('la64vvvvvvvvla64vvvvvvvvl',
$offsets['bias'], '', 0, $offsets['stdmonth'], $offsets['stdday'], $offsets['stdweek'], $offsets['stdhour'], $offsets['stdminute'], $offsets['stdsecond'], $offsets['stdmillis'],
$offsets['stdbias'], '', 0, $offsets['dstmonth'], $offsets['dstday'], $offsets['dstweek'], $offsets['dsthour'], $offsets['dstminute'], $offsets['dstsecond'], $offsets['dstmillis'],
$offsets['dstbias']);
return base64_encode($packed);
}
/**
* Create a offset hash suitable for use in ActiveSync transactions
*
* @param Horde_Date $date A date object representing the date to base the
* the tz data on.
*
* @return array An offset hash.
*/
static public function getOffsetsFromDate(Horde_Date $date)
{
$offsets = array(
'bias' => 0,
'stdname' => '',
'stdyear' => 0,
'stdmonth' => 0,
'stdday' => 0,
'stdweek' => 0,
'stdhour' => 0,
'stdminute' => 0,
'stdsecond' => 0,
'stdmillis' => 0,
'stdbias' => 0,
'dstname' => '',
'dstyear' => 0,
'dstmonth' => 0,
'dstday' => 0,
'dstweek' => 0,
'dsthour' => 0,
'dstminute' => 0,
'dstsecond' => 0,
'dstmillis' => 0,
'dstbias' => 0
);
$timezone = $date->toDateTime()->getTimezone();
list($std, $dst) = self::_getTransitions($timezone, $date);
if ($std) {
$offsets['bias'] = $std['offset'] / 60 * -1;
if ($dst) {
$offsets = self::_generateOffsetsForTransition($offsets, $std, 'std');
$offsets = self::_generateOffsetsForTransition($offsets, $dst, 'dst');
$offsets['stdhour'] += $dst['offset'] / 3600;
$offsets['dsthour'] += $std['offset'] / 3600;
$offsets['dstbias'] = ($dst['offset'] - $std['offset']) / 60 * -1;
}
}
return $offsets;
}
/**
* Get the transition data for moving from DST to STD time.
*
* @param DateTimeZone $timezone The timezone to get the transition for
* @param Horde_Date $date The date to start from. Really only the
* year we are interested in is needed.
*
* @return array An array containing the the STD and DST transitions
*/
static protected function _getTransitions(DateTimeZone $timezone, Horde_Date $date)
{
$std = $dst = array();
$transitions = $timezone->getTransitions(
mktime(0, 0, 0, 12, 1, $date->year - 1),
mktime(24, 0, 0, 12, 31, $date->year)
);
foreach ($transitions as $i => $transition) {
try {
$d = new Horde_Date($transition['time']);
$d->setTimezone('UTC');
} catch (Exception $e) {
continue;
}
if (($d->format('Y') == $date->format('Y')) && isset($transitions[$i + 1])) {
$next = new Horde_Date($transitions[$i + 1]['ts']);
if ($d->format('Y') == $next->format('Y')) {
$dst = $transition['isdst'] ? $transition : $transitions[$i + 1];
$std = $transition['isdst'] ? $transitions[$i + 1] : $transition;
} else {
$dst = $transition['isdst'] ? $transition: null;
$std = $transition['isdst'] ? null : $transition;
}
break;
} elseif ($i == count($transitions) - 1) {
$std = $transition;
}
}
return array($std, $dst);
}
/**
* Calculate the offsets for the specified transition
*
* @param array $offsets A TZ offset hash
* @param array $transition A transition hash
* @param string $type Transition type - dst or std
*
* @return array A populated offset hash
*/
static protected function _generateOffsetsForTransition(array $offsets, array $transition, $type)
{
// We can't use Horde_Date directly here, since it is unable to
// properly convert to UTC from local ON the exact hour of a std -> dst
// transition. This is due to a conversion to DateTime in the localtime
// zone internally before the timezone change is applied
$transitionDate = new DateTime($transition['time']);
$transitionDate->setTimezone(new DateTimeZone('UTC'));
$transitionDate = new Horde_Date($transitionDate);
$offsets[$type . 'month'] = $transitionDate->format('n');
$offsets[$type . 'day'] = $transitionDate->format('w');
$offsets[$type . 'minute'] = (int)$transitionDate->format('i');
$offsets[$type . 'hour'] = (int)$transitionDate->format('H');
for ($i = 5; $i > 0; $i--) {
if (self::_isNthOcurrenceOfWeekdayInMonth($transition['ts'], $i)) {
$offsets[$type . 'week'] = $i;
break;
}
}
return $offsets;
}
/**
* Attempt to guess the timezone identifier from the $offsets array.
*
* @param array|string $offsets The timezone to check. Either an array
* of offsets or an activesynz tz blob.
* @param string $expectedTimezone The expected timezone. If not empty, and
* present in the results, will return.
*
* @return string The timezone identifier
*/
public function getTimezone($offsets, $expectedTimezone = null)
{
$timezones = $this->getListOfTimezones($offsets, $expectedTimezone);
if (isset($timezones[$expectedTimezone])) {
return $expectedTimezone;
} else {
return current($timezones);
}
}
/**
* Get the list of timezone identifiers that match the given offsets, having
* a preference for $expectedTimezone if it's present in the results.
*
* @param array|string $offsets Either an offset array, or a AS timezone
* structure.
* @param string $expectedTimezone The expected timezone.
*
* @return array An array of timezone identifiers
*/
public function getListOfTimezones($offsets, $expectedTimezone = null)
{
if (is_string($offsets)) {
$offsets = self::getOffsetsFromSyncTZ($offsets);
}
$this->_setDefaultStartDate($offsets);
$timezones = array();
foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) {
$timezone = new DateTimeZone($timezoneIdentifier);
if (false !== ($matchingTransition = $this->_checkTimezone($timezone, $offsets))) {
if ($timezoneIdentifier == $expectedTimezone) {
$timezones = array($timezoneIdentifier => $matchingTransition['abbr']);
break;
} else {
$timezones[$timezoneIdentifier] = $matchingTransition['abbr'];
}
}
}
if (empty($timezones)) {
throw new Horde_Mapi_Exception('No timezone found for the given offsets');
}
return $timezones;
}
/**
* Set default value for $_startDate.
*
* Tries to guess the correct startDate depending on object property falls
* back to current date.
*
* @param array $offsets Offsets may be avaluated for a given start year
*/
protected function _setDefaultStartDate(array $offsets = null)
{
if (!empty($this->_startDate)) {
return;
}
if (!empty($offsets['stdyear'])) {
$this->_startDate = new Horde_Date($offsets['stdyear'] . '-01-01');
} else {
$start = new Horde_Date(time());
$start->year--;
$this->_startDate = $start;
}
}
/**
* Check if the given timezone matches the offsets and also evaluate the
* daylight saving time transitions for this timezone if necessary.
*
* @param DateTimeZone $timezone The timezone to check.
* @param array $offsets The offsets to check.
*
* @return array|boolean An array of transition data or false if timezone
* does not match offset.
*/
protected function _checkTimezone(DateTimeZone $timezone, array $offsets)
{
list($std, $dst) = $this->_getTransitions($timezone, $this->_startDate);
if ($this->_checkTransition($std, $dst, $offsets)) {
return $std;
}
return false;
}
/**
* Check if the given standardTransition and daylightTransition match to the
* given offsets.
*
* @param array $std The Standard transition date.
* @param array $dst The DST transition date.
* @param array $offsets The offsets to check.
*
* @return boolean
*/
protected function _checkTransition(array $std, array $dst, array $offsets)
{
if (empty($std) || empty($offsets)) {
return false;
}
$standardOffset = ($offsets['bias'] + $offsets['stdbias']) * 60 * -1;
// check each condition in a single if statement and break the chain
// when one condition is not met - for performance reasons
if ($standardOffset == $std['offset']) {
if ((empty($offsets['dstmonth']) && (empty($dst) || empty($dst['isdst']))) ||
(empty($dst) && !empty($offsets['dstmonth']))) {
// Offset contains DST, but no dst to compare
return true;
}
$daylightOffset = ($offsets['bias'] + $offsets['dstbias']) * 60 * -1;
// the milestone is sending a positive value for daylightBias while it should send a negative value
$daylightOffsetMilestone = ($offsets['dstbias'] + ($offsets['dstbias'] * -1) ) * 60 * -1;
if ($daylightOffset == $dst['offset'] || $daylightOffsetMilestone == $dst['offset']) {
$standardParsed = new DateTime($std['time']);
$daylightParsed = new DateTime($dst['time']);
if ($standardParsed->format('n') == $offsets['stdmonth'] &&
$daylightParsed->format('n') == $offsets['dstmonth'] &&
$standardParsed->format('w') == $offsets['stdday'] &&
$daylightParsed->format('w') == $offsets['dstday'])
{
return self::_isNthOcurrenceOfWeekdayInMonth($dst['ts'], $offsets['dstweek']) &&
self::_isNthOcurrenceOfWeekdayInMonth($std['ts'], $offsets['stdweek']);
}
}
}
return false;
}
/**
* Test if the weekday of the given timestamp is the nth occurence of this
* weekday within its month, where '5' indicates the last occurrence even if
* there is less than five occurrences.
*
* @param integer $timestamp The timestamp to check.
* @param integer $occurence 1 to 5, where 5 indicates the final occurrence
* during the month if that day of the week does
* not occur 5 times
* @return boolean
*/
static protected function _isNthOcurrenceOfWeekdayInMonth($timestamp, $occurence)
{
$original = new Horde_Date($timestamp);
$original->setTimezone('UTC');
if ($occurence == 5) {
$modified = $original->add(array('mday' => 7));
return $modified->month > $original->month;
} else {
$modified = $original->sub(array('mday' => 7 * $occurence));
$modified2 = $original->sub(array('mday' => 7 * ($occurence - 1)));
return $modified->month < $original->month &&
$modified2->month == $original->month;
}
}
}
Horde_Mapi-1.0.2/lib/Horde/Mapi.php 0000664 0000766 0000024 00000014416 12266561121 013660 0 ustar
* @package Mapi_Utils
*/
/**
* Utility functions for dealing with Microsoft MAPI structures.
*
* Copyright 2009-2013 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @copyright 2009-2013 Horde LLC (http://www.horde.org)
* @author Michael J Rubinsky
* @package Mapi_Utils
*/
class Horde_Mapi
{
/**
* Determine if the current machine is little endian.
*
* @return boolean True if endianness is little endian, otherwise false.
*/
static public function isLittleEndian()
{
$testint = 0x00FF;
$p = pack('S', $testint);
return ($testint === current(unpack('v', $p)));
}
/**
* Change the byte order of a number. Used to allow big endian machines to
* decode the timezone blobs, which are encoded in little endian order.
*
* @param integer $num The number to reverse.
*
* @return integer The number, in the reverse byte order.
*/
static public function chbo($num)
{
$u = unpack('l', strrev(pack('l', $num)));
return $u[1];
}
/**
* Obtain the UID from a MAPI GOID.
*
* See http://msdn.microsoft.com/en-us/library/hh338153%28v=exchg.80%29.aspx
*
* @param string $goid Base64 encoded Global Object Identifier.
*
* @return string The UID
*/
static public function getUidFromGoid($goid)
{
$goid = base64_decode($goid);
// First, see if it's an Outlook UID or not.
if (substr($goid, 40, 8) == 'vCal-Uid') {
// For vCal UID values:
// Bytes 37 - 40 contain length of data and padding
// Bytes 41 - 48 are == vCal-Uid
// Bytes 53 until next to the last byte (/0) contain the UID.
return trim(substr($goid, 52, strlen($goid) - 1));
} else {
// If it's not a vCal UID, then it is Outlook style UID:
// The entire decoded goid is converted to hex representation with
// bytes 17 - 20 converted to zero
$hex = array();
foreach (str_split($goid) as $chr) {
$hex[] = sprintf('%02X', ord($chr));
}
array_splice($hex, 16, 4, array('00', '00', '00', '00'));
return implode('', $hex);
}
}
/**
* Create a MAPI GOID from a UID
* See http://msdn.microsoft.com/en-us/library/ee157690%28v=exchg.80%29
*
* @param string $uid The UID value to encode.
*
* @return string A Base64 encoded GOID
*/
static public function createGoid($uid, $options = array())
{
// Bytes 1 - 16 MUST be equal to the GOID identifier:
$arrayid = '040000008200E00074C5B7101A82E008';
// Bytes 17 - 20 - Exception replace time (YH YL M D)
$exception = '00000000';
// Bytes 21 - 28 The 8 byte creation time (can be all zeros if not available).
$creationtime = '0000000000000000';
// Bytes 29 - 36 Reserved 8 bytes must be all zeros.
$reserved = '0000000000000000';
// Bytes 37 - 40 - A long value describing the size of the UID data.
$size = strlen($uid);
// Bytes 41 - 52 - MUST BE vCal-Uid 0x01 0x00 0x00 0x00
$vCard = '7643616C2D55696401000000';
// The UID Data:
$hexuid = '';
foreach (str_split($uid) as $chr) {
$hexuid .= sprintf('%02X', ord($chr));
}
// Pack it
$goid = pack('H*H*H*H*VH*H*x', $arrayid, $exception, $creationtime, $reserved, $size, $vCard, $hexuid);
return base64_encode($goid);
}
/**
* Converts a Windows FILETIME value to a unix timestamp.
*
* Adapted from:
* http://stackoverflow.com/questions/610603/help-me-translate-long-value-expressed-in-hex-back-in-to-a-date-time
*
* @param string $ft Binary representation of FILETIME from a pTypDate
* MAPI property.
*
* @return integer The unix timestamp.
* @throws Horde_Mapi_Exception
*/
static public function filetimeToUnixtime($ft)
{
$ft = bin2hex($ft);
$dtval = substr($ft, 0, 16); // clip overlength string
$dtval = str_pad($dtval, 16, '0'); // pad underlength string
$quad = self::_flipEndian($dtval);
$win64_datetime = self::_hexToBcint($quad);
return self::_win64ToUnix($win64_datetime);
}
// swap little-endian to big-endian
static protected function _flipEndian($str)
{
// make sure #digits is even
if ( strlen($str) & 1 )
$str = '0' . $str;
$t = '';
for ($i = strlen($str)-2; $i >= 0; $i-=2)
$t .= substr($str, $i, 2);
return $t;
}
// convert hex string to BC-int
static protected function _hexToBcint($str)
{
if (!extension_loaded('bcmath')) {
throw new Horde_Mapi_Exception('bcmath extension not loaded.');
}
$hex = array(
'0'=>'0', '1'=>'1', '2'=>'2', '3'=>'3', '4'=>'4',
'5'=>'5', '6'=>'6', '7'=>'7', '8'=>'8', '9'=>'9',
'a'=>'10', 'b'=>'11', 'c'=>'12', 'd'=>'13', 'e'=>'14', 'f'=>'15',
'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15'
);
$bci = '0';
$len = strlen($str);
for ($i = 0; $i < $len; ++$i) {
$bci = bcmul($bci, '16');
$ch = $str[$i];
if (isset($hex[$ch]))
$bci = bcadd($bci, $hex[$ch]);
}
return $bci;
}
static protected function _win64ToUnix($bci)
{
if (!extension_loaded('bcmath')) {
throw new Horde_Mapi_Exception('bcmath extension not loaded.');
}
// Unix epoch as a Windows file date-time value
$magicnum = '116444735995904000';
$t = bcsub($bci, $magicnum); // Cast to Unix epoch
return bcdiv($t, '10000000', 0); // Convert from ticks to seconds
}
}
Horde_Mapi-1.0.2/test/Horde/Mapi/fixtures/filetime 0000664 0000766 0000024 00000000010 12266561121 016733 0 ustar čEj Ď Horde_Mapi-1.0.2/test/Horde/Mapi/AllTests.php 0000664 0000766 0000024 00000000132 12266561121 015612 0 ustar run();
Horde_Mapi-1.0.2/test/Horde/Mapi/bootstrap.php 0000664 0000766 0000024 00000000143 12266561121 016076 0 ustar
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Mapi_Utils
* @subpackage UnitTests
*/
/**
* @author Michael J Rubinsky
* @category Horde
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @package Mapi_Utils
* @subpackage UnitTests
*/
class Horde_Mapi_MapiTest extends PHPUnit_Framework_TestCase
{
public function testFiletimeToUnixTime()
{
$data = file_get_contents(__DIR__ . '/fixtures/filetime');
try {
$this->assertEquals(Horde_Mapi::filetimeToUnixtime($data), 1387818000);
} catch (Horde_Mapi_Exception $e) {
$this->markTestSkipped("bcmath extension isn't loaded");
}
}
/**
* Test parsing GOID value.
*/
public function testParseGlobalObjectId()
{
// Outlook UID
$fixture = 'BAAAAIIA4AB0xbcQGoLgCAfUCRDgQMnBJoXEAQAAAAAAAAAAEAAAAAvw7UtuTulOnjnjhns3jvM=';
$uid = Horde_Mapi::getUidFromGoid($fixture);
$this->assertEquals(
'040000008200E00074C5B7101A82E00800000000E040C9C12685C4010000000000000000100000000BF0ED4B6E4EE94E9E39E3867B378EF3',
$uid);
// vCal
$fixture = 'BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAAHZDYWwtVWlkAQAAAHs4MTQxMkQzQy0yQTI0LTRFOUQtQjIwRS0xMUY3QkJFOTI3OTl9AA==';
$uid = Horde_Mapi::getUidFromGoid($fixture);
$this->assertEquals('{81412D3C-2A24-4E9D-B20E-11F7BBE92799}', $uid);
}
}
Horde_Mapi-1.0.2/test/Horde/Mapi/phpunit.xml 0000664 0000766 0000024 00000000056 12266561121 015564 0 ustar
Horde_Mapi-1.0.2/test/Horde/Mapi/TimezoneTest.php 0000664 0000766 0000024 00000020544 12266561121 016522 0 ustar
* @category Horde
* @package Mapi_Utils
*/
class Horde_Mapi_TimezoneTest extends Horde_Test_Case
{
protected $_offsets = array(
'America/New_York' => array('bias' => 300,
'stdname' => '',
'stdyear' => 0,
'stdmonth' => 11,
'stdday' => 0,
'stdweek' => 1,
'stdhour' => 2,
'stdminute' => 0,
'stdsecond' => 0,
'stdmillis' => 0,
'stdbias' => 0,
'dstname' => '',
'dstyear' => 0,
'dstmonth' => 3,
'dstday' => 0,
'dstweek' => 2,
'dsthour' => 2,
'dstminute' => 0,
'dstsecond' => 0,
'dstmillis' => 0,
'dstbias' => -60),
'Europe/Berlin' => array('bias' => -60,
'stdname' => '',
'stdyear' => 0,
'stdmonth' => 10,
'stdday' => 0,
'stdweek' => 5,
'stdhour' => 3,
'stdminute' => 0,
'stdsecond' => 0,
'stdmillis' => 0,
'stdbias' => 0,
'dstname' => '',
'dstyear' => 0,
'dstmonth' => 3,
'dstday' => 0,
'dstweek' => 5,
'dsthour' => 2,
'dstminute' => 0,
'dstsecond' => 0,
'dstmillis' => 0,
'dstbias' => -60),
'America/Los_Angeles' => array('bias' => 480,
'stdname' => '',
'stdyear' => 0,
'stdmonth' => 11,
'stdday' => 0,
'stdweek' => 1,
'stdhour' => 2,
'stdminute' => 0,
'stdsecond' => 0,
'stdmillis' => 0,
'stdbias' => 0,
'dstname' => '',
'dstyear' => 0,
'dstmonth' => 3,
'dstday' => 0,
'dstweek' => 2,
'dsthour' => 2,
'dstminute' => 0,
'dstsecond' => 0,
'dstmillis' => 0,
'dstbias' => -60,
'timezone' => 480,
'timezonedst' => -60),
'America/Phoenix' => array('bias' => 420,
'stdname' => '',
'stdyear' => 0,
'stdmonth' => 0,
'stdday' => 0,
'stdweek' => 0,
'stdhour' => 0,
'stdminute' => 0,
'stdsecond' => 0,
'stdmillis' => 0,
'stdbias' => 0,
'dstname' => '',
'dstyear' => 0,
'dstmonth' => 0,
'dstday' => 0,
'dstweek' => 0,
'dsthour' => 0,
'dstminute' => 0,
'dstsecond' => 0,
'dstmillis' => 0,
'dstbias' => 0)
);
protected $_packed = array(
'America/New_York' => 'LAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==',
'America/Los_Angeles' => '4AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAACAAIAAAAAAAAAxP///w==',
'Europe/Berlin' => 'xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==',
'America/Phoenix' => 'pAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==',
);
public function setUp()
{
$this->_oldTimezone = date_default_timezone_get();
date_default_timezone_set('America/New_York');
}
public function tearDown()
{
date_default_timezone_set($this->_oldTimezone);
}
/**
* Test building an Offset hash from a given ActiveSync style base64 encoded
* timezone structure.
*/
public function testOffsetsFromSyncTZ()
{
foreach ($this->_packed as $tz => $blob) {
$offsets = Horde_Mapi_Timezone::getOffsetsFromSyncTZ($blob);
foreach ($this->_offsets[$tz] as $key => $value) {
$this->assertEquals($value, $offsets[$key]);
}
}
}
/**
* Test creating a Offset hash for a given timezone.
*/
public function testGetOffsetsFromDate()
{
// The actual time doesn't matter, we really only need a year and a
// timezone that we are interested in.
foreach ($this->_offsets as $tz => $expected) {
$date = new Horde_Date('2011-07-01', $tz);
$offsets = Horde_Mapi_Timezone::getOffsetsFromDate($date);
foreach ($offsets as $key => $value) {
$this->assertEquals($expected[$key], $value);
}
}
}
/**
* Test generating an ActiveSync TZ structure given a TZ Offset hash
*/
public function testGetSyncTZFromOffsets()
{
foreach ($this->_offsets as $tz => $offsets) {
$blob = Horde_Mapi_Timezone::getSyncTZFromOffsets($offsets);
$this->assertEquals($this->_packed[$tz], $blob);
}
}
/**
* Test guessing a timezone identifier from an ActiveSync timezone
* structure.
*/
public function testGuessTimezoneFromOffsets()
{
$timezones = new Horde_Mapi_Timezone();
// Test general functionality, with expected timezone.
foreach ($this->_packed as $tz => $blob) {
$guessed = $timezones->getTimezone($blob, $tz);
$this->assertEquals($tz, $guessed);
}
// Test without a known timezone
$guessed = $timezones->getTimezone($this->_packed['America/New_York']);
$this->assertEquals('EST', $guessed);
$guessed = $timezones->getTimezone($this->_packed['Europe/Berlin']);
$this->assertEquals('CET', $guessed);
}
}