/** * Returns an array containing time zone data from VTimeZone standard/daylight instances * * @param object $vtzc, an iCalcreator calendar standard/daylight instance * @return array, time zone data; array before(offsetHis, offsetSec), array after(offsetHis, offsetSec, tzname) * */ public static function expandTimezoneDates($vtzc) { $tzdates = array(); // prepare time zone "description" to attach to each change $tzbefore = array(); $tzbefore['offsetHis'] = $vtzc->getProperty('tzoffsetfrom'); $tzbefore['offsetSec'] = ICalUtilityFunctions::_tz2offset($tzbefore['offsetHis']); if ('-' != substr((string) $tzbefore['offsetSec'], 0, 1) && '+' != substr((string) $tzbefore['offsetSec'], 0, 1)) { $tzbefore['offsetSec'] = '+' . $tzbefore['offsetSec']; } $tzafter = array(); $tzafter['offsetHis'] = $vtzc->getProperty('tzoffsetto'); $tzafter['offsetSec'] = ICalUtilityFunctions::_tz2offset($tzafter['offsetHis']); if ('-' != substr((string) $tzafter['offsetSec'], 0, 1) && '+' != substr((string) $tzafter['offsetSec'], 0, 1)) { $tzafter['offsetSec'] = '+' . $tzafter['offsetSec']; } if (FALSE === ($tzafter['tzname'] = $vtzc->getProperty('tzname'))) { $tzafter['tzname'] = $tzafter['offsetHis']; } // find out where to start from $dtstart = $vtzc->getProperty('dtstart'); $dtstarttimestamp = mktime($dtstart['hour'], $dtstart['min'], $dtstart['sec'], $dtstart['month'], $dtstart['day'], $dtstart['year']); if (!isset($dtstart['unparsedtext'])) { // ?? $dtstart['unparsedtext'] = sprintf('%04d%02d%02dT%02d%02d%02d', $dtstart['year'], $dtstart['month'], $dtstart['day'], $dtstart['hour'], $dtstart['min'], $dtstart['sec']); } if ($dtstarttimestamp == 0) { // it seems that the dtstart string may not have parsed correctly // let's set a timestamp starting from 1902, using the time part of the original string // so that the time will change at the right time of day // at worst we'll get midnight again $origdtstartsplit = explode('T', $dtstart['unparsedtext']); $dtstarttimestamp = strtotime("19020101", 0); $dtstarttimestamp = strtotime($origdtstartsplit[1], $dtstarttimestamp); } // the date (in dtstart and opt RDATE/RRULE) is ALWAYS LOCAL (not utc!!), adjust from 'utc' to 'local' timestamp $diff = -1 * $tzbefore['offsetSec']; $dtstarttimestamp += $diff; // add this (start) change to the array of changes $tzdates[] = array('timestamp' => $dtstarttimestamp, 'tzbefore' => $tzbefore, 'tzafter' => $tzafter); $datearray = getdate($dtstarttimestamp); // save original array to use time parts, because strtotime (used below) apparently loses the time $changetime = $datearray; // generate dates according to an RRULE line $rrule = $vtzc->getProperty('rrule'); if (is_array($rrule)) { if ($rrule['FREQ'] == 'YEARLY') { // calculate transition dates starting from DTSTART $offsetchangetimestamp = $dtstarttimestamp; // calculate transition dates until 10 years in the future $stoptimestamp = strtotime("+10 year", time()); // if UNTIL is set, calculate until then (however far ahead) if (isset($rrule['UNTIL']) && $rrule['UNTIL'] != '') { $stoptimestamp = mktime($rrule['UNTIL']['hour'], $rrule['UNTIL']['min'], $rrule['UNTIL']['sec'], $rrule['UNTIL']['month'], $rrule['UNTIL']['day'], $rrule['UNTIL']['year']); } $count = 0; $stopcount = isset($rrule['COUNT']) ? $rrule['COUNT'] : 0; $daynames = array('SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday', 'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday', 'SA' => 'Saturday'); // repeat so long as we're between DTSTART and UNTIL, or we haven't prepared COUNT dates while ($offsetchangetimestamp < $stoptimestamp && ($stopcount == 0 || $count < $stopcount)) { // break up the timestamp into its parts $datearray = getdate($offsetchangetimestamp); if (isset($rrule['BYMONTH']) && $rrule['BYMONTH'] != 0) { // set the month $datearray['mon'] = $rrule['BYMONTH']; } if (isset($rrule['BYMONTHDAY']) && $rrule['BYMONTHDAY'] != 0) { // set specific day of month $datearray['mday'] = $rrule['BYMONTHDAY']; } elseif (is_array($rrule['BYDAY'])) { // find the Xth WKDAY in the month // the starting point for this process is the first of the month set above $datearray['mday'] = 1; // turn $datearray as it is now back into a timestamp $offsetchangetimestamp = mktime($datearray['hours'], $datearray['minutes'], $datearray['seconds'], $datearray['mon'], $datearray['mday'], $datearray['year']); if ($rrule['BYDAY'][0] > 0) { // to find Xth WKDAY in month, we find last WKDAY in month before // we do that by finding first WKDAY in this month and going back one week // then we add X weeks (below) $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']], $offsetchangetimestamp); $offsetchangetimestamp = strtotime("-1 week", $offsetchangetimestamp); } else { // to find Xth WKDAY before the end of the month, we find the first WKDAY in the following month // we do that by going forward one month and going to WKDAY there // then we subtract X weeks (below) $offsetchangetimestamp = strtotime("+1 month", $offsetchangetimestamp); $offsetchangetimestamp = strtotime($daynames[$rrule['BYDAY']['DAY']], $offsetchangetimestamp); } // now move forward or back the appropriate number of weeks, into the month we want $offsetchangetimestamp = strtotime($rrule['BYDAY'][0] . " week", $offsetchangetimestamp); $datearray = getdate($offsetchangetimestamp); } // convert the date parts back into a timestamp, setting the time parts according to the // original time data which we stored $offsetchangetimestamp = mktime($changetime['hours'], $changetime['minutes'], $changetime['seconds'] + $diff, $datearray['mon'], $datearray['mday'], $datearray['year']); // add this change to the array of changes $tzdates[] = array('timestamp' => $offsetchangetimestamp, 'tzbefore' => $tzbefore, 'tzafter' => $tzafter); // update counters (timestamp and count) $offsetchangetimestamp = strtotime("+" . (isset($rrule['INTERVAL']) && $rrule['INTERVAL'] != 0 ? $rrule['INTERVAL'] : 1) . " year", $offsetchangetimestamp); $count += 1; } } } // generate dates according to RDATE lines while ($rdates = $vtzc->getProperty('rdate')) { if (is_array($rdates)) { foreach ($rdates as $rdate) { // convert the explicit change date to a timestamp $offsetchangetimestamp = mktime($rdate['hour'], $rdate['min'], $rdate['sec'] + $diff, $rdate['month'], $rdate['day'], $rdate['year']); // add this change to the array of changes $tzdates[] = array('timestamp' => $offsetchangetimestamp, 'tzbefore' => $tzbefore, 'tzafter' => $tzafter); } } } return $tzdates; }
/** * convert timestamp to date array, default UTC or adjusted for offset/timezone * * @author Kjell-Inge Gustafsson, kigkonsult <*****@*****.**> * @since 2.15.1 - 2012-10-17 * @param mixed $timestamp * @param int $parno * @param string $wtz * @return array */ public static function _timestamp2date($timestamp, $parno = 6, $wtz = null) { if (is_array($timestamp)) { $tz = isset($timestamp['tz']) ? $timestamp['tz'] : $wtz; $timestamp = $timestamp['timestamp']; } $tz = isset($tz) ? $tz : $wtz; if (empty($tz) || 'Z' == $tz || 'GMT' == strtoupper($tz)) { $tz = 'UTC'; } elseif (ICalUtilityFunctions::_isOffset($tz)) { $offset = ICalUtilityFunctions::_tz2offset($tz); $tz = 'UTC'; } try { $d = new \DateTime("@{$timestamp}"); // set UTC date if (isset($offset) && 0 != $offset) { // adjust for offset $d->modify($offset . ' seconds'); } elseif ('UTC' != $tz) { $d->setTimezone(new \DateTimeZone($tz)); } // convert to local date $date = $d->format('Y-m-d-H-i-s'); unset($d); } catch (Exception $e) { $date = date('Y-m-d-H-i-s', $timestamp); } $date = explode('-', $date); $output = array('year' => $date[0], 'month' => $date[1], 'day' => $date[2]); if (3 != $parno) { $output['hour'] = $date[3]; $output['min'] = $date[4]; $output['sec'] = $date[5]; if ('UTC' == $tz && (!isset($offset) || 0 == $offset)) { $output['tz'] = 'Z'; } } return $output; }