Skip to content

Commit

Permalink
Added support for parsing VEVENT from iCal format
Browse files Browse the repository at this point in the history
  • Loading branch information
iainbeeston committed Jun 8, 2022
1 parent 10ae8dc commit d4423a5
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added
- Added support for parsing VEVENT from iCal format (#465)
- Indonesian translations. ([#505](https://github.com/seejohnrun/ice_cube/pull/505)) by [@achmiral](https://github.com/achmiral)

### Changed
Expand Down
60 changes: 43 additions & 17 deletions lib/ice_cube/parsers/ical_parser.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,57 @@
module IceCube
class IcalParser
def self.schedule_from_ical(ical_string, options = {})
def self.schedule_from_ical(ical_string, _options = {})
data = {}
current_state = :parsing_calendar
ical_string.each_line do |line|
(property, value) = line.split(":")
(property, _tzid) = property.split(";")
case property
when "DTSTART"
data[:start_time] = TimeUtil.deserialize_time(value)
when "DTEND"
data[:end_time] = TimeUtil.deserialize_time(value)
when "RDATE"
data[:rtimes] ||= []
data[:rtimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
when "EXDATE"
data[:extimes] ||= []
data[:extimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
when "DURATION"
data[:duration] # FIXME
when "RRULE"
data[:rrules] ||= []
data[:rrules] += [rule_from_ical(value)]

if current_state == :parsing_calendar
current_state = parse_calendar(data, property, value)
elsif current_state == :parsing_event
current_state = parse_event(data, property, value)
end
end
Schedule.from_hash data
end

def self.parse_calendar(data, property, value)
case property
when "DTSTART"
data[:start_time] = TimeUtil.deserialize_time(value)
when "DTEND"
data[:end_time] = TimeUtil.deserialize_time(value)
when "RDATE"
data[:rtimes] ||= []
data[:rtimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
when "EXDATE"
data[:extimes] ||= []
data[:extimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
when "DURATION"
data[:duration] # FIXME
when "RRULE"
data[:rrules] ||= []
data[:rrules] += [rule_from_ical(value)]
when "BEGIN"
return :parsing_event if value.chomp == "VEVENT"
end

:parsing_calendar
end

def self.parse_event(data, property, value)
case property
when "DTSTART"
data[:rtimes] ||= []
data[:rtimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
when "END"
return :parsing_calendar if value.chomp == "VEVENT"
end

:parsing_event
end

def self.rule_from_ical(ical)
raise ArgumentError, "empty ical rule" if ical.nil?

Expand Down
42 changes: 42 additions & 0 deletions spec/examples/from_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ module IceCube
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYDAY=FR
ICAL

ical_string_with_events = <<-ICAL.gsub(/^\s*/, "")
BEGIN:VEVENT
DTSTART;VALUE=DATE:20120406
DTEND;VALUE=DATE:20120407
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20120409
DTEND;VALUE=DATE:20120410
END:VEVENT
ICAL

def sorted_ical(ical)
ical.split("\n").sort.map { |field|
k, v = field.split(":")
Expand Down Expand Up @@ -378,13 +389,44 @@ def sorted_ical(ical)
end
end

describe "one-off dates" do
it "handles a specific day as a recurrence time" do
start_time = Time.now

schedule = IceCube::Schedule.new(start_time)
schedule.add_recurrence_time(start_time)

ical = schedule.to_ical
expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical))
end

it "converts events to recurrence times" do
start_time = Time.utc(2019, 1, 16)

schedule = IceCube::Schedule.new(start_time)
schedule.add_recurrence_time(start_time)

event_ical = "DTSTART:20190116T000000Z\nBEGIN:VEVENT\nDTSTART:20190116T000000Z\nDTEND:20190117T000000Z\nEND:VEVENT"

ical = schedule.to_ical
expect(sorted_ical(IceCube::Schedule.from_ical(event_ical).to_ical)).to eq(sorted_ical(ical))
end
end

describe "multiple rules" do
it "handles multiple recurrence rules" do
schedule = IceCube::Schedule.from_ical ical_string_with_multiple_rules
expect(schedule.recurrence_rules.count).to eq(2)
end
end

describe "events" do
it "converts each event into it's own recurrence time" do
schedule = IceCube::Schedule.from_ical ical_string_with_events
expect(schedule.recurrence_times.count).to eq(2)
end
end

describe "invalid ical data" do
shared_examples_for("an invalid ical string") do
it do
Expand Down

0 comments on commit d4423a5

Please sign in to comment.