gem install ice_cubeice_cube is a ruby library for easily handling repeated events (schedules). The API is modeled after iCalendar events, in a pleasant Ruby syntax. The power lies in the ability to specify multiple rules, and have ice_cube quickly figure out whether the schedule falls on a certain date (.occurs_on?), or what times it occurs at (.occurrences, .first, .all_occurrences).
Imagine you want:
Every friday the 13th that falls in October
You would write:
schedule=IceCube::Schedule.newschedule.add_recurrence_rule(IceCube::Rule.yearly.day_of_month(13).day(:friday).month_of_year(:october))With ice_cube, you can specify (in increasing order of precedence):
- Recurrence Rules - Rules on how to include recurring times in a schedule
- Recurrence Times - To specifically include in a schedule
- Exception Times - To specifically exclude from a schedule
Example: Specifying a recurrence with an exception time
schedule=IceCube::Schedule.new(now=Time.now)do |s|
s.add_recurrence_rule(IceCube::Rule.daily.count(4))s.add_exception_time(now + 1.day)end# list occurrences until end_time (end_time is needed for non-terminating rules)occurrences=schedule.occurrences(end_time)# [now]# or all of the occurrences (only for terminating schedules)occurrences=schedule.all_occurrences# [now, now + 2.days, now + 3.days]# or check just a single timeschedule.occurs_at?(now + 1.day)# falseschedule.occurs_at?(now + 2.days)# true# or check just a single dayschedule.occurs_on?(Date.today)# true# or check whether it occurs between two datesschedule.occurs_between?(now,now + 30.days)# trueschedule.occurs_between?(now + 4.days,now + 30.days)# false# or the first (n) occurrencesschedule.first(2)# [now, now + 2.days]schedule.first# now# or the last (n) occurrences (if the schedule terminates)schedule.last(2)# [now + 2.days, now + 3.days]schedule.last# now + 3.days# or the next occurrenceschedule.next_occurrence(from_time)# defaults to Time.nowschedule.next_occurrences(4,from_time)# defaults to Time.nowschedule.remaining_occurrences# for terminating schedules# or the previous occurrenceschedule.previous_occurrence(from_time)schedule.previous_occurrences(4,from_time)# or include prior occurrences with a duration overlapping from_timeschedule.next_occurrences(4,from_time,:spans=>true)schedule.occurrences_between(from_time,to_time,:spans=>true)# or give the schedule a duration and ask if occurring_at?schedule=IceCube::Schedule.new(now,:duration=>3600)schedule.add_recurrence_ruleIceCube::Rule.dailyschedule.occurring_at?(now + 1800)# trueschedule.occurring_between?(t1,t2)# using end_time also sets the durationschedule=IceCube::Schedule.new(start=Time.now,:end_time=>start + 3600)schedule.add_recurrence_ruleIceCube::Rule.dailyschedule.occurring_at?(start + 3599)# trueschedule.occurring_at?(start + 3600)# false# take control and use iterationschedule=IceCube::Schedule.newschedule.add_recurrence_ruleIceCube::Rule.daily.until(Date.today + 30)schedule.each_occurrence{ |t| putst}The reason that schedules have durations and not individual rules, is to maintain compatibility with the ical RFC: http://www.kanzaki.com/docs/ical/rrule.html
To limit schedules use count or until on the recurrence rules. Setting end_time on the schedule just sets the duration (from the start time) for each occurrence.
ice_cube works great without ActiveSupport but only supports the environment's
single "local" time zone (ENV['TZ']) or UTC. To correctly support multiple
time zones (especially for DST), you should require 'active_support/time'.
A schedule's occurrences will be returned in the same class and time zone as the schedule's start_time. Schedule start times are supported as:
- Time.local (default when no time is specified)
- Time.utc
- ActiveSupport::TimeWithZone (with
Time.zone.now,Time.zone.local,time.in_time_zone(tz)) - DateTime (deprecated) and Date are converted to a Time.local
ice_cube implements its own hash-based .to_yaml, so you can quickly (and safely) serialize schedule objects in and out of your data store
It also supports partial serialization to/from ICAL. RDATE are not supported yet.
yaml=schedule.to_yamlIceCube::Schedule.from_yaml(yaml)hash=schedule.to_hashIceCube::Schedule.from_hash(hash)ical=schedule.to_icalIceCube::Schedule.from_ical(ical)ice_cube can provide ical or string representations of individual rules, or the whole schedule.
rule=IceCube::Rule.daily(2).day_of_week(:tuesday=>[1, -1],:wednesday=>[2])rule.to_ical# 'FREQ=DAILY;INTERVAL=2;BYDAY=1TU,-1TU,2WE'rule.to_s# 'Every 2 days on the last and 1st Tuesdays and the 2nd Wednesday'There are many types of recurrence rules that can be added to a schedule:
# every dayschedule.add_recurrence_ruleIceCube::Rule.daily# every third dayschedule.add_recurrence_ruleIceCube::Rule.daily(3)# every weekschedule.add_recurrence_ruleIceCube::Rule.weekly# every other week on monday and tuesdayschedule.add_recurrence_ruleIceCube::Rule.weekly(2).day(:monday,:tuesday)# for programmatic convenience (same as above)schedule.add_recurrence_ruleIceCube::Rule.weekly(2).day(1,2)# specifying a weekly interval with a different first weekday (defaults to Sunday)schedule.add_recurrence_ruleIceCube::Rule.weekly(1,:monday)# every month on the first and last days of the monthschedule.add_recurrence_ruleIceCube::Rule.monthly.day_of_month(1, -1)# every other month on the 15th of the monthschedule.add_recurrence_ruleIceCube::Rule.monthly(2).day_of_month(15)Monthly rules will skip months that are too short for the specified day of
month (e.g. no occurrences in February for day_of_month(31)).
# every month on the first and last tuesdays of the monthschedule.add_recurrence_ruleIceCube::Rule.monthly.day_of_week(:tuesday=>[1, -1])# every other month on the first monday and last tuesdayschedule.add_recurrence_ruleIceCube::Rule.monthly(2).day_of_week(:monday=>[1],:tuesday=>[-1])# for programmatic convenience (same as above)schedule.add_recurrence_ruleIceCube::Rule.monthly(2).day_of_week(1=>[1],2=>[-1])# every year on the 100th days from the beginning and end of the yearschedule.add_recurrence_ruleIceCube::Rule.yearly.day_of_year(100, -100)# every fourth year on new year's eveschedule.add_recurrence_ruleIceCube::Rule.yearly(4).day_of_year(-1)# every year on the same day as start_time but in january and februaryschedule.add_recurrence_ruleIceCube::Rule.yearly.month_of_year(:january,:february)# every third year in marchschedule.add_recurrence_ruleIceCube::Rule.yearly(3).month_of_year(:march)# for programmatic convenience (same as above)schedule.add_recurrence_ruleIceCube::Rule.yearly(3).month_of_year(3)# every hour on the same minute and second as start dateschedule.add_recurrence_ruleIceCube::Rule.hourly# every other hour, on mondaysschedule.add_recurrence_ruleIceCube::Rule.hourly(2).day(:monday)# every 10 minutesschedule.add_recurrence_ruleIceCube::Rule.minutely(10)# every hour and a half, on the last tuesday of the monthschedule.add_recurrence_ruleIceCube::Rule.minutely(90).day_of_week(:tuesday=>[-1])# every secondschedule.add_recurrence_ruleIceCube::Rule.secondly# every 15 seconds between 12:00 - 12:59schedule.add_recurrence_ruleIceCube::Rule.secondly(15).hour_of_day(12)The team over at GetJobber have open-sourced RecurringSelect, which makes working with IceCube easier in a Rails app via some nice helpers.
Check it out at https://github.com/GetJobber/recurring_select
- Andrew Vit (@avit)
- Mat Brown - mat@patch.com
- Philip Roberts
- @sakrafd
Use the GitHub issue tracker
- Contributions are welcome - I use GitHub for issue tracking (accompanying failing tests are awesome) and feature requests
- Submit via fork and pull request (include tests)
- If you're working on something major, shoot me a message beforehand