Motivation
At the moment, the calculation of event recurrences is spread over the
- events API:
|
def transform_event_recurrences(event_translation, today): |
|
""" |
|
Yield all future recurrences of the event. |
|
|
|
:param event_translation: The event translation object which should be converted |
|
:type event_translation: ~integreat_cms.cms.models.events.event_translation.EventTranslation |
|
|
|
:param today: The first date at which event may be yielded |
|
:type today: ~datetime.date |
|
|
|
:return: An iterator over all future recurrences up to ``settings.API_EVENTS_MAX_TIME_SPAN_DAYS`` |
|
:rtype: Iterator[:class:`~datetime.date`] |
|
""" |
|
|
|
event = event_translation.event |
|
recurrence_rule = event.recurrence_rule |
|
if not recurrence_rule: |
|
return |
|
|
|
event_is_invalid = ( |
|
recurrence_rule.recurrence_end_date |
|
and recurrence_rule.recurrence_end_date < today |
|
) |
|
if event_is_invalid: |
|
return |
|
|
|
start_date = event.start_local.date() |
|
event_translation.id = None |
|
|
|
# Calculate all recurrences of this event |
|
for recurrence_date in recurrence_rule.iter_after(start_date): |
|
if recurrence_date - max(start_date, today) > timedelta( |
|
days=settings.API_EVENTS_MAX_TIME_SPAN_DAYS |
|
): |
|
break |
|
if recurrence_date < today or recurrence_date == start_date: |
|
continue |
|
|
|
yield transform_event_translation(event_translation, recurrence_date) |
- Event model:
|
def get_occurrences(self, start, end): |
|
""" |
|
Get occurrences of the event that overlap with ``[start, end]``. |
|
Expects ``start < end``. |
|
|
|
:param start: the begin of the requested interval. |
|
:type start: ~datetime.datetime |
|
|
|
:param end: the end of the requested interval. |
|
:type end: ~datetime.datetime |
|
|
|
:return: start datetimes of occurrences of the event that are in the given timeframe |
|
:rtype: list [ ~datetime.datetime ] |
|
""" |
|
event_start = self.start |
|
event_end = self.end |
|
if self.is_recurring is not None: |
|
return self.recurrence_rule.to_ical_rrule().between(start, end) |
|
return ( |
|
[event_start] |
|
if start <= event_start <= end or start <= event_end <= end |
|
else [] |
|
) |
- RecurrenceRule model:
|
def advance(): |
|
""" |
|
Get the next occurrence by this rule |
|
|
|
:return: date objects |
|
:rtype: Iterator[:class: `~datetime.date`] |
|
""" |
|
|
|
nonlocal next_recurrence |
|
if self.frequency == frequency.DAILY: |
|
yield |
|
next_recurrence += timedelta(days=1) |
|
elif self.frequency == frequency.WEEKLY: |
|
# Yield each day of the week that is valid, since |
|
# ``interval`` should apply here only on weekly basis |
|
for weekday in sorted(self.weekdays_for_weekly): |
|
if weekday < next_recurrence.weekday(): |
|
continue |
|
next_recurrence += timedelta( |
|
days=weekday - next_recurrence.weekday() |
|
) |
|
yield |
|
# advance to the next monday |
|
next_recurrence += timedelta(days=7 - next_recurrence.weekday()) |
|
elif self.frequency == frequency.MONTHLY: |
|
next_recurrence = get_nth_weekday( |
|
next_recurrence, self.weekday_for_monthly, self.week_for_monthly |
|
) |
|
if next_recurrence < start_date: |
|
next_recurrence = get_nth_weekday( |
|
next_month(next_recurrence), |
|
self.weekday_for_monthly, |
|
self.week_for_monthly, |
|
) |
|
yield |
|
next_recurrence = next_month(next_recurrence) |
|
elif self.frequency == frequency.YEARLY: |
|
yield |
|
|
|
# It is not possible to go simply to the next year if the current date is february 29 |
|
year_dif = 1 |
|
while True: |
|
try: |
|
next_recurrence = next_recurrence.replace( |
|
year=next_recurrence.year + year_dif |
|
) |
|
break |
|
except ValueError: |
|
year_dif += 1 |
Proposed Solution
Refactor the code so it
- Isn't spread over different modules anymore (probably the recurrence rule model is the most sensible place for it)
- Uses the iCal rrule to calculate the recurrences instead of the custom function
Additional Context
Also, there is a small bug currently that recurring events which spread over multiple days are not shown in the API when the occurrence is ongoing but started e.g. yesterday.
Motivation
At the moment, the calculation of event recurrences is spread over the
integreat-cms/integreat_cms/api/v3/events.py
Lines 129 to 167 in a15be10
integreat-cms/integreat_cms/cms/models/events/event.py
Lines 216 to 238 in a15be10
integreat-cms/integreat_cms/cms/models/events/recurrence_rule.py
Lines 126 to 174 in a15be10
Proposed Solution
Refactor the code so it
Additional Context
Also, there is a small bug currently that recurring events which spread over multiple days are not shown in the API when the occurrence is ongoing but started e.g. yesterday.