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

Full Timezone support when parsing ical #408

Open
wants to merge 16 commits 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
3 changes: 2 additions & 1 deletion lib/ice_cube/builders/ical_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def self.ical_format(time, force_utc)
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
time_zone = time.respond_to?(:time_zone) ? time.time_zone.name : time.zone
";TZID=#{time_zone}:#{IceCube::I18n.l(time, format: '%Y%m%dT%H%M%S')}" # local time specified
end
end

Expand Down
51 changes: 47 additions & 4 deletions spec/examples/from_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,19 @@ module IceCube

end

describe Schedule, 'from_ical' do
describe Schedule, 'from_ical', system_time_zone: "America/Chicago" do

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

ical_string_with_time_zones = <<-ICAL.gsub(/^\s*/,'')
DTSTART;TZID=America/Denver:20130731T143000
DTEND:20130731T153000
RRULE:FREQ=WEEKLY
EXDATE;TZID=America/Chicago:20130823T143000
ICAL

ical_string_with_multiple_exdates_and_rdates = <<-ICAL.gsub(/^\s*/, '')
Expand All @@ -113,8 +120,8 @@ module IceCube
RDATE;TZID=America/Denver:20150807T143000
ICAL

ical_string_with_multiple_rules = <<-ICAL.gsub(/^\s*/, '' )
DTSTART;TZID=CDT:20151005T195541
ical_string_with_multiple_rules = <<-ICAL.gsub(/^\s*/, '' )
DTSTART;TZID=America/Denver:20151005T195541
RRULE:FREQ=WEEKLY;BYDAY=MO,TU
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU;BYDAY=FR
ICAL
Expand All @@ -132,6 +139,43 @@ def sorted_ical(ical)
it "loads an ICAL string" do
expect(IceCube::Schedule.from_ical(ical_string)).to be_a(IceCube::Schedule)
end

describe "parsing time zones" do
it "sets the time zone of the start time" do
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
expect(schedule.start_time.time_zone).to eq ActiveSupport::TimeZone.new("America/Denver")
expect(schedule.start_time.is_a?(Time)).to be true
expect(schedule.start_time.is_a?(ActiveSupport::TimeWithZone)).to be true
end

it "treats UTC as a Time rather than TimeWithZone" do
schedule = IceCube::Schedule.from_ical(ical_string)
expect(schedule.start_time.utc_offset).to eq 0
expect(schedule.start_time.is_a?(Time)).to be true
expect(schedule.start_time.is_a?(ActiveSupport::TimeWithZone)).to be false
end

it "uses the system time if a time zone is not explicity provided" do
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
expect(schedule.end_time).not_to respond_to :time_zone
end

it "sets the time zone of the exception times" do
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
expect(schedule.exception_times[0].time_zone).to eq ActiveSupport::TimeZone.new("America/Chicago")
end

it "adding the offset doesnt also change the time" do
schedule = IceCube::Schedule.from_ical(ical_string_with_time_zones)
expect(schedule.exception_times[0].hour).to eq 14
end

it "loads the ical DTSTART as output by IceCube to_ical method" do
now = Time.new(2016,5,9,12).in_time_zone("America/Los_Angeles")
schedule = IceCube::Schedule.from_ical(IceCube::Schedule.new(now).to_ical)
expect(schedule.start_time).to eq(now)
end
end
end

describe "daily frequency" do
Expand Down Expand Up @@ -242,7 +286,6 @@ def sorted_ical(ical)
describe 'monthly frequency' do
it 'matches simple monthly' do
start_time = Time.now

schedule = IceCube::Schedule.new(start_time)
schedule.add_recurrence_rule(IceCube::Rule.monthly)

Expand Down
16 changes: 9 additions & 7 deletions spec/examples/hourly_rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,16 @@ module IceCube
end

it 'should not skip times in DST end hour' do
schedule = Schedule.new(Time.local(2013, 11, 3, 0, 0, 0))
tz = ActiveSupport::TimeZone["America/Vancouver"]

