Commit 48b74bcc authored by Bart Vullings's avatar Bart Vullings

ZP-1389 CalDAV timezone handling improvements. Released under the Affero GNU...

ZP-1389 CalDAV timezone handling improvements. Released under the Affero GNU General Public License (AGPL) version 3.
parent 4c334595
......@@ -541,29 +541,40 @@ class BackendCalDAV extends BackendDiff {
$message = new SyncAppointment();
$ical = new iCalComponent($data);
$timezones = $ical->GetComponents("VTIMEZONE");
$timezone = "";
if (count($timezones) > 0) {
$timezone = TimezoneUtil::GetPhpSupportedTimezone($timezones[0]->GetPValue("TZID"));
}
if (!$timezone) {
$timezone = date_default_timezone_get();
$vevents = $ical->GetComponents("VEVENT");
// First check the main vevent to determine time zone and all-day
$message->alldayevent = "0";
$message->timezone = date_default_timezone_get();
foreach ($vevents as $event) {
if (count($event->GetProperties("RECURRENCE-ID")) > 0) {continue;}
if (strlen($event->GetPValue("DTSTART")) == 8
|| strlen($event->GetPValue("DTEND")) == 8
|| $event->GetPValue("X-MICROSOFT-CDO-ALLDAYEVENT") == "TRUE"
) {
$message->alldayevent = "1";
}
$message->timezone = $this->_GetTimezoneString($timezone);
$vevents = $ical->GetComponents("VTIMEZONE", false);
$message->timezone = TimezoneUtil::GetPhpSupportedTimezone($event->GetPParamValue("DTSTART", "TZID"));
}
// Now process all vevents
foreach ($vevents as $event) {
$rec = $event->GetProperties("RECURRENCE-ID");
if (count($rec) > 0) {
$recurrence_id = reset($rec);
$exception = new SyncAppointmentException();
$tzid = TimezoneUtil::GetPhpSupportedTimezone($recurrence_id->GetParameterValue("TZID"));
if (!$tzid) {
$tzid = $timezone;
$exception = $this->_ParseVEventToSyncObject($event, $exception, $truncsize);
if ($recurrence_id->GetParameterValue("TZID")){
$rec_tzid = $recurrence_id->GetParameterValue("TZID");
}
else {
$rec_tzid = $message->timezone;
}
$exception->exceptionstarttime = TimezoneUtil::MakeUTCDate($recurrence_id->Value(), $tzid);
$exception->exceptionstarttime = TimezoneUtil::MakeUTCDate($recurrence_id->Value(), $rec_tzid);
$exception->deleted = "0";
$exception = $this->_ParseVEventToSyncObject($event, $exception, $truncsize);
if (!isset($message->exceptions)) {
$message->exceptions = array();
}
......@@ -573,6 +584,7 @@ class BackendCalDAV extends BackendDiff {
$message = $this->_ParseVEventToSyncObject($event, $message, $truncsize);
}
}
$message->timezone = $this->_GetTimezoneString($message->timezone);
return $message;
}
......@@ -595,9 +607,6 @@ class BackendCalDAV extends BackendDiff {
case "DTSTART":
$message->starttime = TimezoneUtil::MakeUTCDate($property->Value(), TimezoneUtil::GetPhpSupportedTimezone($property->GetParameterValue("TZID")));
if (strlen($property->Value()) == 8) {
$message->alldayevent = "1";
}
break;
case "SUMMARY":
......@@ -623,15 +632,6 @@ class BackendCalDAV extends BackendDiff {
case "DTEND":
$message->endtime = TimezoneUtil::MakeUTCDate($property->Value(), TimezoneUtil::GetPhpSupportedTimezone($property->GetParameterValue("TZID")));
if (strlen($property->Value()) == 8) {
$message->alldayevent = "1";
}
break;
case "X-MICROSOFT-CDO-ALLDAYEVENT":
if ($property->Value() == "TRUE") {
$message->alldayevent = "1";
}
break;
case "DURATION":
......@@ -947,28 +947,76 @@ class BackendCalDAV extends BackendDiff {
$ical->AddProperty("PRODID", "-//z-push-contrib//NONSGML Z-Push-contrib Calendar//EN");
$ical->AddProperty("CALSCALE", "GREGORIAN");
if (isset($data->timezone) && isset($data->starttime) && ($tzid = $this->tzidFromMSTZ($data->timezone, $data->starttime))) {
$tzpar = array("TZID" => $tzid);
// Add timezone
$vtimezone = new iCalComponent();
$vtimezone->SetType("VTIMEZONE");
$vtimezone->AddProperty("TZID", $tzid);
$timezone = new DateTimeZone($tzid);
$year = date("Y", $data->starttime);
$transitions = $timezone->getTransitions(date("U", strtotime($year."0101T000000Z")), date("U", strtotime($year."1231T235959Z")));
$offset_from = self::phpOffsetToIcalOffset($transitions[0]['offset']);
for ($i=0; $i<count($transitions); $i++) {
$offset_to = self::phpOffsetToIcalOffset($transitions[$i]['offset']);
if ($i == 0) {
$offset_from = $offset_to;
if (count($transitions) > 1) {
continue;
}
}
$vtransition->AddProperty("TZOFFSETFROM", $offset_from);
$vtransition->AddProperty("TZOFFSETTO", $offset_to);
$offset_from = $offset_to;
$vtransition = new iCalComponent();
$type = $transitions[$i]['isdst'] == 1 ? "DAYLIGHT" : "STANDARD";
$vtransition->SetType($type);
$vtransition->AddProperty("TZNAME", $transitions[$i]['abbr']);
$vtransition->AddProperty("DTSTART", date("Ymd\THis", $transitions[$i]['ts']));
$vtimezone->AddComponent($vtransition);
}
$ical->AddComponent($vtimezone);
}
else {
$tzid = 'Z';
$tzpar = null;
}
if ($folderid[0] == "C") {
$vevent = $this->_ParseASEventToVEvent($data, $id);
$vevent = $this->_ParseASEventToVEvent($data, $id, $tzid, $tzpar);
$vevent->AddProperty("UID", $id);
$ical->AddComponent($vevent);
if (isset($data->exceptions) && is_array($data->exceptions)) {
foreach ($data->exceptions as $ex) {
if (isset($ex->deleted) && $ex->deleted == "1") {
if ($exdate = $vevent->GetPValue("EXDATE")) {
$vevent->SetPValue("EXDATE", $exdate.",".gmdate("Ymd\THis\Z", $ex->exceptionstarttime));
if ($data->alldayevent == 1) {
$vevent->SetPValue("EXDATE", $exdate.",".$this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE"));
}
else {
$vevent->SetPValue("EXDATE", $exdate.",".$this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar);
}
}
else {
if ($data->alldayevent == 1) {
$vevent->AddProperty("EXDATE", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE"));
}
else {
$vevent->AddProperty("EXDATE", gmdate("Ymd\THis\Z", $ex->exceptionstarttime));
$vevent->AddProperty("EXDATE", $this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar);
}
}
continue;
}
$exception = $this->_ParseASEventToVEvent($ex, $id);
$exception = $this->_ParseASEventToVEvent($ex, $id, $tzid, $tzpar);
if ($data->alldayevent == 1) {
$exception->AddProperty("RECURRENCE-ID", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE"));
}
else {
$exception->AddProperty("RECURRENCE-ID", gmdate("Ymd\THis\Z", $ex->exceptionstarttime));
$exception->AddProperty("RECURRENCE-ID", $this->DAVDateTimeInTimezone($ex->exceptionstarttime, $tzid), $tzpar);
}
$exception->AddProperty("UID", $id);
$ical->AddComponent($exception);
......@@ -989,9 +1037,11 @@ class BackendCalDAV extends BackendDiff {
* Generate a VEVENT from a SyncAppointment(Exception).
* @param string $data
* @param string $id
* @param string $tzid
* @param array $tzpar
* @return iCalComponent
*/
private function _ParseASEventToVEvent($data, $id) {
private function _ParseASEventToVEvent($data, $id, $tzid, $tzpar) {
$vevent = new iCalComponent();
$vevent->SetType("VEVENT");
......@@ -1004,7 +1054,7 @@ class BackendCalDAV extends BackendDiff {
$vevent->AddProperty("DTSTART", $this->_GetDateFromUTC("Ymd", $data->starttime, $data->timezone), array("VALUE" => "DATE"));
}
else {
$vevent->AddProperty("DTSTART", gmdate("Ymd\THis\Z", $data->starttime));
$vevent->AddProperty("DTSTART", $this->DAVDateTimeInTimezone($data->starttime, $tzid), $tzpar);
}
}
if (isset($data->subject)) {
......@@ -1019,7 +1069,7 @@ class BackendCalDAV extends BackendDiff {
$vevent->AddProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "TRUE");
}
else {
$vevent->AddProperty("DTEND", gmdate("Ymd\THis\Z", $data->endtime));
$vevent->AddProperty("DTEND", $this->DAVDateTimeInTimezone($data->endtime, $tzid), $tzpar);
$vevent->AddProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "FALSE");
}
}
......@@ -1027,7 +1077,7 @@ class BackendCalDAV extends BackendDiff {
$vevent->AddProperty("X-MICROSOFT-CDO-ALLDAYEVENT", "TRUE");
}
if (isset($data->recurrence)) {
$vevent->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence));
$vevent->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence, $data->alldayevent == 1));
}
if (isset($data->sensitivity)) {
switch ($data->sensitivity) {
......@@ -1154,8 +1204,9 @@ class BackendCalDAV extends BackendDiff {
/**
* Generate Recurrence
* @param string $rec
* @param boolean $allday
*/
private function _GenerateRecurrence($rec) {
private function _GenerateRecurrence($rec, $allday) {
$rrule = array();
if (isset($rec->type)) {
$freq = "";
......@@ -1177,8 +1228,13 @@ class BackendCalDAV extends BackendDiff {
$rrule[] = "FREQ=" . $freq;
}
if (isset($rec->until)) {
if ($allday) {
$rrule[] = "UNTIL=" . gmdate("Ymd", $rec->until);
}
else {
$rrule[] = "UNTIL=" . gmdate("Ymd\THis\Z", $rec->until);
}
}
if (isset($rec->occurrences)) {
$rrule[] = "COUNT=" . $rec->occurrences;
}
......@@ -1439,7 +1495,7 @@ class BackendCalDAV extends BackendDiff {
}
}
if (isset($data->recurrence)) {
$vtodo->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence));
$vtodo->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence, false));
}
if ($data->reminderset && $data->remindertime) {
$valarm = new iCalComponent();
......@@ -1514,9 +1570,261 @@ class BackendCalDAV extends BackendDiff {
* @throws Exception
*/
private function _GetTimezoneString($timezone, $with_names = true) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_GetTimezoneString(): using '%s' timezone", $timezone));
$tz = TimezoneUtil::GetFullTZFromTZName($timezone);
$blob = TimezoneUtil::GetSyncBlobFromTZ($tz);
return base64_encode($blob);
// UTC needs special handling
if ($timezone == "UTC")
return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
try {
//Generate a timezone string (PHP 5.3 needed for this)
$timezone = new DateTimeZone($timezone);
$trans = $timezone->getTransitions(date("U", time()), date("U", time()));
$stdTime = null;
$dstTime = null;
if (count($trans) < 1) {
throw new Exception();
}
if (count($trans) == 1) {
$stdBias = $trans[0]['offset'] / -60;
$stdName = $trans[0]['abbr'];
$stdMonth = 0;
$stdDay = 0;
$stdWeek = 0;
$stdHour = 0;
$stdMinute = 0;
$dstName = "";
$dstMonth = 0;
$dstDay = 0;
$dstWeek = 0;
$dstHour = 0;
$dstMinute = 0;
$dstBias = -60;
}
else {
if ($trans[1]['isdst'] == 1) {
$dstTime = $trans[1];
$stdTime = $trans[2];
}
else {
$dstTime = $trans[2];
$stdTime = $trans[1];
}
$stdTimeO = new DateTime($stdTime['time']);
$stdFirst = new DateTime(sprintf("first sun of %s %s", $stdTimeO->format('F'), $stdTimeO->format('Y')), timezone_open("UTC"));
$stdBias = $stdTime['offset'] / -60;
$stdName = $stdTime['abbr'];
$stdYear = 0;
$stdMonth = $stdTimeO->format('n');
$stdWeek = floor(($stdTimeO->format("j")-$stdFirst->format("j"))/7)+1;
$stdDay = $stdTimeO->format('w');
$stdHour = $stdTimeO->format('H');
$stdMinute = $stdTimeO->format('i');
$stdTimeO->add(new DateInterval('P7D'));
if ($stdTimeO->format('n') != $stdMonth) {
$stdWeek = 5;
}
$dstTimeO = new DateTime($dstTime['time']);
$dstFirst = new DateTime(sprintf("first sun of %s %s", $dstTimeO->format('F'), $dstTimeO->format('Y')), timezone_open("UTC"));
$dstName = $dstTime['abbr'];
$dstYear = 0;
$dstMonth = $dstTimeO->format('n');
$dstWeek = floor(($dstTimeO->format("j")-$dstFirst->format("j"))/7)+1;
$dstDay = $dstTimeO->format('w');
$dstHour = $dstTimeO->format('H');
$dstMinute = $dstTimeO->format('i');
$dstTimeO->add(new DateInterval('P7D'));
if ($dstTimeO->format('n') != $dstMonth) {
$dstWeek = 5;
}
$dstBias = ($dstTime['offset'] - $stdTime['offset']) / -60;
}
if ($with_names) {
return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', $stdBias, $stdName, 0, $stdMonth, $stdDay, $stdWeek, $stdHour, $stdMinute, 0, 0, 0, $dstName, 0, $dstMonth, $dstDay, $dstWeek, $dstHour, $dstMinute, 0, 0, $dstBias));
}
else {
return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', $stdBias, '', 0, $stdMonth, $stdDay, $stdWeek, $stdHour, $stdMinute, 0, 0, 0, '', 0, $dstMonth, $dstDay, $dstWeek, $dstHour, $dstMinute, 0, 0, $dstBias));
}
}
catch (Exception $e) {
// If invalid timezone is given, we return UTC
return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
private static $knownMSTZS = array(
"-780/-60/0/0/0/0/0/0/0/0"=>"Pacific/Enderbury"
,"-720/-60/4/1/0/3/9/5/0/2"=>"Pacific/Auckland"
,"-660/-60/0/0/0/0/0/0/0/0"=>"Antarctica/Casey"
,"-600/-60/4/1/0/3/10/1/0/2"=>"Australia/Melbourne"
,"-600/-60/0/0/0/0/0/0/0/0"=>"Australia/Brisbane"
,"-570/-60/4/1/0/3/10/1/0/2"=>"Australia/Adelaide"
,"-570/-60/0/0/0/0/0/0/0/0"=>"Australia/Darwin"
,"-540/-60/0/0/0/0/0/0/0/0"=>"Asia/Chita"
,"-480/-60/0/0/0/0/0/0/0/0"=>"Asia/Brunei"
,"-420/-60/0/0/0/0/0/0/0/0"=>"Antarctica/Davis"
,"-390/-60/0/0/0/0/0/0/0/0"=>"Asia/Yangon"
,"-360/-60/0/0/0/0/0/0/0/0"=>"Antarctica/Vostok"
,"-345/-60/0/0/0/0/0/0/0/0"=>"Asia/Kathmandu"
,"-330/-60/0/0/0/0/0/0/0/0"=>"Asia/Colombo"
,"-300/-60/0/0/0/0/0/0/0/0"=>"Antarctica/Mawson"
,"-270/-60/0/0/0/0/0/0/0/0"=>"Asia/Kabul"
,"-210/-60/9/3/4/22/3/3/3/22"=>"Asia/Tehran"
,"-180/-60/0/0/0/0/0/0/0/0"=>"Africa/Addis_Ababa"
,"-120/-60/10/5/0/4/3/5/0/3"=>"Europe/Helsinki"
,"-120/-60/0/0/0/0/0/0/0/0"=>"Africa/Blantyre"
,"-60/-60/10/5/0/3/3/5/0/2"=>"Europe/Berlin"
,"-60/-60/10/4/0/3/3/5/0/2"=>"Europe/Berlin"
,"-60/-60/0/0/0/0/0/0/0/0"=>"Africa/Algiers"
,"0/-60/10/5/0/2/3/5/0/1"=>"Europe/Dublin"
,"0/-60/10/4/0/2/3/5/0/1"=>"Europe/Dublin"
,"0/-60/0/0/0/0/0/0/0/0"=>"Africa/Abidjan"
,"60/-60/0/0/0/0/0/0/0/0"=>"Atlantic/Cape_Verde"
,"180/-60/2/4/6/23/10/3/6/23"=>"America/Sao_Paulo"
,"180/-60/10/5/6/23/3/4/6/22"=>"America/Godthab"
,"180/-60/0/0/0/0/0/0/0/0"=>"America/Araguaina"
,"240/-60/11/1/0/2/3/2/0/2"=>"America/Barbados"
,"240/-60/0/0/0/0/0/0/0/0"=>"America/Anguilla"
,"270/-60/0/0/0/0/0/0/0/0"=>"America/Caracas"
,"300/-60/11/1/0/2/3/2/0/2"=>"America/New_York"
,"300/-60/0/0/0/0/0/0/0/0"=>"America/Atikokan"
,"360/-60/11/1/0/2/3/2/0/2"=>"America/Chicago"
,"360/-60/0/0/0/0/0/0/0/0"=>"America/Belize"
,"420/-60/10/5/0/2/4/1/0/2"=>"America/Chihuahua"
,"420/-60/11/1/0/2/3/2/0/2"=>"America/Denver"
,"420/-60/0/0/0/0/0/0/0/0"=>"America/Creston"
,"480/-60/11/1/0/2/3/2/0/2"=>"America/Los_Angeles"
,"540/-60/11/1/0/2/3/2/0/2"=>"America/Anchorage"
,"600/-60/0/0/0/0/0/0/0/0"=>"Pacific/Honolulu"
);
/**
* Given the MS timezone find a matching tzid, for the year the event starts in.
* @param string $mstz
* @param string $eventstart
* @return string
*/
public static function tzidFromMSTZ($mstz, $eventstart){
// 1. Check known MS time zones
$mstz_parts = unpack("lbias/Z64tzname/vdstendyear/vdstendmonth/vdstendday/vdstendweek/vdstendhour/"
."vdstendminute/vdstendsecond/vdstendmillis/lstdbias/Z64tznamedst/vdststartyear/"
."vdststartmonth/vdststartday/vdststartweek/vdststarthour/vdststartminute/"
."vdststartsecond/vdststartmillis/ldstbias", base64_decode($mstz));
$mstz = $mstz_parts['bias']
."/".$mstz_parts['dstbias']
."/".$mstz_parts['dstendmonth']
."/".$mstz_parts['dstendweek']
."/".$mstz_parts['dstendday']
."/".$mstz_parts['dstendhour']
."/".$mstz_parts['dststartmonth']
."/".$mstz_parts['dststartweek']
."/".$mstz_parts['dststartday']
."/".$mstz_parts['dststarthour'];
if (isset(self::$knownMSTZS[$mstz])) {
$tzid = self::$knownMSTZS[$mstz];
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Found tzid in known list: '%s'.", $tzid));
return $tzid;
}
// 2. Loop all time zones to find a match on offset and transition date
$year = date("Y", $eventstart);
$offset_std = -($mstz_parts["bias"] * 60);
$offset_dst = -(($mstz_parts["bias"] + $mstz_parts["dstbias"]) * 60);
$dststart_timestamp = self::timestampFromMSTZ($mstz_parts, "dststart", $mstz_parts["bias"], $year);
$dstend_timestamp = self::timestampFromMSTZ($mstz_parts, "dstend", $mstz_parts["bias"] + $mstz_parts["dstbias"], $year);
$tzids = DateTimeZone::listIdentifiers();
foreach ($tzids as $tzid) {
$timezone = new DateTimeZone($tzid);
$transitions = $timezone->getTransitions(date("U", strtotime($year."0101T000000Z")), date("U", strtotime($year."1231T235959Z")));
$tno = count($transitions);
if ($tno == 1 && $dststart_timestamp == 0) {
if ($transitions[0]['offset'] == $offset_std) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Found tzid: '%s'.", $tzid));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Add tzid to knownMSTZS array for better performance: '%s'.", ',"'.$mstz.'"=>"'.$tzid.'"'));
return $tzid;
}
}
else if (($tno == 3 || $tno == 5) && $dststart_timestamp != 0) {
if ($dststart_timestamp < $dstend_timestamp) {
if(
$transitions[1]['isdst'] == 1 &&
$transitions[1]['ts'] == $dststart_timestamp &&
$transitions[1]['offset'] == $offset_dst &&
$transitions[2]['isdst'] == 0 &&
$transitions[2]['ts'] == $dstend_timestamp &&
$transitions[2]['offset'] == $offset_std)
{
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Found tzid: '%s'.", $tzid));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Add tzid to knownMSTZS array for better performance: '%s'.", ',"'.$mstz.'"=>"'.$tzid.'"'));
return $tzid;
}
}
else {
if (
$transitions[1]['isdst'] == 0 &&
$transitions[1]['ts'] == $dstend_timestamp &&
$transitions[1]['offset'] == $offset_std &&
$transitions[2]['isdst'] == 1 &&
$transitions[2]['ts'] == $dststart_timestamp &&
$transitions[2]['offset'] == $offset_dst)
{
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Found tzid: '%s'.", $tzid));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->tzidFromMSTZ(): Add tzid to knownMSTZS array for better performance: '%s'.", ',"'.$mstz.'"=>"'.$tzid.'"'));
return $tzid;
}
}
}
}
// 3. Give up, use Zulu
ZLog::Write(LOGLEVEL_WARN, sprintf("BackendCalDAV->tzidFromMSTZ(): Failed to find tzid, defaulting to UTC. MS time zone: '%s'.", join('/', $mstz_parts)));
return null;
}
/*
* Calculate a unix timestamp for DST or STD start times in the MS time zone.
* @param array $mstz_parts
* @param string $prefix
* @param integer $bias
* @param string $year
* @return integer
*/
private static function timestampFromMSTZ($mstz_parts, $prefix, $bias, $year){
if($mstz_parts[$prefix."month"] == 0){return 0;} // If month is empty, there is no transition
$month = $mstz_parts[$prefix."month"];
$weeks = array('', 'first', 'second', 'third', 'fourth', 'last');
$week = $weeks[$mstz_parts[$prefix."week"]];
$days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday','Friday', 'Saturday');
$day = $days[$mstz_parts[$prefix."day"]];
$second = $mstz_parts[$prefix."hour"] * 3600 + $mstz_parts[$prefix."minute"] * 60 + $mstz_parts[$prefix."second"] + $bias * 60;
return date("U", strtotime("$week $day of $year-$month Z") + $second);
}
/*
* Calculate a DAV datetime field with timezone
* @param int $timestamp
* @param string $tzid
* @return string
*/
private static function DAVDateTimeInTimezone($timestamp, $tzid) {
$dt = new DateTime('@'.$timestamp);
$dt->setTimeZone(new DateTimeZone($tzid));
return $tzid == 'Z' ? $dt->format('Ymd\THis\Z') : $dt->format('Ymd\THis');
}
/**
* Convert the offset provided by php to what ical uses in VTIMEZONE.
* @param integer $phpoffset
* @return string
*/
private static function phpOffsetToIcalOffset($phpoffset) {
$prefix = $phpoffset < 0 ? "-" : "+";
$offset = abs($phpoffset);
$hours = floor($offset / 3600);
return sprintf("$prefix%'.02d%'.02d", $hours, ($offset - ($hours * 3600)) / 60);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment