1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - EventAggregator event filtering functionality. 4 5 @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from DateSupport import DateTime, Timespan, TimespanCollection, \ 10 getCurrentDate, getCurrentMonth 11 from MoinSupport import * 12 13 # Event filtering and limits. 14 15 def getEventsInPeriod(events, calendar_period): 16 17 """ 18 Return a collection containing those of the given 'events' which occur 19 within the given 'calendar_period'. 20 """ 21 22 all_shown_events = [] 23 24 for event in events: 25 26 # Test for the suitability of the event. 27 28 if event.as_timespan() is not None: 29 30 # Compare the dates to the requested calendar window, if any. 31 32 if event in calendar_period: 33 all_shown_events.append(event) 34 35 return all_shown_events 36 37 def getEventLimits(events): 38 39 "Return the earliest and latest of the given 'events'." 40 41 earliest = None 42 latest = None 43 44 for event in events: 45 46 # Test for the suitability of the event. 47 48 if event.as_timespan() is not None: 49 ts = event.as_timespan() 50 if earliest is None or ts.start < earliest: 51 earliest = ts.start 52 if latest is None or ts.end > latest: 53 latest = ts.end 54 55 return earliest, latest 56 57 def getLatestEventTimestamp(events): 58 59 """ 60 Return the latest timestamp found from the given 'events'. 61 """ 62 63 latest = None 64 65 for event in events: 66 metadata = event.getMetadata() 67 68 if latest is None or latest < metadata["last-modified"]: 69 latest = metadata["last-modified"] 70 71 return latest 72 73 def getCalendarPeriod(calendar_start, calendar_end): 74 75 """ 76 Return a calendar period for the given 'calendar_start' and 'calendar_end'. 77 These parameters can be given as None. 78 """ 79 80 # Re-order the window, if appropriate. 81 82 if calendar_start is not None and calendar_end is not None and calendar_start > calendar_end: 83 calendar_start, calendar_end = calendar_end, calendar_start 84 85 return Timespan(calendar_start, calendar_end) 86 87 def getConcretePeriod(calendar_start, calendar_end, earliest, latest, resolution): 88 89 """ 90 From the requested 'calendar_start' and 'calendar_end', which may be None, 91 indicating that no restriction is imposed on the period for each of the 92 boundaries, use the 'earliest' and 'latest' event months to define a 93 specific period of interest. 94 """ 95 96 # Define the period as starting with any specified start month or the 97 # earliest event known, ending with any specified end month or the latest 98 # event known. 99 100 first = calendar_start or earliest 101 last = calendar_end or latest 102 103 # If there is no range of months to show, perhaps because there are no 104 # events in the requested period, and there was no start or end month 105 # specified, show only the month indicated by the start or end of the 106 # requested period. If all events were to be shown but none were found show 107 # the current month. 108 109 if resolution == "date": 110 get_current = getCurrentDate 111 else: 112 get_current = getCurrentMonth 113 114 if first is None: 115 first = last or get_current() 116 if last is None: 117 last = first or get_current() 118 119 if resolution == "month": 120 first = first.as_month() 121 last = last.as_month() 122 123 # Permit "expiring" periods (where the start date approaches the end date). 124 125 return min(first, last), last 126 127 def getCoverage(events, resolution="date"): 128 129 """ 130 Determine the coverage of the given 'events', returning a collection of 131 timespans, along with a dictionary mapping locations to collections of 132 slots, where each slot contains a tuple of the form (timespans, events). 133 """ 134 135 all_events = {} 136 full_coverage = TimespanCollection(resolution) 137 138 # Get event details. 139 140 for event in events: 141 event_details = event.getDetails() 142 143 # Find the coverage of this period for the event. 144 145 # For day views, each location has its own slot, but for month 146 # views, all locations are pooled together since having separate 147 # slots for each location can lead to poor usage of vertical space. 148 149 if resolution == "datetime": 150 event_location = event_details.get("location") 151 else: 152 event_location = None 153 154 # Update the overall coverage. 155 156 full_coverage.insert_in_order(event) 157 158 # Add a new events list for a new location. 159 # Locations can be unspecified, thus None refers to all unlocalised 160 # events. 161 162 if not all_events.has_key(event_location): 163 all_events[event_location] = [TimespanCollection(resolution, [event])] 164 165 # Try and fit the event into an events list. 166 167 else: 168 slot = all_events[event_location] 169 170 for slot_events in slot: 171 172 # Where the event does not overlap with the events in the 173 # current collection, add it alongside these events. 174 175 if not event in slot_events: 176 slot_events.insert_in_order(event) 177 break 178 179 # Make a new element in the list if the event cannot be 180 # marked alongside existing events. 181 182 else: 183 slot.append(TimespanCollection(resolution, [event])) 184 185 return full_coverage, all_events 186 187 def getCoverageScale(coverage): 188 189 """ 190 Return a scale for the given coverage so that the times involved are 191 exposed. The scale consists of a list of non-overlapping timespans forming 192 a contiguous period of time. 193 """ 194 195 times = set() 196 for timespan in coverage: 197 start, end = timespan.as_limits() 198 199 # Add either genuine times or dates converted to times. 200 201 if isinstance(start, DateTime): 202 times.add(start) 203 else: 204 times.add(start.as_start_of_day()) 205 206 if isinstance(end, DateTime): 207 times.add(end) 208 else: 209 times.add(end.as_date().next_day()) 210 211 times = list(times) 212 times.sort(cmp_dates_as_day_start) 213 214 scale = [] 215 first = 1 216 start = None 217 for time in times: 218 if not first: 219 scale.append(Timespan(start, time)) 220 else: 221 first = 0 222 start = time 223 224 return scale 225 226 # vim: tabstop=4 expandtab shiftwidth=4