Cocktail is an Elixir date recurrence library based on iCalendar events. Its primary use case currently is to expand schedules with recurrence rules into streams of occurrences. For example: say you wanted to represent a repeating schedule of events that occurred every other week, on Mondays, Wednesdays and Fridays, at 10am and 4pm.
iex> schedule = Cocktail.Schedule.new(~N[2017-01-02 10:00:00])
...> schedule = Cocktail.Schedule.add_recurrence_rule(schedule, :weekly, interval: 2, days: [:monday, :wednesday, :friday], hours: [10, 16])
#Cocktail.Schedule<Every 2 weeks on Mondays, Wednesdays and Fridays on the 10th and 16th hours of the day>
Then to get a list of the first 10 occurrences of this schedule, you would do:
...> stream = Cocktail.Schedule.occurrences(schedule)
...> Enum.take(stream, 10)
[~N[2017-01-02 10:00:00], ~N[2017-01-02 16:00:00], ~N[2017-01-04 10:00:00],
~N[2017-01-04 16:00:00], ~N[2017-01-06 10:00:00], ~N[2017-01-06 16:00:00],
~N[2017-01-16 10:00:00], ~N[2017-01-16 16:00:00], ~N[2017-01-18 10:00:00],
~N[2017-01-18 16:00:00]]
Cocktail is available in Hex and can be installed
by adding cocktail
to your list of dependencies in mix.exs
:
def deps do
[
{:cocktail, "~> 0.10"}
]
end
Detailed documentation with all available options can be found at https://hexdocs.pm/cocktail.
Everything starts with a Cocktail.Schedule; create one like this:
iex> schedule = Cocktail.schedule(start_time, opts)
#Cocktail.Schedule<>
# or
...> schedule = Cocktail.Schedule.new(start_time, opts)
#Cocktail.Schedule<>
start_time
- Either aDateTime
or aNaiveDateTime
representing the beginning of your schedule.opts
:duration
- (optional) How long each occurrence is, in seconds.
Schedules are pretty useless on their own. To have them do something useful, you add recurrence rules to them. Currently, Cocktail supports:
- Monthly
- Weekly
- Daily
- Hourly
- Minutely
- Secondly
On top of these basic recurrence frequencies, you can add various options. Let's see some examples:
iex> every_other_day = Cocktail.Schedule.add_recurrence_rule(schedule, :daily, interval: 2)
#Cocktail.Schedule<Every 2 days>
...> weekly_on_mo_we_fr = Cocktail.Schedule.add_recurrence_rule(schedule, :weekly, days: [:monday, :wednesday, :friday])
#Cocktail.Schedule<Weekly on Mondays, Wednesdays and Fridays>
...> daily_at_9am_and_5pm = Cocktail.Schedule.add_recurrence_rule(schedule, :daily, hours: [9, 17])
#Cocktail.Schedule<Daily on the 9th and 17th hours of the day>
For more details about frequencies and options, see Cocktail.Schedule.add_recurrence_rule/3
Once you've got a schedule set up the way you want, you can generate a stream of occurrences that match the schedule like so:
iex> occurrences = Cocktail.Schedule.occurrences(schedule)
#Function<60.51599720/2 in Stream.unfold/2>
...> Enum.take(occurrences, 3)
[~N[2017-01-01 00:00:00], ~N[2017-01-02 00:00:00], ~N[2017-01-03 00:00:00]]
The type of each occurrence depends on what start time type you used, and wether or not you supplied a duration when creating the schedule.
If you add the duration
option when creating a schedule, you'll get Cocktail.Span
structs as occurrences, with :from
and :until
fields of the same type as your start time.
iex> schedule = Cocktail.schedule(~N[2017-01-01 00:00:00], duration: 3600) |> Cocktail.Schedule.add_recurrence_rule(:daily)
#Cocktail.Schedule<Daily>
...> occurrences = Cocktail.Schedule.occurrences(schedule)
#Function<60.51599720/2 in Stream.unfold/2>
...> Enum.take(occurrences, 3)
[%Cocktail.Span{from: ~N[2017-01-01 00:00:00], until: ~N[2017-01-01 01:00:00]},
%Cocktail.Span{from: ~N[2017-01-02 00:00:00], until: ~N[2017-01-02 01:00:00]},
%Cocktail.Span{from: ~N[2017-01-03 00:00:00], until: ~N[2017-01-03 01:00:00]}]
You can also add one-off recurrence times that don't fit into a normal recurrence pattern, and exception times if you want to exclude a time that would normally be included because of a recurrence rule:
iex> schedule = Cocktail.schedule(~N[2017-01-01 08:00:00]) |> Cocktail.Schedule.add_recurrence_rule(:daily)
#Cocktail.Schedule<Daily>
...> schedule = [~N[2017-01-01 09:00:00], ~N[2017-01-02 11:00:00], ~N[2017-01-03 17:00:00]] |> Enum.reduce(schedule, &Cocktail.Schedule.add_recurrence_time(&2, &1))
#Cocktail.Schedule<Daily>
...> schedule = Cocktail.Schedule.add_exception_time(schedule, ~N[2017-01-02 08:00:00])
#Cocktail.Schedule<Daily>
...> Cocktail.Schedule.occurrences(schedule) |> Enum.take(6)
[~N[2017-01-01 08:00:00], ~N[2017-01-01 09:00:00], ~N[2017-01-02 11:00:00],
~N[2017-01-03 08:00:00], ~N[2017-01-03 17:00:00], ~N[2017-01-04 08:00:00]]
You can convert schedules to and from the iCalendar format like this:
iex> i_calendar = Cocktail.Schedule.to_i_calendar(schedule)
"DTSTART:20170101T000000\nRRULE:FREQ=DAILY"
...> Cocktail.Schedule.from_i_calendar(i_calendar)
{:ok, #Cocktail.Schedule<Daily>}
- investigate and fix DST bugs when using zoned DateTime
- support all iCalendar RRULE options
- support week-start option
- support iCalendar EXRULE
- convert to/from JSON representation
Cocktail is heavily inspired by and based on a very similar Ruby library, ice_cube.