/usr/share/php/Horde/Mapi/Timezone.php is in php-horde-mapi 1.0.3-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 | <?php
/**
* Horde_Mapi_Util_Timezone::
*
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @copyright 2009-2014 Horde LLC (http://www.horde.org)
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @package Mapi_Utils
*/
/**
* Utility functions for dealing with Microsoft MAPI Timezone format.
*
* Copyright 2009-2014 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.
*
* Code dealing with searching for a timezone identifier from an AS timezone
* blob inspired by code in the Tine20 Project (http://tine20.org).
*
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
* @copyright 2009-2014 Horde LLC (http://www.horde.org)
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @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:
* <pre>
* typedef struct TIME_ZONE_INFORMATION {
* LONG Bias;
* WCHAR StandardName[32];
* SYSTEMTIME StandardDate;
* LONG StandardBias;
* WCHAR DaylightName[32];
* SYSTEMTIME DaylightDate;
* LONG DaylightBias;};
* </pre>
*
* With the SYSTEMTIME format being:
* <pre>
* typedef struct _SYSTEMTIME {
* WORD wYear;
* WORD wMonth;
* WORD wDayOfWeek;
* WORD wDay;
* WORD wHour;
* WORD wMinute;
* WORD wSecond;
* WORD wMilliseconds;
* } SYSTEMTIME, *PSYSTEMTIME;
* </pre>
*
* 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 (PHP_MINOR_VERSION >= 5) {
$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;
}
}
}
|