Skip to content
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
14 changes: 13 additions & 1 deletion lib/ice_cube/builders/ical_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,22 @@ def self.ical_utc_format(time)

def self.ical_format(time, force_utc)
time = time.dup.utc if force_utc

# Keep timezone. strftime will serializer short versions of time zone (eg. EEST),
# which are not reversivible, as there are many repeated abbreviated zones. This will result in
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# which are not reversivible, as there are many repeated abbreviated zones. This will result in
# which are not reversible, as there are many repeated abbreviated zones. This will result in

# issues in parsing
if time.respond_to?(:time_zone)
tz_id = time.time_zone.name
return ";TZID=#{tz_id}:#{IceCube::I18n.l(time, format: "%Y%m%dT%H%M%S")}" # local time specified"
end

if time.utc?
":#{IceCube::I18n.l(time, format: "%Y%m%dT%H%M%SZ")}" # utc time
else
";TZID=#{IceCube::I18n.l(time, format: "%Z:%Y%m%dT%H%M%S")}" # local time specified
# Convert to UTC as TZID=+xxxx format is not recognized by JS libraries
warn "IceCube: Time object does not have timezone info. Assuming UTC: #{caller(1..1).first}"
Comment on lines +50 to +51
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Convert to UTC as TZID=+xxxx format is not recognized by JS libraries
warn "IceCube: Time object does not have timezone info. Assuming UTC: #{caller(1..1).first}"
# Convert to UTC as TZID=+xxxx format is not recognized by JS libraries
warn "IceCube: Time object does not have timezone info. Coercing into UTC: #{caller(1..1).first}"

utc_time = time.dup.utc
":#{IceCube::I18n.l(utc_time, format: "%Y%m%dT%H%M%SZ")}" # converted to utc time
end
end

Expand Down
33 changes: 28 additions & 5 deletions lib/ice_cube/parsers/ical_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@ def self.schedule_from_ical(ical_string, options = {})
data = {}
ical_string.each_line do |line|
(property, value) = line.split(":")
(property, _tzid) = property.split(";")
(property, tzid_param) = property.split(";")

# Extract TZID if present
tzid = nil
if tzid_param && tzid_param.start_with?("TZID=")
tzid = tzid_param[5..-1] # Remove "TZID=" prefix
end

case property
when "DTSTART"
data[:start_time] = TimeUtil.deserialize_time(value)
data[:start_time] = deserialize_time_with_tzid(value, tzid)
when "DTEND"
data[:end_time] = TimeUtil.deserialize_time(value)
data[:end_time] = deserialize_time_with_tzid(value, tzid)
when "RDATE"
data[:rtimes] ||= []
data[:rtimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
data[:rtimes] += value.split(",").map { |v| deserialize_time_with_tzid(v, tzid) }
when "EXDATE"
data[:extimes] ||= []
data[:extimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
data[:extimes] += value.split(",").map { |v| deserialize_time_with_tzid(v, tzid) }
when "DURATION"
data[:duration] # FIXME
when "RRULE"
Expand All @@ -26,6 +33,22 @@ def self.schedule_from_ical(ical_string, options = {})
Schedule.from_hash data
end

def self.deserialize_time_with_tzid(time_value, tzid)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should maybe be moved into the TimeUtil class since it's a wrapper for the TimeUtil.deserialize_time

if tzid.nil? || tzid.empty?
# No TZID, use standard deserialization
TimeUtil.deserialize_time(time_value)
else
# TZID is a timezone name - Assume it's a valid timezone in a try-catch block
begin
TimeUtil.deserialize_time({time: time_value, zone: tzid})
rescue ArgumentError
# If the timezone is invalid, fall back to standard deserialization
# Perhaps we want to log this?
TimeUtil.deserialize_time(time_value)
Comment on lines +44 to +47
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we issue a warning here as well?

end
end
end

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

Expand Down
3 changes: 2 additions & 1 deletion lib/ice_cube/time_util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def self.serialize_time(time)
case time
when Time, Date
if time.respond_to?(:time_zone)
{time: time.utc, zone: time.time_zone.name}
# avoid .utc as it changes the object timezone
{time: time.getutc, zone: time.time_zone.name}
else
time
end
Expand Down
2 changes: 1 addition & 1 deletion spec/examples/from_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ module IceCube
ICAL

ical_string_with_multiple_rules = <<-ICAL.gsub(/^\s*/, "")
DTSTART;TZID=CDT:20151005T195541
DTSTART;TZID=America/Chicago:20151005T195541
RRULE:FREQ=WEEKLY;BYDAY=MO,TU
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYDAY=FR
ICAL
Expand Down
24 changes: 12 additions & 12 deletions spec/examples/to_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
it "should be able to serialize a base schedule to ical in local time" do
Time.zone = "Eastern Time (US & Canada)"
schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0))
expect(schedule.to_ical).to eq("DTSTART;TZID=EDT:20100510T090000")
expect(schedule.to_ical).to eq("DTSTART;TZID=Eastern Time (US & Canada):20100510T090000")
end

