Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for parsing VEVENT from iCal format #465

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
57 changes: 41 additions & 16 deletions lib/ice_cube/parsers/ical_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,55 @@ module IceCube
class IcalParser
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)]

case current_state
when :parsing_calendar then current_state = parse_calendar(data, property, value)
when :parsing_event then 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