From c991f997b96b9c4782ad1997d7f2be2350ca0a9a Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sat, 12 Jan 2019 15:54:07 +0000 Subject: [PATCH] Added support for parsing VEVENT from iCal format --- CHANGELOG.md | 1 + lib/ice_cube/parsers/ical_parser.rb | 58 +++++++++++++++++++++-------- spec/examples/from_ical_spec.rb | 42 +++++++++++++++++++++ 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0022b92..3a2ec2e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 429bd84c..78a62389 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -2,30 +2,56 @@ 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)] + + 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? diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 2ab66c3c..9477afbd 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -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(":") @@ -378,6 +389,30 @@ 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 @@ -385,6 +420,13 @@ def sorted_ical(ical) 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