schedule = Schedule.new(tz.local(2013, 11, 3, 0, 0, 0))
schedule.add_recurrence_rule Rule.hourly
expect(schedule.first(4)).to eq([
Time.local(2013, 11, 3, 0, 0, 0), # -0700
Time.local(2013, 11, 3, 1, 0, 0) - ONE_HOUR, # -0700
Time.local(2013, 11, 3, 1, 0, 0), # -0800
Time.local(2013, 11, 3, 2, 0, 0), # -0800
])
expect(schedule.first(4)).to eq [
tz.local(2013, 11, 3, 0, 0, 0), # -0700
tz.local(2013, 11, 3, 1, 0, 0), # -0700
tz.local(2013, 11, 3, 2, 0, 0) - ONE_HOUR, # -0800
tz.local(2013, 11, 3, 2, 0, 0), # -0800
]
end

end
Expand Down
10 changes: 10 additions & 0 deletions spec/examples/recur_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@
expect(schedule.next_occurrence(schedule.start_time)).to eq(schedule.start_time + 30 * ONE_MINUTE)
end

it 'should get the next occurrence across the daylight savings time boundary' do
# 2016 daylight savings time cutoff is Sunday March 13
# Time.zone = 'America/New_York'
start_time = Time.zone.local(2016, 3, 13, 0, 0, 0)
expected_next_time = Time.zone.local(2016, 3, 13, 5, 0, 0)
schedule = Schedule.new(start_time)
schedule.add_recurrence_rule(Rule.hourly(interval=4))

expect(schedule.next_occurrence(schedule.start_time)).to eq expected_next_time
end
end

describe :next_occurrences do
Expand Down
39 changes: 32 additions & 7 deletions spec/examples/to_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,16 @@
].include?(rule.to_ical)).to be_truthy
end

it 'should be able to serialize a base schedule to ical in local time' do
it 'should be able to serialize a base schedule to ical in local time, using a US timezone' 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 local time, using an Olson timezone' do
Time.zone = "America/New_York"
schedule = IceCube::Schedule.new(Time.zone.local(2010, 5, 10, 9, 0, 0))
expect(schedule.to_ical).to eq "DTSTART;TZID=America/New_York:20100510T090000"
end

it 'should be able to serialize a base schedule to ical in UTC time' do
Expand All @@ -110,7 +116,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 +126,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,17 +137,17 @@
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

it 'should be able to serialize a schedule with multiple exrules' do
Time.zone ='Eastern Time (US & Canada)'
Time.zone ='America/New_York'
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=America/New_York: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 @@ -213,6 +219,25 @@
expect(schedule.to_ical(true)).to eq("DTSTART:#{time.utc.strftime('%Y%m%dT%H%M%S')}Z")
end

it 'displays an ActiveSupport::TimeWithZone at utc time as Z' do
time = Time.now.utc
schedule = IceCube::Schedule.new(time)
expect(schedule.to_ical(false)).to eq "DTSTART:#{time.strftime('%Y%m%dT%H%M%S')}Z"
end

it 'displays an ActiveSupport::TimeWithZone to utc when using force_utc' do
# this is 8am in NY, 12pm UTC (UTC -4 in summer)
time = Time.new(2016, 5, 9, 12, 0, 0, 0).in_time_zone('America/New_York')
schedule = IceCube::Schedule.new(time)
expect(schedule.to_ical(true)).to eq "DTSTART:20160509T120000Z"
end

it 'displays a Time utc time as Z' do
time = Time.now.utc
schedule = IceCube::Schedule.new(time)
expect(schedule.to_ical(true)).to eq "DTSTART:#{time.strftime('%Y%m%dT%H%M%S')}Z"
end

it 'should be able to serialize to ical with an until date' do
rule = IceCube::Rule.weekly.until Time.utc(2123, 12, 31, 12, 34, 56.25)
expect(rule.to_ical).to match "FREQ=WEEKLY;UNTIL=21231231T123456Z"
Expand Down