Hiccup

Build Status Code Climate

Hiccup mixes a-la-cart recurrence features into your recurring model. It doesn't dictate the data structure of your model, just the interface. It works like Devise does for authenticatable models.

Hiccup does provide a lightweight Schedule class that mixes in all of Hiccup's feature, but you don't have to use Hiccup's Schedule if you don't want to.

Usage

class Schedule
  extend Hiccup

  hiccup :enumerable,
         :validatable,
         :humanizable,
         :inferable,
         :serializable => [:ical]

end

Interface

Hiccup requires that a recurring object expose the following properties

  • kind: one of :never, :weekly, :monthly, :annually
  • start_date: the date when the recurrence should start
  • ends: either true or false, indicating whether the recurrence has an end date
  • end_date: the date when the recurrence should end
  • skip: the number of instances to skip (You'd set skip to 2 to specify an event that occurs every other week, for example)
  • weekly_pattern: an array of weekdays on which a weekly event should recur
  • monthly_pattern: an array of recurrence rules for a monthly event

Examples

# Every other Monday
Schedule.new(:kind => :weekly, :weekly_pattern => ["Monday"])

# Every year on June 21 (starting in 1999)
Schedule.new(:kind => :yearly, :start_date => Date.new(1999, 6, 21))

# The second and fourth Sundays of the month
Schedule.new(:kind => :monthly, :monthly_pattern => [[2, "Sunday"], [4, "Sunday"]])

Modules

Enumerable

Supplies methods for creating instances of a recurring pattern

Examples

schedule = Schedule.new(
  :kind => :weekly,
  :weekly_pattern => %w{Monday Wednesday Friday},
  :start_date => Date.new(2009, 3, 15))

# includes?
schedule.includes? Date.new(2009, 5, 20) # => true
schedule.includes? Date.new(2009, 3, 15) # => false (3/15/09 is a Sunday)

# occurrences_between
schedule.occurrences_between(
  Date.new(2009, 3, 26),
  Date.new(2009, 3, 31)) #=> [Fri, 27 Mar 2009, Mon, 30 Mar 2009]

# n_occurrences_before
schedule.n_occurrences_before(3, Date.new(2009, 3, 31)) # => [Mon, 30 Mar 2009, Fri, 27 Mar 2009, Wed, 25 Mar 2009]

Validatable

Mixes in ActiveModel validations for recurrence models

Humanizable

Represents a recurring pattern in a human-readable string

Examples:

# A monthly recurrence
schedule = Schedule.new(:kind => :monthly, :monthly_pattern => [[-1, "Tuesday"]])
schedule.humanize # => "The last Tuesday of every month"

# With skips
schedule = Schedule.new(:kind => :weekly, :weekly_pattern => %w[Sunday], :skip => 2)
schedule.humanize # => "Every other Sunday"

# An anniversary
schedule = Schedule.new(:kind => :annually, start_date: Date.new(2012, 10, 1))
schedule.humanize # => "Every year on October 1"

Inferable

Infers a schedule from an array of dates

Examples:

schedule = Schedule.infer %w{2012-7-9 2012-8-13 2012-9-10}
schedule.humanize # => "The second Monday of every month"

schedule = Schedule.infer %w{2010-3-4 2011-3-4 2012-3-4}
schedule.humanize # => "Every year on March 4"

schedule = Schedule.infer %w{2012-3-6 2012-3-8 2012-3-15 2012-3-20 2012-3-27 2012-3-29}
schedule.humanize # => "Every Tuesday and Thursday"

Serializable

Supports serializing and deserializing a recurrence pattern to an array of formats

Examples:

schedule = Schedule.from_ical <<-ICAL
DTSTART;VALUE=DATE-TIME:20090101T000000Z
RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH
ICAL
schedule.humanize # => "Tuesday and Thursday of every other week"

schedule = Schedule.new(
  :kind => :weekly,
  :weekly_pattern => %w{Tuesday Thursday},
  :start_date => DateTime.new(2009, 1, 1),
  :skip => 2)
schedule.to_ical # => "DTSTART;VALUE=DATE-TIME:20090101T000000Z\nRRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,TH\n"

License

Copyright (c) 2012 Bob Lail, released under the MIT license