@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
at upstream/main 162 lines 4.3 kB view raw
1<?php 2 3final class PhutilCalendarRecurrenceSet 4 extends Phobject { 5 6 private $sources = array(); 7 private $viewerTimezone = 'UTC'; 8 9 public function addSource(PhutilCalendarRecurrenceSource $source) { 10 $this->sources[] = $source; 11 return $this; 12 } 13 14 public function setViewerTimezone($viewer_timezone) { 15 $this->viewerTimezone = $viewer_timezone; 16 return $this; 17 } 18 19 public function getViewerTimezone() { 20 return $this->viewerTimezone; 21 } 22 23 public function getEventsBetween( 24 ?PhutilCalendarDateTime $start = null, 25 ?PhutilCalendarDateTime $end = null, 26 $limit = null) { 27 28 if ($end === null && $limit === null) { 29 throw new Exception( 30 pht( 31 'Recurring event range queries must have an end date, a limit, or '. 32 'both.')); 33 } 34 35 $timezone = $this->getViewerTimezone(); 36 37 $sources = array(); 38 foreach ($this->sources as $source) { 39 $source = clone $source; 40 $source->setViewerTimezone($timezone); 41 $source->resetSource(); 42 43 $sources[] = array( 44 'source' => $source, 45 'state' => null, 46 'epoch' => null, 47 ); 48 } 49 50 if ($start) { 51 $start = clone $start; 52 $start->setViewerTimezone($timezone); 53 $min_epoch = $start->getEpoch(); 54 } else { 55 $min_epoch = 0; 56 } 57 58 if ($end) { 59 $end = clone $end; 60 $end->setViewerTimezone($timezone); 61 $end_epoch = $end->getEpoch(); 62 } else { 63 $end_epoch = null; 64 } 65 66 $results = array(); 67 $index = 0; 68 $cursor = 0; 69 while (true) { 70 // Get the next event for each source which we don't have a future 71 // event for. 72 foreach ($sources as $key => $source) { 73 $state = $source['state']; 74 $epoch = $source['epoch']; 75 76 if ($state !== null && $epoch >= $cursor) { 77 // We have an event for this source, and it's a future event, so 78 // we don't need to do anything. 79 continue; 80 } 81 82 $next = $source['source']->getNextEvent($cursor); 83 if ($next === null) { 84 // This source doesn't have any more events, so we're all done. 85 unset($sources[$key]); 86 continue; 87 } 88 89 $next_epoch = $next->getEpoch(); 90 91 if ($end_epoch !== null && $next_epoch > $end_epoch) { 92 // We have an end time and the next event from this source is 93 // past that end, so we know there are no more relevant events 94 // coming from this source. 95 unset($sources[$key]); 96 continue; 97 } 98 99 $sources[$key]['state'] = $next; 100 $sources[$key]['epoch'] = $next_epoch; 101 } 102 103 if (!$sources) { 104 // We've run out of sources which can produce valid events in the 105 // window, so we're all done. 106 break; 107 } 108 109 // Find the minimum event time across all sources. 110 $next_epoch = null; 111 foreach ($sources as $source) { 112 if ($next_epoch === null) { 113 $next_epoch = $source['epoch']; 114 } else { 115 $next_epoch = min($next_epoch, $source['epoch']); 116 } 117 } 118 119 $is_exception = false; 120 $next_source = null; 121 foreach ($sources as $source) { 122 if ($source['epoch'] == $next_epoch) { 123 if ($source['source']->getIsExceptionSource()) { 124 $is_exception = true; 125 } else { 126 $next_source = $source; 127 } 128 } 129 } 130 131 // If this is an exception, it means the event does NOT occur. We 132 // skip it and move on. If it's not an exception, it does occur, so 133 // we record it. 134 if (!$is_exception) { 135 136 // Only actually include this event in the results if it starts after 137 // any specified start time. We increment the index regardless, so we 138 // return results with proper offsets. 139 if ($next_source['epoch'] >= $min_epoch) { 140 $results[$index] = $next_source['state']; 141 } 142 $index++; 143 144 if ($limit !== null && (count($results) >= $limit)) { 145 break; 146 } 147 } 148 149 $cursor = $next_epoch + 1; 150 151 // If we have an end of the window and we've reached it, we're done. 152 if ($end_epoch) { 153 if ($cursor > $end_epoch) { 154 break; 155 } 156 } 157 } 158 159 return $results; 160 } 161 162}