Ruby: Experience with Gems for recurring calendar events?

Alexey picture Alexey · Mar 14, 2011 · Viewed 7.3k times · Source

I want to find a ruby gem for my project to work with reccuring events that matches following requirements:

  • can process patterns like "Weekly on Tuesday and Wednessday", or "Monthly on the last Tuesday"
  • can compute next occurence
  • can serialize/deserialize a pattern into string to store in database
  • serialization has stable format (e.i. it will be able to load even after upgrading)
  • work at least with following pattern components: Time, Day Of Week, Date of Month, nth day of week in month;
  • can repeat daily, weekly, monthly or with interval of n days, weeks or months
  • can represent a pattern in natural English language
  • can parse a pattern from English (optional)
  • can export to some popular format like iCal (optional)
  • can integrate with other gems/systems for calendar and task management (optional)
  • support of Active Record - parameter parsing, validation (optional)
  • has enough tests, and few bugs, more then 1 user :)
  • has reasonable performance

I found two relevant candidates:

  • Tickle - the good is that it can parse English.
  • Ice_Cube(+Schedule-Attributes) - the good is that it most popular and can export to iCal

Could you suggest a gem and describe positive and negative experiences with it?

Maybe also you could add some relevant criteria/requirements, that i've not mentioned.

P.S. Pease someone with 1,5K+ add recurring_events tag. Thanks

Answer

Alexey picture Alexey · Apr 19, 2011

I end up using Ice_Cube for the following reasons:

  • Most popular
  • can compute next occurence
  • can serialize/deserialize a pattern into string to store in database
  • serialization has stable format (e.i. it will be able to load even after upgrading)
  • work at least with following pattern components: Time, Day Of Week, Date of Month, nth day of week in month;
  • can repeat daily, weekly, monthly or with interval of n days, weeks or months
  • can parse a pattern from English (optional)
  • can export to some popular format like iCal (optional)

These on my criteria are not fullfilled by it:

  • can represent a pattern in natural English language
  • support of Active Record - parameter parsing, validation (optional)

This one is not verified:

  • has reasonable performance

Creating Ice_Cube::Schedule from user input in Rails is not very convinient, but doable:

class EntryForm < FormModel

  include IceCube
  class_eval &ValidatesTimelinessSupport[{:start_date => :datetime}]

  Units = [Day = 'day', Week = 'week']
  Intervals = %w[0 1 2 3 4 5 6 7 8 9]
  Week_Days = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]

  Days_With_Letters = Week_Days.zip(%w[S M T W T F S])

  attr_accessible_accessors :interval, :unit, :start_date
  attr_accessible_accessors *Week_Days

  def_delegators :@model, :display_title, :schedule_yaml, :schedule_yaml=

  validates_date :start_date, :allow_blank => true
  validates_inclusion_of :unit, :in => Units
  validates_inclusion_of :interval, :in => Intervals
  validates_inclusion_of :complete, :in => %w[0 1], :allow_blank => true
  Week_Days.each { |day| validates_inclusion_of day, :in => %w[0 1], :allow_blank => true }

  before_edit {
    if not schedule_yaml.blank? and hash = YAML::load(schedule_yaml)
      schedule = Schedule.from_hash(hash)
    end

    if schedule and rule = schedule.rrules.first
      @start_date = schedule.start_date

      rule_hash = rule.to_hash
      @interval = rule_hash[:interval]

      case rule
      when DailyRule
        @unit = Day
      when WeeklyRule
        @unit = Week
        rule_hash[:validations][:day].try :each do |day_index|
          send "#{Week_Days[day_index]}=", 1
        end
      end

    else
      @start_date = Date.today
      @interval = 1
      @unit = Day
    end
  }

  before_save {
      sd = @start_date.blank? ?
          Date.today.to_all_day :
          @start_date.parse_date_in_timezone
      i = @interval.to_i
      schedule = Schedule.new(sd)


      rule = case @unit
        when Day
          Rule.daily i
        when Week
          Rule.weekly(i).day(
            *Week_Days.
            select { |day| send(day).to_i == 1 } )
      end

      schedule.add_recurrence_rule(rule)

      self.schedule_yaml = schedule.to_yaml
    end
  }
end