Calendar: iCal can import events that use RDATE:VALUE=PERIOD

This commit is contained in:
nathan 2023-11-13 17:21:15 -07:00
parent 66a1fd2670
commit 0d87e1ab2b
3 changed files with 78 additions and 3 deletions

View File

@ -945,7 +945,8 @@ class calendar_bo
}
foreach($events as $event)
{
$is_exception = in_array(Api\DateTime::to($event['start'], true), $exceptions);
// PERIOD
$is_exception = $event['recur_type'] != calendar_rrule::PERIOD && in_array(Api\DateTime::to($event['start'], true), $exceptions);
$start = $this->date2ts($event['start'],true);
if ($event['whole_day'])
{
@ -1164,12 +1165,20 @@ class calendar_bo
new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid']));
// unset exceptions, as we need to add them as recurrence too, but marked as exception
unset($event['recur_exception']);
// (Period needs them though)
if($event['recur_type'] != calendar_rrule::PERIOD)
{
unset($event['recur_exception']);
}
// loop over all recurrences and insert them, if they are after $start
$rrule = calendar_rrule::event2rrule($event, !$event['whole_day'], // true = we operate in usertime, like the rest of calendar_bo
// For whole day events, just stay in server time
$event['whole_day'] ? Api\DateTime::$server_timezone->getName() : Api\DateTime::$user_timezone->getName()
);
if($event['recur_type'] == calendar_rrule::PERIOD)
{
unset($event['recur_exception']);
}
foreach($rrule as $time)
{
// $time is in timezone of event, convert it to usertime used here

View File

@ -2748,6 +2748,27 @@ class calendar_ical extends calendar_boupdate
$vcardData += calendar_rrule::parseRrule($attributes['value'], false, $vcardData);
if (!empty($vcardData['recur_enddate'])) self::check_fix_endate ($vcardData);
break;
case 'RDATE':
$hour = date('H', $vcardData['start']);
$minutes = date('i', $vcardData['start']);
$seconds = date('s', $vcardData['start']);
if($attributes['params']['VALUE'] == 'PERIOD')
{
$vcardData['recur_type'] = calendar_rrule::PERIOD;
$vcardData['recur_exception'] = [];
foreach($attributes['values'] as $date)
{
$vcardData['recur_exception'][] = mktime(
$hour,
$minutes,
$seconds,
$date['month'],
$date['mday'],
$date['year']
);
}
}
break;
case 'EXDATE': // current Horde_Icalendar returns dates, no timestamps
if ($attributes['values'])
{

View File

@ -65,6 +65,12 @@ class calendar_rrule implements Iterator
*/
const MINUTELY = 7;
/**
* By date or period
* (a list of dates)
*/
const PERIOD = 9;
/**
* Translate recure types to labels
*
@ -77,6 +83,7 @@ class calendar_rrule implements Iterator
self::MONTHLY_WDAY => 'Monthly (by day)',
self::MONTHLY_MDAY => 'Monthly (by date)',
self::YEARLY => 'Yearly',
self::PERIOD => 'By date or period'
);
/**
@ -90,6 +97,7 @@ class calendar_rrule implements Iterator
self::YEARLY => 'YEARLY',
self::HOURLY => 'HOURLY',
self::MINUTELY => 'MINUTELY',
self::PERIOD => 'PERIOD'
);
/**
@ -135,6 +143,12 @@ class calendar_rrule implements Iterator
*/
public $monthly_bymonthday;
/**
* Period list
* @var
*/
public $period = [];
/**
* Enddate of recurring event or null, if not ending
*
@ -261,7 +275,8 @@ class calendar_rrule implements Iterator
$this->time = $time instanceof Api\DateTime ? $time : new Api\DateTime($time);
if (!in_array($type,array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY, self::YEARLY, self::HOURLY, self::MINUTELY)))
if(!in_array($type, array(self::NONE, self::DAILY, self::WEEKLY, self::MONTHLY_MDAY, self::MONTHLY_WDAY,
self::YEARLY, self::HOURLY, self::MINUTELY, self::PERIOD)))
{
throw new Api\Exception\WrongParameter(__METHOD__."($time,$type,$interval,$enddate,$weekdays,...) type $type is NOT valid!");
}
@ -302,6 +317,19 @@ class calendar_rrule implements Iterator
$this->interval = (int)$interval;
$this->enddate = $enddate;
if($type == self::PERIOD)
{
foreach($exceptions as $exception)
{
$exception->setTimezone($this->time->getTimezone());
$this->period[] = $exception;
}
$enddate = clone(count($this->period) ? end($this->period) : $this->time);
// Make sure to include the last date as valid
$enddate->modify('+1 second');
reset($this->period);
unset($exceptions);
}
// no recurrence --> current date is enddate
if ($type == self::NONE)
{
@ -469,6 +497,16 @@ class calendar_rrule implements Iterator
case self::MINUTELY:
$this->current->modify($this->interval.' minute');
break;
case self::PERIOD:
$index = array_search($this->current, $this->period);
$next = $this->enddate ?? new Api\DateTime();
if($index !== false && $index + 1 < count($this->period))
{
$next = $this->period[$index + 1];
}
$this->current->setDate($next->format('Y'), $next->format('m'), $next->format('d'));
$this->current->setTime($next->format('H'), $next->format('i'), $next->format('s'), 0);
break;
default:
throw new Api\Exception\AssertionFailed(__METHOD__."() invalid type #$this->type !");
@ -737,6 +775,13 @@ class calendar_rrule implements Iterator
$rrule['BYDAY'] = $this->monthly_byday_num .
strtoupper(substr($this->time->format('l'),0,2));
break;
case self::PERIOD:
$period = [];
foreach($this->period as $date)
{
$period[] = $date->format("Ymd\THms\Z");
}
$rrule['PERIOD'] = implode(',', $period);
}
if ($this->interval > 1)
{