1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 21:02:41 +01:00

Drive calendar event queries through the RRULE engine

Summary: Ref T10747. This drives event queries through RRULE, too.

Test Plan: Created recurring events, saw them appear correctly on the calendar.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16668
This commit is contained in:
epriestley 2016-10-03 07:40:01 -07:00
parent 5dfb672a80
commit 20f7de91ce
2 changed files with 105 additions and 64 deletions

View file

@ -165,70 +165,54 @@ final class PhabricatorCalendarEventQuery
// discard anything outside of the time window. // discard anything outside of the time window.
$events = $this->getEventsInRange($events); $events = $this->getEventsInRange($events);
$enforced_end = null; $generate_from = $this->rangeBegin;
$generate_until = $this->rangeEnd;
foreach ($parents as $key => $event) { foreach ($parents as $key => $event) {
$sequence_start = 0;
$sequence_end = null;
$start = null;
$duration = $event->getDuration(); $duration = $event->getDuration();
$frequency = $event->getFrequencyUnit(); $start_date = $this->getRecurrenceWindowStart(
$modify_key = '+1 '.$frequency; $event,
$generate_from - $duration);
if (($this->rangeBegin !== null) && $end_date = $this->getRecurrenceWindowEnd(
($this->rangeBegin > $event->getStartDateTimeEpoch())) { $event,
$max_date = $this->rangeBegin - $duration; $generate_until);
$date = $event->getStartDateTimeEpoch();
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
while ($date < $max_date) { $limit = $this->getRecurrenceLimit($event, $raw_limit);
// TODO: optimize this to not loop through all off-screen events
$sequence_start++;
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
$date = $datetime->modify($modify_key)->format('U');
}
$start = $this->rangeBegin; $set = $event->newRecurrenceSet();
$recurrences = $set->getEventsBetween(
null,
$end_date,
$limit + 1);
// We're generating events from the beginning and then filtering them
// here (instead of only generating events starting at the start date)
// because we need to know the proper sequence indexes to generate ghost
// events. This may change after RDATE support.
if ($start_date) {
$start_epoch = $start_date->getEpoch();
} else { } else {
$start = $event->getStartDateTimeEpoch() - $duration; $start_epoch = null;
} }
$date = $start; foreach ($recurrences as $sequence_index => $sequence_datetime) {
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); if (!$sequence_index) {
// This is the parent event, which we already have.
// Select the minimum end time we need to generate events until. continue;
$end_times = array();
if ($this->rangeEnd) {
$end_times[] = $this->rangeEnd;
} }
if ($event->getUntilDateTimeEpoch()) { if ($start_epoch) {
$end_times[] = $event->getUntilDateTimeEpoch(); if ($sequence_datetime->getEpoch() < $start_epoch) {
continue;
}
} }
if ($enforced_end) { $events[] = $event->newGhost(
$end_times[] = $enforced_end; $viewer,
} $sequence_index,
$sequence_datetime);
if ($end_times) {
$end = min($end_times);
$sequence_end = $sequence_start;
while ($date < $end) {
$sequence_end++;
$datetime->modify($modify_key);
$date = $datetime->format('U');
if ($sequence_end > $raw_limit + $sequence_start) {
break;
}
}
} else {
$sequence_end = $raw_limit + $sequence_start;
}
$sequence_start = max(1, $sequence_start);
for ($index = $sequence_start; $index < $sequence_end; $index++) {
$events[] = $event->newGhost($viewer, $index);
} }
// NOTE: We're slicing results every time because this makes it cheaper // NOTE: We're slicing results every time because this makes it cheaper
@ -240,7 +224,7 @@ final class PhabricatorCalendarEventQuery
if (count($events) > $raw_limit) { if (count($events) > $raw_limit) {
$events = msort($events, 'getStartDateTimeEpoch'); $events = msort($events, 'getStartDateTimeEpoch');
$events = array_slice($events, 0, $raw_limit, true); $events = array_slice($events, 0, $raw_limit, true);
$enforced_end = last($events)->getStartDateTimeEpoch(); $generate_until = last($events)->getEndDateTimeEpoch();
} }
} }
} }
@ -525,4 +509,44 @@ final class PhabricatorCalendarEventQuery
return $events; return $events;
} }
private function getRecurrenceWindowStart(
PhabricatorCalendarEvent $event,
$generate_from) {
if (!$generate_from) {
return null;
}
return PhutilCalendarAbsoluteDateTime::newFromEpoch($generate_from);
}
private function getRecurrenceWindowEnd(
PhabricatorCalendarEvent $event,
$generate_until) {
$end_epochs = array();
if ($generate_until) {
$end_epochs[] = $generate_until;
}
$until_epoch = $event->getUntilDateTimeEpoch();
if ($until_epoch) {
$end_epochs[] = $until_epoch;
}
if (!$end_epochs) {
return null;
}
return PhutilCalendarAbsoluteDateTime::newFromEpoch(min($end_epochs));
}
private function getRecurrenceLimit(
PhabricatorCalendarEvent $event,
$raw_limit) {
return $raw_limit;
}
} }

View file

@ -103,7 +103,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
->applyViewerTimezone($actor); ->applyViewerTimezone($actor);
} }
private function newChild(PhabricatorUser $actor, $sequence) { private function newChild(
PhabricatorUser $actor,
$sequence,
PhutilCalendarDateTime $start = null) {
if (!$this->isParentEvent()) { if (!$this->isParentEvent()) {
throw new Exception( throw new Exception(
pht( pht(
@ -124,7 +127,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
->setDateFrom(0) ->setDateFrom(0)
->setDateTo(0); ->setDateTo(0);
return $child->copyFromParent($actor); return $child->copyFromParent($actor, $start);
} }
protected function readField($field) { protected function readField($field) {
@ -156,7 +159,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
} }
public function copyFromParent(PhabricatorUser $actor) { public function copyFromParent(
PhabricatorUser $actor,
PhutilCalendarDateTime $start = null) {
if (!$this->isChildEvent()) { if (!$this->isChildEvent()) {
throw new Exception( throw new Exception(
pht( pht(
@ -176,11 +182,18 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
->setDescription($parent->getDescription()); ->setDescription($parent->getDescription());
$sequence = $this->getSequenceIndex(); $sequence = $this->getSequenceIndex();
if ($start) {
$start_datetime = $start;
} else {
$start_datetime = $parent->newSequenceIndexDateTime($sequence); $start_datetime = $parent->newSequenceIndexDateTime($sequence);
if (!$start_datetime) { if (!$start_datetime) {
throw new Exception( throw new Exception(
"Sequence {$sequence} does not exist for event!"); pht(
'Sequence "%s" is not valid for event!',
$sequence));
}
} }
$duration = $parent->newDuration(); $duration = $parent->newDuration();
@ -225,8 +238,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return $stub; return $stub;
} }
public function newGhost(PhabricatorUser $actor, $sequence) { public function newGhost(
$ghost = $this->newChild($actor, $sequence); PhabricatorUser $actor,
$sequence,
PhutilCalendarDateTime $start = null) {
$ghost = $this->newChild($actor, $sequence, $start);
$ghost $ghost
->setIsGhostEvent(true) ->setIsGhostEvent(true)