diff --git a/lib/ice_cube/builders/ical_builder.rb b/lib/ice_cube/builders/ical_builder.rb index 2ba4e646..21722824 100644 --- a/lib/ice_cube/builders/ical_builder.rb +++ b/lib/ice_cube/builders/ical_builder.rb @@ -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 diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 12cf8bf8..bfee5aac 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -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*/, '') @@ -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 @@ -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 @@ -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) diff --git a/spec/examples/hourly_rule_spec.rb b/spec/examples/hourly_rule_spec.rb index c5f1d1cb..fdfc67cc 100644 --- a/spec/examples/hourly_rule_spec.rb +++ b/spec/examples/hourly_rule_spec.rb @@ -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 diff --git a/spec/examples/recur_spec.rb b/spec/examples/recur_spec.rb index 66351932..c3d6974f 100644 --- a/spec/examples/recur_spec.rb +++ b/spec/examples/recur_spec.rb @@ -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 diff --git a/spec/examples/to_ical_spec.rb b/spec/examples/to_ical_spec.rb index 10521739..efc07c8c 100644 --- a/spec/examples/to_ical_spec.rb +++ b/spec/examples/to_ical_spec.rb @@ -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 @@ -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 @@ -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) @@ -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) @@ -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"