views:

5538

answers:

14

I'm building a group calendar application that needs to support recurring events, but all the solutions I've come up with to handle these events seem like a hack. I can limit how far ahead one can look, and then generate all the events at once. Or I can store the events as repeating and dynamically display them when one looks ahead on the calendar, but I'll have to convert them to a normal event if someone wants to change the details on a particular instance of the event.

I'm sure there's a better way to do this, but I haven't found it yet. What's the best way to model recurring events, where you can change details of or delete particular event instances?

(I'm using Ruby, but please don't let that constrain your answer. If there's a Ruby-specific library or something, though, that's good to know.)

+7  A: 

You may want to look at iCalendar software implementations or the standard itself (RFC 2445 RFC 5545). Ones to come to mind quickly are the Mozilla projects http://www.mozilla.org/projects/calendar/ A quick search reveals http://icalendar.rubyforge.org/ as well.

Other options can be considered depending on how you're going to store the events. Are you building your own database schema? Using something iCalendar-based, etc.?

Kris Kumler
if you could just provide a link to one of these your post would be perfect
Jean
Looks like RFC2445 has been made obsolete by RFC5545 (http://tools.ietf.org/html/rfc5545)
Eric Freese
+1  A: 

You could store the events as repeating, and if a particular instance was edited, create a new event with the same event ID. Then when looking up the event, search for all events with the same event ID to get all the information. I'm not sure if you rolled your own event library, or if you're using an existing one so it may not be possible.

Vincent McNabb
+20  A: 

I would use a 'link' concept for all future recurring events. They are dynamically displayed in the calendar and link back to a single reference object. When events have taken place the link is broken and the event becomes a standalone instance. If you attempt to edit a recurring event then prompt to change all future items (i.e. change single linked reference) or change just that instance (in which case convert this to a standalone instance and then make change). The latter cased is slightly problematic as you need to keep track in your recurring list of all future events that were converted to single instance. But, this is entirely do-able.

So, in essence, have 2 classes of events - single instances and recurring events.

A: 

Store the events as repeating and dynamically display them, however allow the recurring event to contain a list of specific events that could override the default information on a specific day.

When you query the recurring event it can check for a specific override for that day.

If a user makes changes, then you can ask if he wants to update for all instances (default details) or just that day (make a new specific event and add it to the list).

If a user asks to delete all recurrences of this event you also have the list of specifics to hand and can remove them easily.

The only problematic case would be if the user wants to update this event and all future events. In which case you'll have to split the recurring event into two. At this point you may want to consider linking recurring events in some way so you can delete them all.

Andrew Johnson
+1  A: 
  1. Keep track of a recurrence rule (probably based on iCalendar, per @Kris K.). This will include a pattern and a range (Every third Tuesday, for 10 occurrences).
  2. For when you want to edit/delete a specific occurrence, keep track of exception dates for the above recurrence rule (dates where the event doesn't occur as the rule specifies).
  3. If you deleted, that's all you need, if you edited, create another event, and give it a parent ID set to the main event. You can choose whether to include all of the main event's information in this record, or if it only holds the changes and inherits everything that doesn't change.

Note that if you allow recurrence rules that don't end, you have to think about how to display your now infinite amount of information.

Hope that helps!

bdukes
+3  A: 

I'd recommend using the power of the date library and the semantics of the range module of ruby. A recurring event is really a time, a date range (a start & end) and usually a single day of the week. Using date & range you can answer any question:

#!/usr/bin/ruby
require 'date'

start_date = Date.parse('2008-01-01')
end_date   = Date.parse('2008-04-01')
wday = 5 # friday

(start_date..end_date).select{|d| d.wday == wday}.map{|d| d.to_s}.inspect

Produces all days of the event, including the leap year!

# =>"[\"2008-01-04\", \"2008-01-11\", \"2008-01-18\", \"2008-01-25\", \"2008-02-01\", \"2008-02-08\", \"2008-02-15\", \"2008-02-22\", \"2008-02-29\", \"2008-03-07\", \"2008-03-14\", \"2008-03-21\", \"2008-03-28\"]"
Purfideas
This is not very flexible. A recurring event model would often require specifying the repetition period (hourly, weekly, fortnightly, etc). Additionally the recurrence might not be qualified by a total number, rather an end date for the last occurrence
bjeanes
+1  A: 

From these answers, I've sort of sifted out a solution. I really like the idea of the link concept. Recurring events could be a linked list, with the tail knowing its recurrence rule. Changing one event would then be easy, because the links stay in place, and deleting an event is easy as well - you just unlink an event, delete it, and re-link the event before and after it. You still have to query recurring events every time someone looks at a new time period never been looked at before on the calendar, but otherwise this is pretty clean.

Clinton R. Nixon
A: 

For .NET programmers who are prepared to pay some licensing fees, you might find Aspose.Network useful... it includes an iCalendar compatible library for recurring appointments.

Shaul
A: 

There can be many problems with recurring events, let me highlight a few that I know of.

Solution 1 - no instances

Store original appointment + recurrence data, do not store all the instances.

Problems:

  • You'll have to calculate all the instances in a date window when you need them, costly
  • Unable to handle exceptions (ie. you delete one of the instances, or move it, or rather, you can't do this with this solution)

Solution 2 - store instances

Store everything from 1, but also all the instances, linked back to the original appointment.

Problems:

  • Takes a lot of space (but space is cheap, so minor)
  • Exceptions must be handled gracefully, especially if you go back and edit the original appointment after making an exception. For instance, if you move the third instance one day forward, what if you go back and edit the time of the original appointment, re-insert another on the original day and leave the moved one? Unlink the moved one? Try to change the moved one appropriately?

Of course, if you're not going to do exceptions, then either solution should be fine, and you basically choose from a time/space trade off scenario.

Lasse V. Karlsen
What if you have a recurring appointment with no end date? As cheap as space is, you don't have infinite space, so Solution 2 is a non-starter there...
Shaul
+1  A: 

I'm using the database schema as described below to store the recurrence parameters

http://github.com/bakineggs/recurring%5Fevents%5Ffor

Then I use runt to dynamically calculate the dates.

http://github.com/texel/runt

liangzan
+1  A: 

"What if you have a recurring appointment with no end date? As cheap as space is, you don't have infinite space, so Solution 2 is a non-starter there... – Shaul May 20 at 8:14"

May I suggest that "no end date" can be resolved to an end date at the end of the century. Even for a dayly event the amount of space remains cheap.

poumtatalia
How soon we forget the lessons of y2k... :)
Hightechrider
A: 

Also check out the CalDav standards.

Eric Freese
A: 

.NET developers might like to look at http://www.ddaysoftware.com/Pages/Projects/DDay.iCal/Default.aspx also

Hightechrider
+1  A: 

I'm working with the following:

  • h_IMNOTSPAM_ttp://github.com/elevation/event_calendar - model and helper for a calendar
  • h_IMNOTSPAM_ttp://github.com/seejohnrun/ice_cube - awesome recurring gem
  • h_IMNOTSPAM_ttp://github.com/justinfrench/formtastic - easy forms

and a gem in progress that extends formtastic with an input type :recurring (form.schedule :as => :recurring), which renders an iCal-like interface and a before_filter to serialize the view into an IceCube object again, ghetto-ly.

My idea is to make it incredibility easy to add recurring attributes to a model and connect it easily in the view. All in a couple of lines.


So what does this give me? Indexed, Edit-able, Recurring attributes.

events stores a single day instance, and is used in the calendar view/helper say task.schedule stores the yaml'd IceCube object, so you can do calls like : task.schedule.next_suggestion.

Recap: I use two models, one flat, for the calendar display, and one attribute'd for the functionality.

Vee