From 8db090e9709b85601044b0efbabed403c4b2d8ba Mon Sep 17 00:00:00 2001 From: Sean Devine Date: Mon, 29 Jun 2015 10:59:20 -0400 Subject: [PATCH 01/11] Parse time zones when creating schedules from ICAL; fix DST test bug #295 --- lib/ice_cube/parsers/ical_parser.rb | 16 +++++++++++++--- spec/examples/from_ical_spec.rb | 28 ++++++++++++++++++++++++---- spec/examples/hourly_rule_spec.rb | 13 +++++++------ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 6729fca8..09a44e2d 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -7,12 +7,14 @@ def self.schedule_from_ical(ical_string, options = {}) (property, tzid) = property.split(';') case property when 'DTSTART' - data[:start_time] = Time.parse(value) + data[:start_time] = _parse_in_tzid(value, tzid) when 'DTEND' - data[:end_time] = Time.parse(value) + data[:end_time] = _parse_in_tzid(value, tzid) when 'EXDATE' data[:extimes] ||= [] - data[:extimes] += value.split(',').map{|v| Time.parse(v)} + data[:extimes] += value.split(',').map do |v| + _parse_in_tzid(v, tzid) + end when 'DURATION' data[:duration] # FIXME when 'RRULE' @@ -22,6 +24,14 @@ def self.schedule_from_ical(ical_string, options = {}) Schedule.from_hash data end + def self._parse_in_tzid(value, tzid) + t = Time.parse(value) + if tzid + t = t.in_time_zone(ActiveSupport::TimeZone[tzid.split("=")[1]]) + end + t + end + def self.rule_from_ical(ical) params = { validations: { } } diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 68d218ea..31064166 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -94,7 +94,7 @@ 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 @@ -102,7 +102,14 @@ module IceCube RRULE:FREQ=WEEKLY;BYDAY=TH;UNTIL=20130531T100000Z ICAL - ical_string_woth_multiple_exdates = <<-ICAL.gsub(/^\s*/, '') + 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 = <<-ICAL.gsub(/^\s*/, '') DTSTART;TZID=America/Denver:20130731T143000 DTEND;TZID=America/Denver:20130731T153000 RRULE:FREQ=WEEKLY;UNTIL=20140730T203000Z;BYDAY=MO,WE,FR @@ -125,6 +132,20 @@ 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") + 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 + end end describe "daily frequency" do @@ -235,7 +256,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) @@ -359,7 +379,7 @@ def sorted_ical(ical) end it 'handles multiple EXDATE lines' do - schedule = IceCube::Schedule.from_ical ical_string_woth_multiple_exdates + schedule = IceCube::Schedule.from_ical ical_string_with_multiple_exdates schedule.exception_times.count.should == 3 end end diff --git a/spec/examples/hourly_rule_spec.rb b/spec/examples/hourly_rule_spec.rb index d0685585..77cd10be 100644 --- a/spec/examples/hourly_rule_spec.rb +++ b/spec/examples/hourly_rule_spec.rb @@ -39,13 +39,14 @@ module IceCube end it 'should not skip times in DST end hour' do - schedule = Schedule.new(t0 = Time.local(2013, 11, 3, 0, 0, 0)) + tz = ActiveSupport::TimeZone["America/Vancouver"] + schedule = Schedule.new(t0 = tz.local(2013, 11, 3, 0, 0, 0)) schedule.add_recurrence_rule Rule.hourly - schedule.first(4).should == [ - 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 From f754169289194399d75d4189e841bb736dee81b0 Mon Sep 17 00:00:00 2001 From: John Hamelink Date: Sat, 3 Oct 2015 16:17:51 +0100 Subject: [PATCH 02/11] Add correct offset without changing time --- lib/ice_cube/parsers/ical_parser.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 09a44e2d..6f795131 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -25,11 +25,11 @@ def self.schedule_from_ical(ical_string, options = {}) end def self._parse_in_tzid(value, tzid) - t = Time.parse(value) + time_parser = Time if tzid - t = t.in_time_zone(ActiveSupport::TimeZone[tzid.split("=")[1]]) + time_parser = ActiveSupport::TimeZone.new(tzid.split('=')[1]) || Time end - t + time_parser.parse(value) end def self.rule_from_ical(ical) From f03e439b279650035941a596d661f864f1332673 Mon Sep 17 00:00:00 2001 From: John Hamelink Date: Sat, 3 Oct 2015 16:29:16 +0100 Subject: [PATCH 03/11] Add spec which proves hour doesn't change when timezone is set --- spec/examples/from_ical_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 31064166..f1fec335 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -145,6 +145,10 @@ def sorted_ical(ical) 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 end end From 237e7a96908100851b11b7e00c9afeae16bf7f47 Mon Sep 17 00:00:00 2001 From: Danny Cosson Date: Mon, 9 May 2016 19:03:25 -0700 Subject: [PATCH 04/11] Use real Timezones in ical serialization, instead of US timezone offsets like PDT, PST, etc. --- lib/ice_cube/builders/ical_builder.rb | 6 ++-- spec/examples/from_ical_spec.rb | 10 ++++-- spec/examples/to_ical_spec.rb | 49 ++++++++++++++++----------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/ice_cube/builders/ical_builder.rb b/lib/ice_cube/builders/ical_builder.rb index 2ba4e646..3067e1f3 100644 --- a/lib/ice_cube/builders/ical_builder.rb +++ b/lib/ice_cube/builders/ical_builder.rb @@ -36,11 +36,11 @@ def self.ical_utc_format(time) end def self.ical_format(time, force_utc) - time = time.dup.utc if force_utc + time = time.dup.utc if force_utc || !time.respond_to?('time_zone') if time.utc? - ":#{IceCube::I18n.l(time, format: '%Y%m%dT%H%M%SZ')}" # utc time + ":#{IceCube::I18n.l(time.utc, 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 + ";TZID=#{time.time_zone.name}:#{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 3694bdc2..9b6e8d55 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -118,8 +118,8 @@ module IceCube EXDATE;TZID=America/Denver:20130807T143000 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 @@ -158,6 +158,12 @@ def sorted_ical(ical) 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 diff --git a/spec/examples/to_ical_spec.rb b/spec/examples/to_ical_spec.rb index fdd42b45..72ec6ec0 100644 --- a/spec/examples/to_ical_spec.rb +++ b/spec/examples/to_ical_spec.rb @@ -94,10 +94,16 @@ ].include?(rule.to_ical).should be_true 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)) - schedule.to_ical.should == "DTSTART;TZID=EDT:20100510T090000" + schedule.to_ical.should == "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)) + schedule.to_ical.should == "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' schedule.to_ical.should == 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" schedule.to_ical.should == 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' schedule.to_ical.should == 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" schedule.to_ical.should == expectation @@ -192,12 +198,6 @@ schedule.duration.should == 3600 end - it 'should default to to_ical using local time' do - time = Time.now - schedule = IceCube::Schedule.new(Time.now) - schedule.to_ical.should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" # default false - end - it 'should not have an rtime that duplicates start time' do start = Time.utc(2012, 12, 12, 12, 0, 0) schedule = IceCube::Schedule.new(start) @@ -205,12 +205,23 @@ schedule.to_ical.should == "DTSTART:20121212T120000Z" end - it 'should be able to receive a to_ical in utc time' do - time = Time.now - schedule = IceCube::Schedule.new(Time.now) - schedule.to_ical.should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" # default false - schedule.to_ical(false).should == "DTSTART;TZID=#{time.zone}:#{time.strftime('%Y%m%dT%H%M%S')}" - schedule.to_ical(true).should == "DTSTART:#{time.utc.strftime('%Y%m%dT%H%M%S')}Z" + it 'displays an ActiveSupport::TimeWithZone at utc time as Z' do + time = Time.now.utc + schedule = IceCube::Schedule.new(time) + schedule.to_ical(false).should == "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) + schedule.to_ical(true).should == "DTSTART:20160509T120000Z" + end + + it 'displays a Time utc time as Z' do + time = Time.now.utc + schedule = IceCube::Schedule.new(time) + schedule.to_ical(true).should == "DTSTART:#{time.strftime('%Y%m%dT%H%M%S')}Z" end it 'should be able to serialize to ical with an until date' do From a8f222b0c1cbe8401cb8a706fcdc535353baa06f Mon Sep 17 00:00:00 2001 From: Danny Cosson Date: Mon, 9 May 2016 21:57:31 -0700 Subject: [PATCH 05/11] add a test for daylight savings --- spec/examples/recur_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/examples/recur_spec.rb b/spec/examples/recur_spec.rb index 6f31422c..ba377c6f 100644 --- a/spec/examples/recur_spec.rb +++ b/spec/examples/recur_spec.rb @@ -115,6 +115,15 @@ schedule.next_occurrence(schedule.start_time).should == 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, 12, 0, 0) + next_time = Time.zone.local(2016, 3, 14, 12, 0, 0) + schedule = Schedule.new(start_time, :end_time => start_time + 14 * 24 * ONE_HOUR) + schedule.add_recurrence_rule(Rule.daily) + schedule.next_occurrence(schedule.start_time).should == next_time + end end describe :next_occurrences do From 9db21046d0adbfa1833ce93eecd223ae43562be7 Mon Sep 17 00:00:00 2001 From: Danny Cosson Date: Tue, 10 May 2016 13:43:05 -0700 Subject: [PATCH 06/11] add test that the start_time object when parsing UTC is a simple Time not TimeWithZone, while when parsing a timezone it is a TimeWithZone --- spec/examples/from_ical_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 9b6e8d55..65e6ec6d 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -142,6 +142,15 @@ def sorted_ical(ical) 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 From f5b00253428fa3cb43803cf9aa9411e3aa920a07 Mon Sep 17 00:00:00 2001 From: Danny Cosson Date: Thu, 12 May 2016 13:46:29 -0700 Subject: [PATCH 07/11] update test of DST cutover --- ice_cube.gemspec | 1 + spec/examples/recur_spec.rb | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ice_cube.gemspec b/ice_cube.gemspec index 195a6de6..f1ca1ac4 100644 --- a/ice_cube.gemspec +++ b/ice_cube.gemspec @@ -21,5 +21,6 @@ Gem::Specification.new do |s| s.add_development_dependency('rspec', '~> 2.12.0') s.add_development_dependency('activesupport', '>= 3.0.0') s.add_development_dependency('tzinfo') + s.add_development_dependency('timecop') s.add_development_dependency('i18n') end diff --git a/spec/examples/recur_spec.rb b/spec/examples/recur_spec.rb index ba377c6f..ac15e5f2 100644 --- a/spec/examples/recur_spec.rb +++ b/spec/examples/recur_spec.rb @@ -1,4 +1,5 @@ require File.dirname(__FILE__) + '/../spec_helper' +require 'timecop' include IceCube @@ -117,12 +118,15 @@ 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, 12, 0, 0) - next_time = Time.zone.local(2016, 3, 14, 12, 0, 0) - schedule = Schedule.new(start_time, :end_time => start_time + 14 * 24 * ONE_HOUR) - schedule.add_recurrence_rule(Rule.daily) - schedule.next_occurrence(schedule.start_time).should == next_time + # 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)) + + Timecop.freeze(start_time) do + schedule.next_occurrence(schedule.start_time).should == expected_next_time + end end end From 496fa590601bfc133062871e6ada557b9e82dc6b Mon Sep 17 00:00:00 2001 From: John Crepezzi Date: Mon, 7 Aug 2017 11:57:34 +0200 Subject: [PATCH 08/11] Fix tests --- ice_cube.gemspec | 1 - lib/ice_cube/builders/ical_builder.rb | 5 +++-- lib/ice_cube/parsers/ical_parser.rb | 2 +- spec/examples/from_ical_spec.rb | 1 + spec/examples/recur_spec.rb | 5 +---- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ice_cube.gemspec b/ice_cube.gemspec index 436d8e31..a713fb44 100644 --- a/ice_cube.gemspec +++ b/ice_cube.gemspec @@ -22,5 +22,4 @@ Gem::Specification.new do |s| s.add_development_dependency('rake') s.add_development_dependency('rspec', '> 3') - s.add_development_dependency('timecop') end diff --git a/lib/ice_cube/builders/ical_builder.rb b/lib/ice_cube/builders/ical_builder.rb index 1ee75084..21722824 100644 --- a/lib/ice_cube/builders/ical_builder.rb +++ b/lib/ice_cube/builders/ical_builder.rb @@ -38,9 +38,10 @@ def self.ical_utc_format(time) def self.ical_format(time, force_utc) time = time.dup.utc if force_utc if time.utc? - ":#{IceCube::I18n.l(time.utc, format: '%Y%m%dT%H%M%SZ')}" # utc time + ":#{IceCube::I18n.l(time, format: '%Y%m%dT%H%M%SZ')}" # utc time else - ";TZID=#{time.time_zone.name}:#{IceCube::I18n.l(time, format: '%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/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 3929f4e1..c6584350 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -12,7 +12,7 @@ def schedule_from_ical(ical_string, options = {}) when 'DTEND' data[:end_time] = parse_in_tzid(value, tzid) when 'RDATE' - date[:rtimes] ||= [] + data[:rtimes] ||= [] data[:rtimes] += value.split(',').map do |v| parse_in_tzid(v, tzid) end diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index d3cc5921..47130a22 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -405,6 +405,7 @@ def sorted_ical(ical) schedule.add_exception_time(Time.now + (IceCube::ONE_DAY * 2)) schedule.add_recurrence_time(Time.now + IceCube::ONE_DAY * 4) + ical = schedule.to_ical expect(sorted_ical(IceCube::Schedule.from_ical(ical).to_ical)).to eq(sorted_ical(ical)) end diff --git a/spec/examples/recur_spec.rb b/spec/examples/recur_spec.rb index 506cb1bc..90607f2b 100644 --- a/spec/examples/recur_spec.rb +++ b/spec/examples/recur_spec.rb @@ -1,5 +1,4 @@ require File.dirname(__FILE__) + '/../spec_helper' -require 'timecop' include IceCube @@ -124,9 +123,7 @@ schedule = Schedule.new(start_time) schedule.add_recurrence_rule(Rule.hourly(interval=4)) - Timecop.freeze(start_time) do - expect(schedule.next_occurrence(schedule.start_time)).to eq expected_next_time - end + expect(schedule.next_occurrence(schedule.start_time)).to eq expected_next_time end end From 1072173f1851d8e57d48aed04c74cf10fab6aca4 Mon Sep 17 00:00:00 2001 From: John Crepezzi Date: Mon, 7 Aug 2017 12:05:48 +0200 Subject: [PATCH 09/11] AS conditional --- lib/ice_cube/parsers/ical_parser.rb | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index c6584350..691d9a18 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -8,18 +8,18 @@ def schedule_from_ical(ical_string, options = {}) (property, tzid) = property.split(';') case property when 'DTSTART' - data[:start_time] = parse_in_tzid(value, tzid) + data[:start_time] = time_parser_for_zone(tzid).parse(value) when 'DTEND' - data[:end_time] = parse_in_tzid(value, tzid) + data[:end_time] = time_parser_for_zone(tzid).parse(value) when 'RDATE' data[:rtimes] ||= [] data[:rtimes] += value.split(',').map do |v| - parse_in_tzid(v, tzid) + time_parser_for_zone(tzid).parse(v) end when 'EXDATE' data[:extimes] ||= [] data[:extimes] += value.split(',').map do |v| - parse_in_tzid(v, tzid) + time_parser_for_zone(tzid).parse(v) end when 'DURATION' data[:duration] # FIXME @@ -90,12 +90,22 @@ def rule_from_ical(ical) private - def parse_in_tzid(value, tzid) - time_parser = Time + def time_parser_for_zone(tzid) if tzid - time_parser = ActiveSupport::TimeZone.new(tzid.split('=')[1]) || Time + tzid = tzid.split('=')[1] + if active_support_loaded? + ActiveSupport::TimeZone.new(tzid) || Time + else + raise ArgumentError, "must load ActiveSupport to parse ical with time zones" + end + else + Time end - time_parser.parse(value) + end + + def active_support_loaded? + return true if @as_loaded + @as_loaded = defined? ActiveSupport::TimeWithZone end end end From 67f02664e19f9f387143d8f78061e17954b6e0a1 Mon Sep 17 00:00:00 2001 From: John Crepezzi Date: Thu, 28 Sep 2017 11:00:01 -0400 Subject: [PATCH 10/11] Clean up a bit --- lib/ice_cube/parsers/ical_parser.rb | 118 ++++++++++++---------------- spec/examples/hourly_rule_spec.rb | 4 +- 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 56f13b45..e46857d7 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -22,85 +22,65 @@ def self.schedule_from_ical(ical_string, options = {}) data[:rrules] ||= [] data[:rrules] += [rule_from_ical(value)] end - Schedule.from_hash data end + Schedule.from_hash data + end - def rule_from_ical(ical) - raise ArgumentError, 'empty ical rule' if ical.nil? + def self.rule_from_ical(ical) + raise ArgumentError, 'empty ical rule' if ical.nil? - validations = {} - params = {validations: validations, interval: 1} + validations = {} + params = {validations: validations, interval: 1} - ical.split(';').each do |rule| - (name, value) = rule.split('=') - raise ArgumentError, "Invalid iCal rule component" if value.nil? - value.strip! - case name - when 'FREQ' - params[:rule_type] = "IceCube::#{value[0]}#{value.downcase[1..-1]}Rule" - when 'INTERVAL' - params[:interval] = value.to_i - when 'COUNT' - params[:count] = value.to_i - when 'UNTIL' - params[:until] = Time.parse(value).utc - when 'WKST' - params[:week_start] = TimeUtil.ical_day_to_symbol(value) - when 'BYSECOND' - validations[:second_of_minute] = value.split(',').map(&:to_i) - when 'BYMINUTE' - validations[:minute_of_hour] = value.split(',').map(&:to_i) - when 'BYHOUR' - validations[:hour_of_day] = value.split(',').map(&:to_i) - when 'BYDAY' - dows = {} - days = [] - value.split(',').each do |expr| - day = TimeUtil.ical_day_to_symbol(expr.strip[-2..-1]) - if expr.strip.length > 2 # day with occurence - occ = expr[0..-3].to_i - dows[day].nil? ? dows[day] = [occ] : dows[day].push(occ) - days.delete(TimeUtil.sym_to_wday(day)) - else - days.push TimeUtil.sym_to_wday(day) if dows[day].nil? - end + ical.split(';').each do |rule| + (name, value) = rule.split('=') + raise ArgumentError, "Invalid iCal rule component" if value.nil? + value.strip! + case name + when 'FREQ' + params[:rule_type] = "IceCube::#{value[0]}#{value.downcase[1..-1]}Rule" + when 'INTERVAL' + params[:interval] = value.to_i + when 'COUNT' + params[:count] = value.to_i + when 'UNTIL' + params[:until] = Time.parse(value).utc + when 'WKST' + params[:week_start] = TimeUtil.ical_day_to_symbol(value) + when 'BYSECOND' + validations[:second_of_minute] = value.split(',').map(&:to_i) + when 'BYMINUTE' + validations[:minute_of_hour] = value.split(',').map(&:to_i) + when 'BYHOUR' + validations[:hour_of_day] = value.split(',').map(&:to_i) + when 'BYDAY' + dows = {} + days = [] + value.split(',').each do |expr| + day = TimeUtil.ical_day_to_symbol(expr.strip[-2..-1]) + if expr.strip.length > 2 # day with occurence + occ = expr[0..-3].to_i + dows[day].nil? ? dows[day] = [occ] : dows[day].push(occ) + days.delete(TimeUtil.sym_to_wday(day)) + else + days.push TimeUtil.sym_to_wday(day) if dows[day].nil? end - validations[:day_of_week] = dows unless dows.empty? - validations[:day] = days unless days.empty? - when 'BYMONTHDAY' - validations[:day_of_month] = value.split(',').map(&:to_i) - when 'BYMONTH' - validations[:month_of_year] = value.split(',').map(&:to_i) - when 'BYYEARDAY' - validations[:day_of_year] = value.split(',').map(&:to_i) - when 'BYSETPOS' - else - validations[name] = nil # invalid type - end - end - - Rule.from_hash(params) - end - - private - - def time_parser_for_zone(tzid) - if tzid - tzid = tzid.split('=')[1] - if active_support_loaded? - ActiveSupport::TimeZone.new(tzid) || Time - else - raise ArgumentError, "must load ActiveSupport to parse ical with time zones" end + validations[:day_of_week] = dows unless dows.empty? + validations[:day] = days unless days.empty? + when 'BYMONTHDAY' + validations[:day_of_month] = value.split(',').map(&:to_i) + when 'BYMONTH' + validations[:month_of_year] = value.split(',').map(&:to_i) + when 'BYYEARDAY' + validations[:day_of_year] = value.split(',').map(&:to_i) + when 'BYSETPOS' else - Time + validations[name] = nil # invalid type end end - def active_support_loaded? - return true if @as_loaded - @as_loaded = defined? ActiveSupport::TimeWithZone - end + Rule.from_hash(params) end end end diff --git a/spec/examples/hourly_rule_spec.rb b/spec/examples/hourly_rule_spec.rb index a90b9dc9..fdfc67cc 100644 --- a/spec/examples/hourly_rule_spec.rb +++ b/spec/examples/hourly_rule_spec.rb @@ -39,7 +39,9 @@ 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 [ tz.local(2013, 11, 3, 0, 0, 0), # -0700 From 10ed1e4122042737364420974d69448a8c1a19a7 Mon Sep 17 00:00:00 2001 From: John Crepezzi Date: Thu, 28 Sep 2017 11:01:13 -0400 Subject: [PATCH 11/11] Use deserialize_time --- lib/ice_cube/parsers/ical_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index e46857d7..c6b91a1a 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -44,7 +44,7 @@ def self.rule_from_ical(ical) when 'COUNT' params[:count] = value.to_i when 'UNTIL' - params[:until] = Time.parse(value).utc + params[:until] = TimeUtil.deserialize_time(value).utc when 'WKST' params[:week_start] = TimeUtil.ical_day_to_symbol(value) when 'BYSECOND'