Skip to content

Instantly share code, notes, and snippets.

@jfeaver
Last active August 20, 2023 06:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfeaver/6fb0eb391c86a86511d0a4c77b6a64f0 to your computer and use it in GitHub Desktop.
Save jfeaver/6fb0eb391c86a86511d0a4c77b6a64f0 to your computer and use it in GitHub Desktop.
Parse full RRULEs to pass on to the rrule gem
# frozen_string_literal: true
# The rrule gem parses only the recurring rules of the RRULE specification.
# That is, the part that comes after "RRULE:" on the last line of an RRULE. To
# include the DTSTART, and EXDATE as options to the rrule gem, let's add our
# own interface. We remove the "RRULE:" and parse other rules and send them to
# the gem with the desired structure.
#
# Example RRULE which is now parseable as a string:
#
# EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z
# DTSTART:19960401T000000Z
# RRULE:FREQ=WEEKLY;BYHOUR=2;BYMINUTE=0;BYDAY=WE
#
# See the RRULE specification:
# https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html
module RRule::Parse
refine RRule.singleton_class do
def parse(rrule, **options)
lines = rrule.split("\n")
dt_start = nil
tz_id = nil
ex_date = nil
rule = nil
lines.each do |line|
case
when line.match?(/^DTSTART/)
dt_start = Time.new(line.match(/:(.*)$/)[1]).utc
tz_id_match = line.match(/TZID=(.*):/)
tz_id = tz_id_match[1] if tz_id_match
when line.match?(/^EXDATE/)
ex_date = line.match(/:(.*)$/)[1].split(',').map { |time| Time.new(time).utc }
when line.match?(/^RRULE/)
rule = line.sub(/^RRULE*:/, '')
when line.include?('FREQ')
rule = line
end
end
raise RRule::InvalidRRule unless rule
parsed_options = {
dtstart: dt_start,
tzid: tz_id,
exdate: ex_date
}.compact
RRule::Rule.new(rule, **parsed_options.merge(options))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment