Skip to content

Commit a33ee6c

Browse files
committed
Applying Full Timezone Support ice-cube-ruby#335
1 parent b003063 commit a33ee6c

File tree

5 files changed

+88
-20
lines changed

5 files changed

+88
-20
lines changed

ice_cube.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ Gem::Specification.new do |s|
2121
s.add_development_dependency('rspec', '~> 2.12.0')
2222
s.add_development_dependency('activesupport', '>= 3.0.0')
2323
s.add_development_dependency('tzinfo')
24+
s.add_development_dependency('timecop')
2425
end

lib/ice_cube/builders/ical_builder.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ def to_s
3232

3333
def self.ical_utc_format(time)
3434
time = time.dup.utc
35-
"#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time
35+
IceCube::I18n.l(time, format: '%Y%m%dT%H%M%SZ') # utc time
3636
end
3737

3838
def self.ical_format(time, force_utc)
39-
time = time.dup.utc if force_utc
39+
time = time.dup.utc if force_utc || !time.respond_to?('time_zone')
4040
if time.utc?
41-
":#{time.strftime('%Y%m%dT%H%M%SZ')}" # utc time
41+
":#{IceCube::I18n.l(time.utc, format: '%Y%m%dT%H%M%SZ')}" # utc time
4242
else
43-
";TZID=#{time.strftime('%Z:%Y%m%dT%H%M%S')}" # local time specified
43+
";TZID=#{time.time_zone.name}:#{IceCube::I18n.l(time, format: '%Y%m%dT%H%M%S')}" # local time specified
4444
end
4545
end
4646

lib/ice_cube/parsers/ical_parser.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,32 @@ def self.schedule_from_ical(ical_string, options = {})
77
(property, tzid) = property.split(';')
88
case property
99
when 'DTSTART'
10-
data[:start_time] = Time.parse(value)
10+
data[:start_time] = _parse_in_tzid(value, tzid)
1111
when 'DTEND'
12-
data[:end_time] = Time.parse(value)
12+
data[:end_time] = _parse_in_tzid(value, tzid)
1313
when 'EXDATE'
1414
data[:extimes] ||= []
15-
data[:extimes] += value.split(',').map{|v| Time.parse(v)}
15+
data[:extimes] += value.split(',').map do |v|
16+
_parse_in_tzid(v, tzid)
17+
end
1618
when 'DURATION'
1719
data[:duration] # FIXME
1820
when 'RRULE'
19-
data[:rrules] = [rule_from_ical(value)]
21+
data[:rrules] ||= []
22+
data[:rrules] += [rule_from_ical(value)]
2023
end
2124
end
2225
Schedule.from_hash data
2326
end
2427

28+
def self._parse_in_tzid(value, tzid)
29+
time_parser = Time
30+
if tzid
31+
time_parser = ActiveSupport::TimeZone.new(tzid.split('=')[1]) || Time
32+
end
33+
time_parser.parse(value)
34+
end
35+
2536
def self.rule_from_ical(ical)
2637
params = { validations: { } }
2738

spec/examples/from_ical_spec.rb

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,22 @@ module IceCube
9494

9595
end
9696

97-
describe Schedule, 'from_ical' do
97+
describe Schedule, 'from_ical', system_time_zone: "America/Chicago" do
9898

9999
ical_string = <<-ICAL.gsub(/^\s*/, '')
100100
DTSTART:20130314T201500Z
101101
DTEND:20130314T201545Z
102102
RRULE:FREQ=WEEKLY;BYDAY=TH;UNTIL=20130531T100000Z
103103
ICAL
104104

105-
ical_string_woth_multiple_exdates = <<-ICAL.gsub(/^\s*/, '')
105+
ical_string_with_time_zones = <<-ICAL.gsub(/^\s*/,'')
106+
DTSTART;TZID=America/Denver:20130731T143000
107+
DTEND:20130731T153000
108+
RRULE:FREQ=WEEKLY
109+
EXDATE;TZID=America/Chicago:20130823T143000
110+
ICAL
111+
112+
ical_string_with_multiple_exdates = <<-ICAL.gsub(/^\s*/, '')
106113
DTSTART;TZID=America/Denver:20130731T143000
107114
DTEND;TZID=America/Denver:20130731T153000
108115
RRULE:FREQ=WEEKLY;UNTIL=20140730T203000Z;BYDAY=MO,WE,FR
@@ -111,6 +118,11 @@ module IceCube
111118
EXDATE;TZID=America/Denver:20130807T143000
112119
ICAL
113120

121+
ical_string_with_multiple_rules = <<-ICAL.gsub(/^\s*/, '' )
122+
DTSTART;TZID=America/Denver:20151005T195541
123+
RRULE:FREQ=WEEKLY;BYDAY=MO,TU
124+
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYDAY=FR
125+
ICAL
114126

115127
def sorted_ical(ical)
116128
ical.split(/\n/).sort.map { |field|
@@ -125,6 +137,43 @@ def sorted_ical(ical)
125137
it "loads an ICAL string" do
126138
expect(IceCube::Schedule.from_ical(ical_string)).to be_a(IceCube::Schedule)
127139
end
140+
141+
describe "parsing time zones" do
142+
it "sets the time zone of the start time" do
143+
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
144+
expect(schedule.start_time.time_zone).to eq ActiveSupport::TimeZone.new("America/Denver")
145+
expect(schedule.start_time.is_a?(Time)).to be true
146+
expect(schedule.start_time.is_a?(ActiveSupport::TimeWithZone)).to be true
147+
end
148+
149+
it "treats UTC as a Time rather than TimeWithZone" do
150+
schedule = IceCube::Schedule.from_ical(ical_string)
151+
expect(schedule.start_time.utc_offset).to eq 0
152+
expect(schedule.start_time.is_a?(Time)).to be true
153+
expect(schedule.start_time.is_a?(ActiveSupport::TimeWithZone)).to be false
154+
end
155+
156+
it "uses the system time if a time zone is not explicity provided" do
157+
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
158+
expect(schedule.end_time).not_to respond_to :time_zone
159+
end
160+
161+
it "sets the time zone of the exception times" do
162+
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
163+
expect(schedule.exception_times[0].time_zone).to eq ActiveSupport::TimeZone.new("America/Chicago")
164+
end
165+
166+
it "adding the offset doesnt also change the time" do
167+
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
168+
expect(schedule.exception_times[0].hour).to eq 14
169+
end
170+
171+
it "loads the ical DTSTART as output by IceCube to_ical method" do
172+
now = Time.new(2016,5,9,12).in_time_zone("America/Los_Angeles")
173+
schedule = IceCube::Schedule.from_ical(IceCube::Schedule.new(now).to_ical)
174+
expect(schedule.start_time).to eq(now)
175+
end
176+
end
128177
end
129178

130179
describe "daily frequency" do
@@ -235,7 +284,6 @@ def sorted_ical(ical)
235284
describe 'monthly frequency' do
236285
it 'matches simple monthly' do
237286
start_time = Time.now
238-
239287
schedule = IceCube::Schedule.new(start_time)
240288
schedule.add_recurrence_rule(IceCube::Rule.monthly)
241289

@@ -359,10 +407,17 @@ def sorted_ical(ical)
359407
end
360408

361409
it 'handles multiple EXDATE lines' do
362-
schedule = IceCube::Schedule.from_ical ical_string_woth_multiple_exdates
410+
schedule = IceCube::Schedule.from_ical ical_string_with_multiple_exdates
363411
schedule.exception_times.count.should == 3
364412
end
365413
end
414+
415+
describe 'multiple rules' do
416+
it 'handles multiple recurrence rules' do
417+
schedule = IceCube::Schedule.from_ical ical_string_with_multiple_rules
418+
schedule.recurrence_rules.count.should == 2
419+
end
420+
end
366421
end
367422

368423
end

spec/examples/hourly_rule_spec.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
require File.dirname(__FILE__) + '/../spec_helper'
1+
re File.dirname(__FILE__) + '/../spec_helper'
22

33
module IceCube
44
describe HourlyRule do
@@ -39,13 +39,14 @@ module IceCube
3939
end
4040

4141
it 'should not skip times in DST end hour' do
42-
schedule = Schedule.new(t0 = Time.local(2013, 11, 3, 0, 0, 0))
42+
tz = ActiveSupport::TimeZone["America/Vancouver"]
43+
schedule = Schedule.new(t0 = tz.local(2013, 11, 3, 0, 0, 0))
4344
schedule.add_recurrence_rule Rule.hourly
44-
schedule.first(4).should == [
45-
Time.local(2013, 11, 3, 0, 0, 0), # -0700
46-
Time.local(2013, 11, 3, 1, 0, 0) - ONE_HOUR, # -0700
47-
Time.local(2013, 11, 3, 1, 0, 0), # -0800
48-
Time.local(2013, 11, 3, 2, 0, 0), # -0800
45+
expect(schedule.first(4)).to eq [
46+
tz.local(2013, 11, 3, 0, 0, 0), # -0700
47+
tz.local(2013, 11, 3, 1, 0, 0), # -0700
48+
tz.local(2013, 11, 3, 2, 0, 0) - ONE_HOUR, # -0800
49+
tz.local(2013, 11, 3, 2, 0, 0), # -0800
4950
]
5051
end
5152

@@ -55,7 +56,7 @@ module IceCube
5556
schedule = double(start_time: t0 = Time.now)
5657
rule = Rule.hourly(7)
5758
rule.interval(5)
58-
rule.next_time(t0 + 1, schedule, nil).should == t0 + 5.hours
59+
rule.next_time(t0 + 1, schedule, nil).should == t0 + 5 * ONE_HOUR
5960
end
6061

6162
it 'should produce the correct days for @interval = 3' do

0 commit comments

Comments
 (0)