it "should be able to serialize a base schedule to ical in UTC time" do
Expand All @@ -110,7 +110,7 @@
schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0))
schedule.add_recurrence_rule IceCube::Rule.weekly
# test equality
expectation = "DTSTART;TZID=PDT:20100510T090000\n"
expectation = "DTSTART;TZID=Pacific Time (US & Canada):20100510T090000\n"
expectation << "RRULE:FREQ=WEEKLY"
expect(schedule.to_ical).to eq(expectation)
end
Expand All @@ -120,7 +120,7 @@
schedule = IceCube::Schedule.new(Time.zone.local(2010, 10, 20, 4, 30, 0))
schedule.add_recurrence_rule IceCube::Rule.weekly.day_of_week(monday: [2, -1])
schedule.add_recurrence_rule IceCube::Rule.hourly
expectation = "DTSTART;TZID=EDT:20101020T043000\n"
expectation = "DTSTART;TZID=Eastern Time (US & Canada):20101020T043000\n"
expectation << "RRULE:FREQ=WEEKLY;BYDAY=2MO,-1MO\n"
expectation << "RRULE:FREQ=HOURLY"
expect(schedule.to_ical).to eq(expectation)
Expand All @@ -131,7 +131,7 @@
schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0))
schedule.add_exception_rule IceCube::Rule.weekly
# test equality
expectation = "DTSTART;TZID=PDT:20100510T090000\n"
expectation = "DTSTART;TZID=Pacific Time (US & Canada):20100510T090000\n"
expectation << "EXRULE:FREQ=WEEKLY"
expect(schedule.to_ical).to eq(expectation)
end
Expand All @@ -141,7 +141,7 @@
schedule = IceCube::Schedule.new(Time.zone.local(2010, 10, 20, 4, 30, 0))
schedule.add_exception_rule IceCube::Rule.weekly.day_of_week(monday: [2, -1])
schedule.add_exception_rule IceCube::Rule.hourly
expectation = "DTSTART;TZID=EDT:20101020T043000\n"
expectation = "DTSTART;TZID=Eastern Time (US & Canada):20101020T043000\n"
expectation << "EXRULE:FREQ=WEEKLY;BYDAY=2MO,-1MO\n"
expectation << "EXRULE:FREQ=HOURLY"
expect(schedule.to_ical).to eq(expectation)
Expand Down Expand Up @@ -192,10 +192,10 @@
expect(schedule.duration).to eq(3600)
end

it "should default to to_ical using local time" do
it "should default to to_ical using UTC when there is no timezone info" do
time = Time.now
schedule = IceCube::Schedule.new(Time.now)
expect(schedule.to_ical).to eq("DTSTART;TZID=#{time.zone}:#{time.strftime("%Y%m%dT%H%M%S")}") # default false
schedule = IceCube::Schedule.new(time)
expect(schedule.to_ical).to eq("DTSTART:#{time.utc.strftime("%Y%m%dT%H%M%S")}Z") # converts local to UTC
end

it "should not have an rtime that duplicates start time" do
Expand All @@ -207,10 +207,10 @@

it "should be able to receive a to_ical in utc time" do
time = Time.now
schedule = IceCube::Schedule.new(Time.now)
expect(schedule.to_ical).to eq("DTSTART;TZID=#{time.zone}:#{time.strftime("%Y%m%dT%H%M%S")}") # default false
expect(schedule.to_ical(false)).to eq("DTSTART;TZID=#{time.zone}:#{time.strftime("%Y%m%dT%H%M%S")}")
expect(schedule.to_ical(true)).to eq("DTSTART:#{time.utc.strftime("%Y%m%dT%H%M%S")}Z")
schedule = IceCube::Schedule.new(time)
expect(schedule.to_ical).to eq("DTSTART:#{time.utc.strftime("%Y%m%dT%H%M%S")}Z") # converts local to UTC
expect(schedule.to_ical(false)).to eq("DTSTART:#{time.utc.strftime("%Y%m%dT%H%M%S")}Z") # still converts local to UTC
expect(schedule.to_ical(true)).to eq("DTSTART:#{time.utc.strftime("%Y%m%dT%H%M%S")}Z") # force UTC
end

it "should be able to serialize to ical with an until date" do
Expand Down