@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Mostly drive Calendar event recurrence with the RRULE engine

Summary:
Ref T10737. Today, we evalute recurrence twice: once when querying, and once in all other cases. This converts the second case to use the RRULE engine.

Next up is making the query use the RRULE engine, too.

Test Plan: Created a new recurring event, iterated through it by clicking "next instance", viewed it on Calendar view.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10737

Differential Revision: https://secure.phabricator.com/D16667

+60 -48
+2 -2
src/applications/calendar/query/PhabricatorCalendarEventQuery.php
··· 203 203 $end_times[] = $this->rangeEnd; 204 204 } 205 205 206 - if ($event->getRecurrenceEndDate()) { 207 - $end_times[] = $event->getRecurrenceEndDate(); 206 + if ($event->getUntilDateTimeEpoch()) { 207 + $end_times[] = $event->getUntilDateTimeEpoch(); 208 208 } 209 209 210 210 if ($enforced_end) {
+58 -46
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 176 176 ->setDescription($parent->getDescription()); 177 177 178 178 $sequence = $this->getSequenceIndex(); 179 - $duration = $parent->getDuration(); 180 - $epochs = $parent->getSequenceIndexEpochs($actor, $sequence, $duration); 179 + $start_datetime = $parent->newSequenceIndexDateTime($sequence); 181 180 182 - $start_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 183 - $epochs['dateFrom'], 184 - $parent->newStartDateTime()->getTimezone()); 185 - $end_datetime = PhutilCalendarAbsoluteDateTime::newFromEpoch( 186 - $epochs['dateTo'], 187 - $parent->newEndDateTime()->getTimezone()); 181 + if (!$start_datetime) { 182 + throw new Exception( 183 + "Sequence {$sequence} does not exist for event!"); 184 + } 185 + 186 + $duration = $parent->newDuration(); 187 + $end_datetime = $start_datetime->newRelativeDateTime($duration); 188 188 189 189 $this 190 190 ->setStartDateTime($start_datetime) ··· 194 194 } 195 195 196 196 public function isValidSequenceIndex(PhabricatorUser $viewer, $sequence) { 197 - try { 198 - $this->getSequenceIndexEpochs($viewer, $sequence, $this->getDuration()); 199 - return true; 200 - } catch (Exception $ex) { 201 - return false; 202 - } 197 + return (bool)$this->newSequenceIndexDateTime($sequence); 203 198 } 204 199 205 - private function getSequenceIndexEpochs( 206 - PhabricatorUser $viewer, 207 - $sequence, 208 - $duration) { 200 + public function newSequenceIndexDateTime($sequence) { 201 + $set = $this->newRecurrenceSet(); 202 + if (!$set) { 203 + return null; 204 + } 209 205 210 - $frequency = $this->getFrequencyUnit(); 211 - $modify_key = '+'.$sequence.' '.$frequency; 206 + $instances = $set->getEventsBetween( 207 + null, 208 + $this->newUntilDateTime(), 209 + $sequence + 1); 212 210 213 - $date_time = $this->newStartDateTime() 214 - ->setViewerTimezone($viewer->getTimezoneIdentifier()) 215 - ->newPHPDateTime(); 216 - 217 - $date_time->modify($modify_key); 218 - $date = $date_time->format('U'); 219 - 220 - $end_date = $this->getUntilDateTimeEpoch(); 221 - if ($end_date && $date > $end_date) { 222 - throw new Exception( 223 - pht( 224 - 'Sequence "%s" is invalid for this event: it would occur after '. 225 - 'the event stops repeating.', 226 - $sequence)); 227 - } 228 - 229 - return array( 230 - 'dateFrom' => $date, 231 - 'dateTo' => $date + $duration, 232 - ); 211 + return idx($instances, $sequence, null); 233 212 } 234 213 235 214 public function newStub(PhabricatorUser $actor, $sequence) { ··· 785 764 return null; 786 765 } 787 766 788 - $epochs = $this->getParentEvent()->getSequenceIndexEpochs( 789 - new PhabricatorUser(), 790 - $this->getSequenceIndex(), 791 - $this->getDuration()); 767 + $index = $this->getSequenceIndex(); 768 + if (!$index) { 769 + return null; 770 + } 792 771 793 - $epoch = $epochs['dateFrom']; 794 - return $this->newDateTimeFromEpoch($epoch); 772 + return $this->newSequenceIndexDateTime($index); 795 773 } 796 774 797 775 private function newDateTimeFromEpoch($epoch) { ··· 843 821 return $this->setParameter( 844 822 'untilDateTime', 845 823 $datetime->newAbsoluteDateTime()->toDictionary()); 824 + } 825 + 826 + 827 + public function newRecurrenceRule() { 828 + if ($this->isChildEvent()) { 829 + return $this->getParentEvent()->newRecurrenceRule(); 830 + } 831 + 832 + // TODO: This is a little fragile since it relies on the convenient 833 + // definition of FREQUENCY constants here and in RecurrenceRule, but 834 + // should be gone soon. 835 + $map = array( 836 + 'FREQ' => phutil_utf8_strtoupper($this->getFrequencyRule()), 837 + ); 838 + 839 + $rrule = PhutilCalendarRecurrenceRule::newFromDictionary($map); 840 + 841 + $start = $this->newStartDateTime(); 842 + $rrule->setStartDateTime($start); 843 + 844 + return $rrule; 845 + } 846 + 847 + public function newRecurrenceSet() { 848 + if ($this->isChildEvent()) { 849 + return $this->getParentEvent()->newRecurrenceSet(); 850 + } 851 + 852 + $set = new PhutilCalendarRecurrenceSet(); 853 + 854 + $rrule = $this->newRecurrenceRule(); 855 + $set->addSource($rrule); 856 + 857 + return $set; 846 858 } 847 859 848 860 /* -( Markup Interface )--------------------------------------------------- */