# HG changeset patch # User Paul Boddie # Date 1496600144 -7200 # Node ID eb97b1194e2e0f9a3411b47de538681e20280767 # Parent ae83db83bdbb3674e3138718494036d4610382fb Obtain only future event periods when scheduling. Added support for specifying the start of the scheduling window, instead of employing the current moment in time as the start, in order to support testing of events defined in the past. diff -r ae83db83bdbb -r eb97b1194e2e imiptools/client.py --- a/imiptools/client.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imiptools/client.py Sun Jun 04 20:15:44 2017 +0200 @@ -25,8 +25,10 @@ get_window_end, is_new_object, make_freebusy, \ make_uid, to_part, uri_dict, uri_item, uri_items, \ uri_parts, uri_values -from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \ - get_duration, get_timestamp +from imiptools.dates import check_permitted_values, format_datetime, \ + get_datetime, get_default_timezone, \ + get_duration, get_time, get_timestamp, \ + to_datetime from imiptools.i18n import get_translator from imiptools.freebusy import SupportAttendee, SupportExpires from imiptools.profile import Preferences @@ -106,14 +108,28 @@ return prefs and prefs.get("TZID") or get_default_timezone() def get_window_size(self): + + "Return the period window size as an integer." + prefs = self.get_preferences() try: return prefs and int(prefs.get("window_size")) or self.default_window_size except (TypeError, ValueError): return self.default_window_size + def get_window_start(self): + + "Return the period window start as a datetime." + + prefs = self.get_preferences() + start = prefs and get_datetime(prefs.get("window_start"), {"TZID" : self.get_tzid()}) + return isinstance(start, datetime) and start or start and to_datetime(start, self.get_tzid()) + def get_window_end(self): - return get_window_end(self.get_tzid(), self.get_window_size()) + + "Return the period window end as a datetime." + + return get_window_end(self.get_tzid(), self.get_window_size(), self.get_window_start()) def is_participating(self): @@ -224,16 +240,22 @@ if self.messenger and self.messenger.sender != get_address(self.user): attr["SENT-BY"] = get_uri(self.messenger.sender) - def get_periods(self, obj, explicit_only=False): + def get_periods(self, obj, explicit_only=False, future_only=False): """ Return periods for the given 'obj'. Interpretation of periods can depend - on the time zone, which is obtained for the current user. If - 'explicit_only' is set to a true value, only explicit periods will be + on the time zone, which is obtained for the current user. + + If 'explicit_only' is set to a true value, only explicit periods will be returned, not rule-based periods. + + If 'future_only' is set to a true value, only future periods will be + returned, not all periods defined by an event starting in the past. """ - return obj.get_periods(self.get_tzid(), not explicit_only and self.get_window_end() or None) + return obj.get_periods(self.get_tzid(), + start=(future_only and self.get_window_start() or None), + end=(not explicit_only and self.get_window_end() or None)) # Store operations. @@ -1096,7 +1118,7 @@ # Obtain the affected periods. - periods = self.get_periods(obj) + periods = self.get_periods(obj, future_only=True) # Define an overriding transparency, the indicated event transparency, # or the default transparency for the free/busy entry. diff -r ae83db83bdbb -r eb97b1194e2e imiptools/data.py --- a/imiptools/data.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imiptools/data.py Sun Jun 04 20:15:44 2017 +0200 @@ -259,12 +259,12 @@ return (dtstart, dtstart_attr), (dtend, dtend_attr) - def get_periods(self, tzid, end=None, inclusive=False): + def get_periods(self, tzid, start=None, end=None, inclusive=False): """ Return periods defined by this object, employing the given 'tzid' where no time zone information is defined, and limiting the collection to a - window of time with the given 'end'. + window of time with the given 'start' and 'end'. If 'end' is omitted, only explicit recurrences and recurrences from explicitly-terminated rules will be returned. @@ -273,7 +273,7 @@ will be included. """ - return get_periods(self, tzid, end, inclusive) + return get_periods(self, tzid, start, end, inclusive) def has_period(self, tzid, period): @@ -282,7 +282,7 @@ zone information is defined, has the given 'period'. """ - return period in self.get_periods(tzid, period.get_start_point(), inclusive=True) + return period in self.get_periods(tzid, end=period.get_start_point(), inclusive=True) def has_recurrence(self, tzid, recurrenceid): @@ -292,23 +292,24 @@ """ start_point = self.get_recurrence_start_point(recurrenceid, tzid) - for p in self.get_periods(tzid, start_point, inclusive=True): + for p in self.get_periods(tzid, end=start_point, inclusive=True): if p.get_start_point() == start_point: return True return False - def get_active_periods(self, recurrenceids, tzid, end=None): + def get_active_periods(self, recurrenceids, tzid, start=None, end=None): """ Return all periods specified by this object that are not replaced by those defined by 'recurrenceids', using 'tzid' as a fallback time zone - to convert floating dates and datetimes, and using 'end' to indicate the - end of the time window within which periods are considered. + to convert floating dates and datetimes, and using 'start' and 'end' to + respectively indicate the start and end of the time window within which + periods are considered. """ # Specific recurrences yield all specified periods. - periods = self.get_periods(tzid, end) + periods = self.get_periods(tzid, start, end) if self.get_recurrenceid(): return periods @@ -1035,12 +1036,13 @@ return delegators -def get_periods(obj, tzid, end=None, inclusive=False): +def get_periods(obj, tzid, start=None, end=None, inclusive=False): """ Return periods for the given object 'obj', employing the given 'tzid' where no time zone information is available (for whole day events, for example), - confining materialised periods to before the given 'end' datetime. + confining materialised periods to after the given 'start' datetime and + before the given 'end' datetime. If 'end' is omitted, only explicit recurrences and recurrences from explicitly-terminated rules will be returned. @@ -1083,11 +1085,32 @@ end = end and min(until_dt, end) or until_dt inclusive = True + # Define a selection period with a start point. The end will be handled + # in the materialisation process below. + + selection_period = Period(start, None) + + # Obtain period instances, starting from the main period. Since counting + # must start from the first period, filtering from a start date must be + # done after the instances have been obtained. + for recurrence_start in selector.materialise(dtstart, end, parameters.get("COUNT"), parameters.get("BYSETPOS"), inclusive): + + # Determine the resolution of the period. + create = len(recurrence_start) == 3 and date or datetime recurrence_start = to_timezone(create(*recurrence_start), obj_tzid) recurrence_end = recurrence_start + main_period.get_duration() - periods.append(RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr)) + + # Create the period with accompanying metadata based on the main + # period and event details. + + period = RecurringPeriod(recurrence_start, recurrence_end, tzid, "RRULE", dtstart_attr) + + # Filter out periods before the start. + + if period.within(selection_period): + periods.append(period) else: periods = [] @@ -1138,13 +1161,14 @@ return senders -def get_window_end(tzid, days=100): +def get_window_end(tzid, days=100, start=None): """ Return a datetime in the time zone indicated by 'tzid' marking the end of a - window of the given number of 'days'. + window of the given number of 'days'. If 'start' is not indicated, the start + of the window will be the current moment. """ - return to_timezone(datetime.now(), tzid) + timedelta(days) + return to_timezone(start or datetime.now(), tzid) + timedelta(days) # vim: tabstop=4 expandtab shiftwidth=4 diff -r ae83db83bdbb -r eb97b1194e2e imiptools/handlers/scheduling/common.py --- a/imiptools/handlers/scheduling/common.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imiptools/handlers/scheduling/common.py Sun Jun 04 20:15:44 2017 +0200 @@ -3,7 +3,7 @@ """ Common scheduling functionality. -Copyright (C) 2016 Paul Boddie +Copyright (C) 2016, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -55,7 +55,7 @@ for user in users: conflicts[user] = 0 - overlapping = freebusy.get_overlapping(handler.get_periods(handler.obj)) + overlapping = freebusy.get_overlapping(handler.get_periods(handler.obj, future_only=True)) # Where scheduling cannot occur, find the busy potential users. diff -r ae83db83bdbb -r eb97b1194e2e imiptools/handlers/scheduling/freebusy.py --- a/imiptools/handlers/scheduling/freebusy.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imiptools/handlers/scheduling/freebusy.py Sun Jun 04 20:15:44 2017 +0200 @@ -3,7 +3,7 @@ """ Free/busy-related scheduling functionality. -Copyright (C) 2015, 2016 Paul Boddie +Copyright (C) 2015, 2016, 2017 Paul Boddie This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -49,7 +49,7 @@ # If newer than any old version, discard old details from the # free/busy record and check for suitability. - periods = handler.get_periods(handler.obj) + periods = handler.get_periods(handler.obj, future_only=True) freebusy = freebusy or handler.get_store().get_freebusy(handler.user) offers = handler.get_store().get_freebusy_offers(handler.user) @@ -169,7 +169,7 @@ last = None - for period in handler.get_periods(handler.obj, explicit_only=True): + for period in handler.get_periods(handler.obj, explicit_only=True, future_only=True): duration = period.get_duration() # Try and schedule periods normally since some of them may be diff -r ae83db83bdbb -r eb97b1194e2e imiptools/handlers/scheduling/quota.py --- a/imiptools/handlers/scheduling/quota.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imiptools/handlers/scheduling/quota.py Sun Jun 04 20:15:44 2017 +0200 @@ -187,7 +187,7 @@ total = timedelta(0) - for period in handler.get_periods(handler.obj): + for period in handler.get_periods(handler.obj, future_only=True): duration = period.get_duration() # Decline events whose period durations are endless. @@ -211,7 +211,7 @@ if handler.obj.possibly_recurring_indefinitely(): return None - periods = handler.get_periods(handler.obj) + periods = handler.get_periods(handler.obj, future_only=True) return periods and to_utc_datetime(periods[-1].get_end_point()) or None def _get_usage(entries): @@ -282,7 +282,7 @@ # Check the event periods against the quota's consolidated record of the # organiser's reservations. - periods = handler.get_periods(handler.obj) + periods = handler.get_periods(handler.obj, future_only=True) freebusy = handler.get_journal().get_entries(quota, organiser) scheduled = handler.can_schedule(freebusy, periods) diff -r ae83db83bdbb -r eb97b1194e2e imipweb/calendar.py --- a/imipweb/calendar.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imipweb/calendar.py Sun Jun 04 20:15:44 2017 +0200 @@ -469,7 +469,7 @@ # Requests are listed and linked to their tentative positions in the # calendar. Other participants are also shown. - request_summary = self._get_request_summary() + request_summary = self._get_request_summary(view_period) period_groups = [request_summary, freebusy] period_group_types = ["request", "freebusy"] diff -r ae83db83bdbb -r eb97b1194e2e imipweb/resource.py --- a/imipweb/resource.py Sun Jun 04 18:28:18 2017 +0200 +++ b/imipweb/resource.py Sun Jun 04 20:15:44 2017 +0200 @@ -150,9 +150,12 @@ def _get_counters(self, uid, recurrenceid=None): return self.store.get_counters(self.user, uid, recurrenceid) - def _get_request_summary(self): + def _get_request_summary(self, view_period): - "Return a list of periods comprising the request summary." + """ + Return a list of periods comprising the request summary within the given + 'view_period'. + """ summary = FreeBusyCollection() @@ -176,7 +179,9 @@ # Obtain only active periods, not those replaced by redefined # recurrences, converting to free/busy periods. - for p in obj.get_active_periods(recurrenceids, self.get_tzid(), self.get_window_end()): + for p in obj.get_active_periods(recurrenceids, self.get_tzid(), + start=view_period.get_start(), end=view_period.get_end()): + summary.append(obj.get_freebusy_period(p)) return summary diff -r ae83db83bdbb -r eb97b1194e2e tests/test_outgoing_invitation.sh --- a/tests/test_outgoing_invitation.sh Sun Jun 04 18:28:18 2017 +0200 +++ b/tests/test_outgoing_invitation.sh Sun Jun 04 20:15:44 2017 +0200 @@ -7,6 +7,7 @@ mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" echo 'share' > "$PREFS/$USER/freebusy_sharing" +echo '20141010' > "$PREFS/$USER/window_start" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request.txt" 2>> $ERROR diff -r ae83db83bdbb -r eb97b1194e2e tests/test_resource_invitation_constraints_quota_recurring_unlimited.sh --- a/tests/test_resource_invitation_constraints_quota_recurring_unlimited.sh Sun Jun 04 18:28:18 2017 +0200 +++ b/tests/test_resource_invitation_constraints_quota_recurring_unlimited.sh Sun Jun 04 20:15:44 2017 +0200 @@ -25,6 +25,7 @@ schedule_in_freebusy check_quota $QUOTA EOF +echo '20141010' > "$PREFS/$USER1/window_start" mkdir -p "$PREFS/$USER2" echo 'Europe/Oslo' > "$PREFS/$USER2/TZID" @@ -33,6 +34,7 @@ schedule_in_freebusy check_quota $QUOTA EOF +echo '20141010' > "$PREFS/$USER2/window_start" cat < "$PREFS/$USER/TZID" echo 'share' > "$PREFS/$USER/freebusy_sharing" +echo '20141010' > "$PREFS/$USER/window_start" "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-all.txt" 2>> $ERROR \ | "$SHOWMAIL" \