I've now spent several weeks updating the Events module to give much more flexibility around recurring events. Up to version 4.0.2, recurring events were represented by one record in the database. I.e. All instances of a recurring event were exactly the same (Description, price, time, etc), if you changed/deleted one, you changed/deleted them all.
The good news is that from 4.1.0 this will all change. Now all attributes of an event instance can be changed individually, this is because each instance is represented by its own record in the database, with a new table to record the 'Master' set of data.
Normally I would post much of the code here, so you can see what is going on, but this is too big to do that, so I thought I'd at least give you a view of the logic. No fancy screenshots either, since the only visible change is a couple of extra links (lots of code change, little GUI change).
There is now a new table EventsRecurMaster which stores the master set of data, from which we can identify any differentials on individual instances of an event. Individual occurrence records look the same as previous single event items (RepeatType of N), to ensure backward compatibility. No attributes have been removed from the Events table at this time.
Importantly EventsRecurMaster record stores the recurrence rule in a format that is very largely based on the iCal specification (RFC 2445). I say largely, because the dates are not. That is because iCal dates are required to be UTC dates, but the Events Module does not currently support storage of dates in UTC (because DNN does not, because .Net 2.0 does not). As and when UTC support is added, this can be brought in line with iCal.
The module does not support all iCal recurrence variations, just enough to meet the options available in the module. However, it should be fairly easy to extend this, and should enable easier import/export when that functionality is added.
Add Event
When an event is added:
- A RRULE (iCal Recurrence Rule) is created and stored in EventsRecurMaster, and used to create all the individual occurrence records.
- The end date is stored in EventsRecurMaster as the iCal named Until field. It is currently a standard Date/Time field.
- The start date/time is stored in EventsRecurMaster as the iCal named DTSTART field. It is currently a standard Date/Time field.
- All attributes are added to EventsRecurMaster, and applied to all occurrences of the event.
- The culture of the user creating the event is stored on EventsRecurMaster
Change Event
When you click on Edit on the Event Detail screen or the Edit icon in List/Day view or the event link in Moderate view, you will enter the Edit dialogue editing the single instance of the event:-
- Any fields you change are stored to the single event occurrence
- EventsRecurMaster is not updated
When you click Edit Series on the Event Detail screen, you enter the Edit dialogue edit all instances of the event:-
- EventsRecurMaster is updated with all changes
- Any fields you change are applied to all instances of the event, which have not been previously changed by editing the single instance. This means that if I have previously edited the name on a single instance, and now edit the name and description on all instances, then description is changed across all instances, and the name is changed on all instances apart from the one previously changed.
- If you changed the time of an event, it will be changed across all instances, provided the time (note time not date) on each instance has not previously been changed.
- If you change the dates/recurrence pattern, then a new set of recurrences are calculated, and then matched against existing recurrences and added/updated/deleted as necessary.
- The culture stored on EventsRecurMaster is never updated
Delete Event
When you click on Delete on the Event Detail screen, or Delete when editing in single instance mode or when a single event moderation request is denied then:-
- If this is a single event, then the EventsRecurMaster is deleted, and deletes the related Event instance
- If this is a recurring event, the single instance is marked as cancelled (Cancelled attribute set to True) - This is so that future updates don't recreate the event
When you click on Delete Series when editing in all instance mode, or when a recurring event moderation request is denied then:
- EventsRecurMaster is deleted, and deletes all Event instances
Moderation
A new table has been added to Event Moderation that shows recurring events:-
- If a recurring event is approved, all occurrences are approved
- If a recurring event is denied, EventsRecurMaster is deleted and all event instances are deleted
- If a single occurrence of a recurring event is denied, the single instance is Cancelled
Data Flow
Because each instance of an event has it's own record, there is no longer the need to pass the selected date in the URL to Event Details. This should remove some errors....
RRULE
The following are examples of recurrence rules against their previous equivalents. For those who don't know the RepeatTypes, these are are displayed in the Edit dialogue
| Repeat Type | RRULE Example | Meaning |
| N | blank | Single event |
| P1 | FREQ=DAILY;INTERVAL=1 FREQ=WEEKLY;INTERVAL=2 FREQ=MONTHLY;INTERVAL=1 FREQ=YEARLY;INTERVAL=1 | Repeats daily Repeats every other week Repeats monthly Repeats yearly |
| W1 | FREQ=WEEKLY;WKST=MO;INTERVAL=1;BYDAY=SU,MO | Repeats weekly, on Sunday and Monday, week starting on Monday * |
| M1 | FREQ=MONTHLY;INTERVAL=1;BYDAY=+1WE FREQ=MONTHLY;INTERVAL=1;BYDAY=-1MO | Repeats monthly on the first Wednesday of the month Repeats monthly on the last Monday of the month |
| M2 | FREQ=MONTHLY;INTERVAL=3;BYMONTHDAY=+25 | Repeats every third month, on the 25th |
| Y1 | FREQ=YEARLY;INTERVAL=1;BYMONTH=8;BYMONTHDAY=+31 | Repeats every year on the 31st August |
* Week starting on is important for weekly recurring events with an interval greater than 1. e.g. If your start date for the recurrence is Wednesday 3rd September and you specify Wednesday and Sunday as the occurrence days and your interval is 2 then:
- If the WKST is MO, then occurrences will happen on 3rd/7th, 17th/21st September
- If the WKST is SU, then occurrences will happen on 3rd, 14th/17th September - since in this instance the 14th is in the week two weeks after the start of the week the start date is in (start of week is 31st August)
This may well differ from recurrence pattern calculations in 4.0.2, but brings the module into line with standards.
For this reason the culture is never changed on EventsRecurMaster, since this could potentially affect the recurrence pattern for these events. iCal does allow WKST other than SU/MO, but I have limited to these two for the current build, since I have tried not to change the GUI with this version.
Upgrade
During upgrade:-
- Via the SQLDataProvider
- A dummy EventsRecurMaster is created for each Events module instance
- Existing Events for each module are linked to the relevant EventsRecurMaster
- A module Setting "RecurDummy" is created with the relevant RecurMasterID for each Module
- When IUpgradeable is triggered
- The RecurDummy setting for each Module instance is retrieved
- Each existing event is retrieved for the Module
- A RRULE is created, and all values stored on EventsRecurMaster
- The culture of the user who created the original event is stored on EventsRecurMaster
- A new Event record is created for all instances other than the first - we don't delete and re-add the first record, since any existing Notification and Enrollment records are linked to it
- The first record is updated to make it into a single instance and linked to the new EventsRecurMaster
- Existing Notification records are linked to the relevant new Event record
- Existing Enrollment records are linked to the relevant new Event record
- At the end of IUpgradeable, a check is done to ensure all Events are unlinked from the dummy EventsRecurMasters
- If they are (which they should be), then the dummy EventsRecurMaster is deleted
- If not, then the dummy EventsRecurMaster (so as to be non-destructive) is retained, and a note raised in the event log