Skip to content

Commit 3c96a6f

Browse files
committed
feat: add timezone parsing support with tests from @epologee
1 parent ab36aba commit 3c96a6f

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
- Indonesian translations. ([#505](https://github.com/seejohnrun/ice_cube/pull/505)) by [@achmiral](https://github.com/achmiral)
10+
- The `IceCube::IcalParser` finds the time zones matching an iCal schedule's date/time strings, accomodating for times with daylight savings ([#555](https://github.com/ice-cube-ruby/ice_cube/pull/555)) by [@jankeesvw](https://github.com/jankeesvw) and [@epologee](https://github.com/epologee)
1011

1112
### Changed
1213
- Removed use of `delegate` method added in [66f1d797](https://github.com/ice-cube-ruby/ice_cube/commit/66f1d797092734563bfabd2132c024c7d087f683) , reverting to previous implementation. ([#522](https://github.com/ice-cube-ruby/ice_cube/pull/522)) by [@pacso](https://github.com/pacso)

lib/ice_cube/parsers/ical_parser.rb

+27-3
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,27 @@ def self.schedule_from_ical(ical_string, options = {})
44
data = {}
55
ical_string.each_line do |line|
66
(property, value) = line.split(":")
7-
(property, _tzid) = property.split(";")
7+
(property, tzid) = property.split(";")
8+
zone = find_zone(tzid, value) if tzid.present?
89
case property
910
when "DTSTART"
11+
value = { time: value, zone: zone } if zone.present?
1012
data[:start_time] = TimeUtil.deserialize_time(value)
1113
when "DTEND"
14+
value = { time: value, zone: zone } if zone.present?
1215
data[:end_time] = TimeUtil.deserialize_time(value)
1316
when "RDATE"
1417
data[:rtimes] ||= []
15-
data[:rtimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
18+
data[:rtimes] += value.split(",").map do |v|
19+
v = {time: v, zone: zone} if zone.present?
20+
TimeUtil.deserialize_time(v)
21+
end
1622
when "EXDATE"
1723
data[:extimes] ||= []
18-
data[:extimes] += value.split(",").map { |v| TimeUtil.deserialize_time(v) }
24+
data[:extimes] += value.split(",").map do |v|
25+
v = {time: v, zone: zone} if zone.present?
26+
TimeUtil.deserialize_time(v)
27+
end
1928
when "DURATION"
2029
data[:duration] # FIXME
2130
when "RRULE"
@@ -83,5 +92,20 @@ def self.rule_from_ical(ical)
8392

8493
Rule.from_hash(params)
8594
end
95+
96+
private_class_method def self.find_zone(tzid, time_string)
97+
(_, zone) = tzid&.split("=")
98+
begin
99+
Time.find_zone!(zone) if zone.present?
100+
rescue ArgumentError
101+
(rails_zone, _tzinfo_id) = ActiveSupport::TimeZone::MAPPING.find do |(k, _)|
102+
time = Time.parse(time_string)
103+
104+
Time.find_zone!(k).local(time.year, time.month, time.day, time.hour, time.min).strftime("%Z") == zone
105+
end
106+
107+
Time.find_zone(rails_zone)
108+
end
109+
end
86110
end
87111
end

spec/examples/from_ical_spec.rb

+63
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,69 @@ def sorted_ical(ical)
348348
end
349349
end
350350

351+
describe "time zone support" do
352+
it "parses start time with the correct time zone" do
353+
schedule = IceCube::Schedule.from_ical ical_string_with_multiple_rules
354+
355+
expect(schedule.start_time).to eq Time.find_zone!("America/Chicago").local(2015, 10, 5, 19, 55, 41)
356+
end
357+
358+
it "parses time zones correctly" do
359+
schedule = IceCube::Schedule.from_ical ical_string_with_multiple_exdates_and_rdates
360+
361+
utc_times = [
362+
schedule.recurrence_rules.map(&:until_time)
363+
].flatten
364+
365+
denver_times = [
366+
schedule.start_time,
367+
schedule.end_time,
368+
schedule.exception_times,
369+
schedule.rtimes
370+
].flatten
371+
372+
utc_times.each do |t|
373+
expect(t.zone).to eq "UTC"
374+
end
375+
376+
denver_times.each do |t|
377+
expect(t.zone).to eq "MDT"
378+
end
379+
end
380+
381+
it "round trips from and to ical with time zones in the summer (MDT)" do
382+
original = <<-ICAL.gsub(/^\s*/, "").strip
383+
DTSTART;TZID=MDT:20130731T143000
384+
RRULE:FREQ=WEEKLY;UNTIL=20140730T203000Z;BYDAY=MO,WE,FR
385+
RDATE;TZID=MDT:20150812T143000
386+
RDATE;TZID=MDT:20150807T143000
387+
EXDATE;TZID=MDT:20130823T143000
388+
EXDATE;TZID=MDT:20130812T143000
389+
EXDATE;TZID=MDT:20130807T143000
390+
DTEND;TZID=MDT:20130731T153000
391+
ICAL
392+
393+
schedule_from_ical = IceCube::Schedule.from_ical original
394+
expect(schedule_from_ical.to_ical).to eq original
395+
end
396+
397+
t "round trips from and to ical with time zones in the winter (MST)" do
398+
original = <<-ICAL.gsub(/^\s*/, "").strip
399+
DTSTART;TZID=MST:20130131T143000
400+
RRULE:FREQ=WEEKLY;UNTIL=20140130T203000Z;BYDAY=MO,WE,FR
401+
RDATE;TZID=MST:20150212T143000
402+
RDATE;TZID=MST:20150207T143000
403+
EXDATE;TZID=MST:20130223T143000
404+
EXDATE;TZID=MST:20130212T143000
405+
EXDATE;TZID=MST:20130207T143000
406+
DTEND;TZID=MST:20130131T153000
407+
ICAL
408+
409+
schedule_from_ical = IceCube::Schedule.from_ical original
410+
expect(schedule_from_ical.to_ical).to eq original
411+
end
412+
end
413+
351414
describe "exceptions" do
352415
it "handles single EXDATE lines, single RDATE lines" do
353416
start_time = Time.now

0 commit comments

Comments
 (0)