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:
parent
5dfb672a80
commit
20f7de91ce
2 changed files with 105 additions and 64 deletions
|
@ -165,70 +165,54 @@ final class PhabricatorCalendarEventQuery
|
|||
// discard anything outside of the time window.
|
||||
$events = $this->getEventsInRange($events);
|
||||
|
||||
$enforced_end = null;
|
||||
$generate_from = $this->rangeBegin;
|
||||
$generate_until = $this->rangeEnd;
|
||||
foreach ($parents as $key => $event) {
|
||||
$sequence_start = 0;
|
||||
$sequence_end = null;
|
||||
$start = null;
|
||||
|
||||
$duration = $event->getDuration();
|
||||
|
||||
$frequency = $event->getFrequencyUnit();
|
||||
$modify_key = '+1 '.$frequency;
|
||||
$start_date = $this->getRecurrenceWindowStart(
|
||||
$event,
|
||||
$generate_from - $duration);
|
||||
|
||||
if (($this->rangeBegin !== null) &&
|
||||
($this->rangeBegin > $event->getStartDateTimeEpoch())) {
|
||||
$max_date = $this->rangeBegin - $duration;
|
||||
$date = $event->getStartDateTimeEpoch();
|
||||
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
||||
$end_date = $this->getRecurrenceWindowEnd(
|
||||
$event,
|
||||
$generate_until);
|
||||
|
||||
while ($date < $max_date) {
|
||||
// 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');
|
||||
}
|
||||
$limit = $this->getRecurrenceLimit($event, $raw_limit);
|
||||
|
||||
$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 {
|
||||
$start = $event->getStartDateTimeEpoch() - $duration;
|
||||
$start_epoch = null;
|
||||
}
|
||||
|
||||
$date = $start;
|
||||
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
||||
|
||||
// Select the minimum end time we need to generate events until.
|
||||
$end_times = array();
|
||||
if ($this->rangeEnd) {
|
||||
$end_times[] = $this->rangeEnd;
|
||||
foreach ($recurrences as $sequence_index => $sequence_datetime) {
|
||||
if (!$sequence_index) {
|
||||
// This is the parent event, which we already have.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($event->getUntilDateTimeEpoch()) {
|
||||
$end_times[] = $event->getUntilDateTimeEpoch();
|
||||
if ($start_epoch) {
|
||||
if ($sequence_datetime->getEpoch() < $start_epoch) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($enforced_end) {
|
||||
$end_times[] = $enforced_end;
|
||||
}
|
||||
|
||||
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);
|
||||
$events[] = $event->newGhost(
|
||||
$viewer,
|
||||
$sequence_index,
|
||||
$sequence_datetime);
|
||||
}
|
||||
|
||||
// NOTE: We're slicing results every time because this makes it cheaper
|
||||
|
@ -240,7 +224,7 @@ final class PhabricatorCalendarEventQuery
|
|||
if (count($events) > $raw_limit) {
|
||||
$events = msort($events, 'getStartDateTimeEpoch');
|
||||
$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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -103,7 +103,10 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->applyViewerTimezone($actor);
|
||||
}
|
||||
|
||||
private function newChild(PhabricatorUser $actor, $sequence) {
|
||||
private function newChild(
|
||||
PhabricatorUser $actor,
|
||||
$sequence,
|
||||
PhutilCalendarDateTime $start = null) {
|
||||
if (!$this->isParentEvent()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -124,7 +127,7 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setDateFrom(0)
|
||||
->setDateTo(0);
|
||||
|
||||
return $child->copyFromParent($actor);
|
||||
return $child->copyFromParent($actor, $start);
|
||||
}
|
||||
|
||||
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()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -176,11 +182,18 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
->setDescription($parent->getDescription());
|
||||
|
||||
$sequence = $this->getSequenceIndex();
|
||||
|
||||
if ($start) {
|
||||
$start_datetime = $start;
|
||||
} else {
|
||||
$start_datetime = $parent->newSequenceIndexDateTime($sequence);
|
||||
|
||||
if (!$start_datetime) {
|
||||
throw new Exception(
|
||||
"Sequence {$sequence} does not exist for event!");
|
||||
pht(
|
||||
'Sequence "%s" is not valid for event!',
|
||||
$sequence));
|
||||
}
|
||||
}
|
||||
|
||||
$duration = $parent->newDuration();
|
||||
|
@ -225,8 +238,12 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return $stub;
|
||||
}
|
||||
|
||||
public function newGhost(PhabricatorUser $actor, $sequence) {
|
||||
$ghost = $this->newChild($actor, $sequence);
|
||||
public function newGhost(
|
||||
PhabricatorUser $actor,
|
||||
$sequence,
|
||||
PhutilCalendarDateTime $start = null) {
|
||||
|
||||
$ghost = $this->newChild($actor, $sequence, $start);
|
||||
|
||||
$ghost
|
||||
->setIsGhostEvent(true)
|
||||
|
|
Loading…
Reference in a new issue