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.
|
// 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++;
|
$set = $event->newRecurrenceSet();
|
||||||
$datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer);
|
|
||||||
$date = $datetime->modify($modify_key)->format('U');
|
$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_epoch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($recurrences as $sequence_index => $sequence_datetime) {
|
||||||
|
if (!$sequence_index) {
|
||||||
|
// This is the parent event, which we already have.
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$start = $this->rangeBegin;
|
if ($start_epoch) {
|
||||||
} else {
|
if ($sequence_datetime->getEpoch() < $start_epoch) {
|
||||||
$start = $event->getStartDateTimeEpoch() - $duration;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($event->getUntilDateTimeEpoch()) {
|
|
||||||
$end_times[] = $event->getUntilDateTimeEpoch();
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
$events[] = $event->newGhost(
|
||||||
for ($index = $sequence_start; $index < $sequence_end; $index++) {
|
$viewer,
|
||||||
$events[] = $event->newGhost($viewer, $index);
|
$sequence_index,
|
||||||
|
$sequence_datetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
$start_datetime = $parent->newSequenceIndexDateTime($sequence);
|
|
||||||
|
|
||||||
if (!$start_datetime) {
|
if ($start) {
|
||||||
throw new Exception(
|
$start_datetime = $start;
|
||||||
"Sequence {$sequence} does not exist for event!");
|
} else {
|
||||||
|
$start_datetime = $parent->newSequenceIndexDateTime($sequence);
|
||||||
|
|
||||||
|
if (!$start_datetime) {
|
||||||
|
throw new Exception(
|
||||||
|
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)
|
||||||
|
|
Loading…
Reference in a new issue