From 3e221e4ec737792f2db741caa1e11a0146fd5ffa Mon Sep 17 00:00:00 2001 From: Nicolas Marlier Date: Tue, 23 Aug 2016 12:47:23 +0200 Subject: [PATCH 01/11] Support BYSETPOS for MONTHLY AND YEARLY freq --- .gitignore | 3 + lib/ice_cube.rb | 3 + lib/ice_cube/parsers/ical_parser.rb | 1 + lib/ice_cube/rules/monthly_rule.rb | 1 + lib/ice_cube/rules/yearly_rule.rb | 1 + lib/ice_cube/time_util.rb | 32 +++++++ lib/ice_cube/validated_rule.rb | 3 +- .../validations/monthly_by_set_pos.rb | 87 ++++++++++++++++++ lib/ice_cube/validations/yearly_by_set_pos.rb | 89 +++++++++++++++++++ spec/examples/by_set_pos_spec.rb | 29 ++++++ spec/examples/from_ical_spec.rb | 5 ++ 11 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 lib/ice_cube/validations/monthly_by_set_pos.rb create mode 100644 lib/ice_cube/validations/yearly_by_set_pos.rb create mode 100644 spec/examples/by_set_pos_spec.rb diff --git a/.gitignore b/.gitignore index 8eb3b06b..64d30ba1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,8 @@ /spec/reports/ /tmp/ +# rubymine +.idea + # rspec failure tracking .rspec_status diff --git a/lib/ice_cube.rb b/lib/ice_cube.rb index 4a38232e..c7c3339d 100644 --- a/lib/ice_cube.rb +++ b/lib/ice_cube.rb @@ -50,6 +50,9 @@ module Validations autoload :YearlyInterval, 'ice_cube/validations/yearly_interval' autoload :HourlyInterval, 'ice_cube/validations/hourly_interval' + autoload :MonthlyBySetPos, 'ice_cube/validations/monthly_by_set_pos' + autoload :YearlyBySetPos, 'ice_cube/validations/yearly_by_set_pos' + autoload :HourOfDay, 'ice_cube/validations/hour_of_day' autoload :MonthOfYear, 'ice_cube/validations/month_of_year' autoload :MinuteOfHour, 'ice_cube/validations/minute_of_hour' diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index c6b91a1a..92d9a78d 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -75,6 +75,7 @@ def self.rule_from_ical(ical) when 'BYYEARDAY' validations[:day_of_year] = value.split(',').map(&:to_i) when 'BYSETPOS' + params[:validations][:by_set_pos] = value.split(',').collect(&:to_i) else validations[name] = nil # invalid type end diff --git a/lib/ice_cube/rules/monthly_rule.rb b/lib/ice_cube/rules/monthly_rule.rb index 3e1307fb..d5518574 100644 --- a/lib/ice_cube/rules/monthly_rule.rb +++ b/lib/ice_cube/rules/monthly_rule.rb @@ -12,6 +12,7 @@ class MonthlyRule < ValidatedRule # include Validations::DayOfYear # n/a include Validations::MonthlyInterval + include Validations::MonthlyBySetPos def initialize(interval = 1) super diff --git a/lib/ice_cube/rules/yearly_rule.rb b/lib/ice_cube/rules/yearly_rule.rb index 3a18b0a6..832570c0 100644 --- a/lib/ice_cube/rules/yearly_rule.rb +++ b/lib/ice_cube/rules/yearly_rule.rb @@ -12,6 +12,7 @@ class YearlyRule < ValidatedRule include Validations::DayOfYear include Validations::YearlyInterval + include Validations::YearlyBySetPos def initialize(interval = 1) super diff --git a/lib/ice_cube/time_util.rb b/lib/ice_cube/time_util.rb index ff43399e..bb11efcb 100644 --- a/lib/ice_cube/time_util.rb +++ b/lib/ice_cube/time_util.rb @@ -1,5 +1,7 @@ require 'date' require 'time' +require 'active_support' +require 'active_support/core_ext' module IceCube module TimeUtil @@ -198,6 +200,36 @@ def self.which_occurrence_in_month(time, wday) [nth_occurrence_of_weekday, this_weekday_in_month_count] end + # Use Activesupport CoreExt functions to manipulate time + def self.start_of_month time + time.beginning_of_month + end + + # Use Activesupport CoreExt functions to manipulate time + def self.end_of_month time + time.end_of_month + end + + # Use Activesupport CoreExt functions to manipulate time + def self.start_of_year time + time.beginning_of_year + end + + # Use Activesupport CoreExt functions to manipulate time + def self.end_of_year time + time.end_of_year + end + + # Use Activesupport CoreExt functions to manipulate time + def self.previous_month time + time - 1.month + end + + # Use Activesupport CoreExt functions to manipulate time + def self.previous_year time + time - 1.year + end + # Get the days in the month for +time def self.days_in_month(time) date = Date.new(time.year, time.month, 1) diff --git a/lib/ice_cube/validated_rule.rb b/lib/ice_cube/validated_rule.rb index 69830fab..97055133 100644 --- a/lib/ice_cube/validated_rule.rb +++ b/lib/ice_cube/validated_rule.rb @@ -20,7 +20,8 @@ class ValidatedRule < Rule :base_sec, :base_min, :base_day, :base_hour, :base_month, :base_wday, :day_of_year, :second_of_minute, :minute_of_hour, :day_of_month, :hour_of_day, :month_of_year, :day_of_week, - :interval + :interval, + :by_set_pos ] attr_reader :validations diff --git a/lib/ice_cube/validations/monthly_by_set_pos.rb b/lib/ice_cube/validations/monthly_by_set_pos.rb new file mode 100644 index 00000000..dc87deaf --- /dev/null +++ b/lib/ice_cube/validations/monthly_by_set_pos.rb @@ -0,0 +1,87 @@ +module IceCube + + module Validations::MonthlyBySetPos + + def by_set_pos(*by_set_pos) + return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum) + + unless by_set_pos.nil? || by_set_pos.is_a?(Array) + raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}" + end + by_set_pos.flatten! + by_set_pos.each do |set_pos| + unless (set_pos >= -366 && set_pos <= -1) || + (set_pos <= 366 && set_pos >= 1) + raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})" + end + end + + @by_set_pos = by_set_pos + replace_validations_for(:by_set_pos, by_set_pos && [Validation.new(by_set_pos, self)]) + self + end + + class Validation + + attr_reader :rule, :by_set_pos + + def initialize(by_set_pos, rule) + + @by_set_pos = by_set_pos + @rule = rule + end + + def type + :day + end + + def dst_adjust? + true + end + + def validate(step_time, schedule) + start_of_month = TimeUtil.start_of_month step_time + end_of_month = TimeUtil.end_of_month step_time + + + new_schedule = IceCube::Schedule.new(TimeUtil.previous_month(step_time)) do |s| + s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k}) + end + + puts step_time + occurrences = new_schedule.occurrences_between(start_of_month, end_of_month) + p occurrences + index = occurrences.index(step_time) + if index == nil + 1 + else + positive_set_pos = index + 1 + negative_set_pos = index - occurrences.length + + if @by_set_pos.include?(positive_set_pos) || @by_set_pos.include?(negative_set_pos) + 0 + else + 1 + end + end + end + + + def build_s(builder) + builder.piece(:by_set_pos) << by_set_pos + end + + def build_hash(builder) + builder[:by_set_pos] = by_set_pos + end + + def build_ical(builder) + builder['BYSETPOS'] << by_set_pos + end + + nil + end + + end + +end diff --git a/lib/ice_cube/validations/yearly_by_set_pos.rb b/lib/ice_cube/validations/yearly_by_set_pos.rb new file mode 100644 index 00000000..af629117 --- /dev/null +++ b/lib/ice_cube/validations/yearly_by_set_pos.rb @@ -0,0 +1,89 @@ +module IceCube + + module Validations::YearlyBySetPos + + def by_set_pos(*by_set_pos) + return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum) + + unless by_set_pos.nil? || by_set_pos.is_a?(Array) + raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}" + end + by_set_pos.flatten! + by_set_pos.each do |set_pos| + unless (set_pos >= -366 && set_pos <= -1) || + (set_pos <= 366 && set_pos >= 1) + raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})" + end + end + + @by_set_pos = by_set_pos + replace_validations_for(:by_set_pos, by_set_pos && [Validation.new(by_set_pos, self)]) + self + end + + class Validation + + attr_reader :rule, :by_set_pos + + def initialize(by_set_pos, rule) + + @by_set_pos = by_set_pos + @rule = rule + end + + def type + :day + end + + def dst_adjust? + true + end + + def validate(step_time, schedule) + start_of_year = TimeUtil.start_of_year step_time + end_of_year = TimeUtil.end_of_year step_time + + + new_schedule = IceCube::Schedule.new(TimeUtil.previous_year(step_time)) do |s| + s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k}) + end + + occurrences = new_schedule.occurrences_between(start_of_year, end_of_year) + + index = occurrences.index(step_time) + if index == nil + 1 + else + positive_set_pos = index + 1 + negative_set_pos = index - occurrences.length + + if @by_set_pos.include?(positive_set_pos) || @by_set_pos.include?(negative_set_pos) + 0 + else + 1 + end + end + + + + end + + + def build_s(builder) + builder.piece(:by_set_pos) << by_set_pos + end + + def build_hash(builder) + builder[:by_set_pos] = by_set_pos + end + + def build_ical(builder) + builder['BYSETPOS'] << by_set_pos + end + + nil + end + + end + +end diff --git a/spec/examples/by_set_pos_spec.rb b/spec/examples/by_set_pos_spec.rb new file mode 100644 index 00000000..c9d0a124 --- /dev/null +++ b/spec/examples/by_set_pos_spec.rb @@ -0,0 +1,29 @@ +require File.dirname(__FILE__) + '/../spec_helper' + +module IceCube + + describe MonthlyRule, 'BYSETPOS' do + it 'should behave correctly' do + schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=4" + schedule.start_time = Time.new(2015, 5, 28, 12, 0, 0) + expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([ + Time.new(2015,6,24,12,0,0), + Time.new(2015,7,22,12,0,0), + Time.new(2015,8,26,12,0,0), + Time.new(2015,9,23,12,0,0) + ]) + end + + end + + describe YearlyRule, 'BYSETPOS' do + it 'should behave correctly' do + schedule = IceCube::Schedule.from_ical "RRULE:FREQ=YEARLY;BYMONTH=7;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" + schedule.start_time = Time.new(1966,7,5) + expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([ + Time.new(2015, 7, 31), + Time.new(2016, 7, 31) + ]) + end + end +end diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 12cf8bf8..1bbc0ceb 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -86,6 +86,11 @@ module IceCube expect(rule).to eq(IceCube::Rule.weekly(2, :monday)) end + it 'should be able to parse by_set_pos start (BYSETPOS)' do + rule = IceCube::Rule.from_ical("FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1") + rule.should == IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1]) + end + it 'should return no occurrences after daily interval with count is over' do schedule = IceCube::Schedule.new(Time.now) schedule.add_recurrence_rule(IceCube::Rule.from_ical("FREQ=DAILY;COUNT=5")) From 408e0b0df3c5e6605d326dc74372cd06ecabce47 Mon Sep 17 00:00:00 2001 From: Nathan Ehresman Date: Mon, 30 Jul 2018 16:03:35 -0400 Subject: [PATCH 02/11] Modernize BYSETPOS commit A few small updates to Nicolas Marlier's BYSETPOS support added in PR #349 --- lib/ice_cube/validations/monthly_by_set_pos.rb | 4 +--- lib/ice_cube/validations/yearly_by_set_pos.rb | 2 +- spec/examples/from_ical_spec.rb | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/ice_cube/validations/monthly_by_set_pos.rb b/lib/ice_cube/validations/monthly_by_set_pos.rb index dc87deaf..3bc887be 100644 --- a/lib/ice_cube/validations/monthly_by_set_pos.rb +++ b/lib/ice_cube/validations/monthly_by_set_pos.rb @@ -3,7 +3,7 @@ module IceCube module Validations::MonthlyBySetPos def by_set_pos(*by_set_pos) - return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum) + return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Integer) unless by_set_pos.nil? || by_set_pos.is_a?(Array) raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}" @@ -48,9 +48,7 @@ def validate(step_time, schedule) s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k}) end - puts step_time occurrences = new_schedule.occurrences_between(start_of_month, end_of_month) - p occurrences index = occurrences.index(step_time) if index == nil 1 diff --git a/lib/ice_cube/validations/yearly_by_set_pos.rb b/lib/ice_cube/validations/yearly_by_set_pos.rb index af629117..7ecb807e 100644 --- a/lib/ice_cube/validations/yearly_by_set_pos.rb +++ b/lib/ice_cube/validations/yearly_by_set_pos.rb @@ -3,7 +3,7 @@ module IceCube module Validations::YearlyBySetPos def by_set_pos(*by_set_pos) - return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum) + return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Integer) unless by_set_pos.nil? || by_set_pos.is_a?(Array) raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}" diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 1bbc0ceb..b5716221 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -88,7 +88,7 @@ module IceCube it 'should be able to parse by_set_pos start (BYSETPOS)' do rule = IceCube::Rule.from_ical("FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1") - rule.should == IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1]) + expect(rule).to eq(IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1])) end it 'should return no occurrences after daily interval with count is over' do From 6acee08e39836dc9ce1d7cf93d2a1af45b7ff8d8 Mon Sep 17 00:00:00 2001 From: Christopher Nelson Date: Wed, 23 Nov 2022 15:48:10 +1100 Subject: [PATCH 03/11] Fix bugs --- spec/examples/occurrence_spec.rb | 2 +- spec/examples/to_yaml_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/examples/occurrence_spec.rb b/spec/examples/occurrence_spec.rb index 80c18421..cfbec38e 100644 --- a/spec/examples/occurrence_spec.rb +++ b/spec/examples/occurrence_spec.rb @@ -28,7 +28,7 @@ time_now = Time.current occurrence = Occurrence.new(time_now) - expect(occurrence.to_s(:short)).to eq time_now.to_s(:short) + expect(occurrence.to_fs(:short)).to eq time_now.to_fs(:short) end end diff --git a/spec/examples/to_yaml_spec.rb b/spec/examples/to_yaml_spec.rb index e7c62c59..599b85d3 100644 --- a/spec/examples/to_yaml_spec.rb +++ b/spec/examples/to_yaml_spec.rb @@ -78,7 +78,7 @@ module IceCube end it "should be able to make a round-trip to YAML with .day_of_year" do - schedule1 = Schedule.new(Time.now) + schedule1 = Schedule.new(Time.zone.now) schedule1.add_recurrence_rule Rule.yearly.day_of_year(100, 200) yaml_string = schedule1.to_yaml @@ -90,7 +90,7 @@ module IceCube end it "should be able to make a round-trip to YAML with .hour_of_day" do - schedule = Schedule.new(Time.now) + schedule = Schedule.new(Time.zone.now) schedule.add_recurrence_rule Rule.daily.hour_of_day(1, 2) yaml_string = schedule.to_yaml @@ -101,7 +101,7 @@ module IceCube end it "should be able to make a round-trip to YAML with .minute_of_hour" do - schedule = Schedule.new(Time.now) + schedule = Schedule.new(Time.zone.now) schedule.add_recurrence_rule Rule.daily.minute_of_hour(0, 30) yaml_string = schedule.to_yaml @@ -112,7 +112,7 @@ module IceCube end it "should be able to make a round-trip to YAML with .month_of_year" do - schedule = Schedule.new(Time.now) + schedule = Schedule.new(Time.zone.now) schedule.add_recurrence_rule Rule.yearly.month_of_year(:april, :may) yaml_string = schedule.to_yaml @@ -123,7 +123,7 @@ module IceCube end it "should be able to make a round-trip to YAML with .second_of_minute" do - schedule = Schedule.new(Time.now) + schedule = Schedule.new(Time.zone.now) schedule.add_recurrence_rule Rule.daily.second_of_minute(1, 2) yaml_string = schedule.to_yaml From 0c5d72b35770ba9a916617e551c8b498659713e9 Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Wed, 22 Feb 2023 13:59:30 +1100 Subject: [PATCH 04/11] NEB-1737 Added pertinent RRULE test, fixed minor issue in another spec --- spec/examples/by_set_pos_spec.rb | 13 +++++++++++++ spec/examples/time_util_spec.rb | 6 ++++++ spec/examples/to_yaml_spec.rb | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/spec/examples/by_set_pos_spec.rb b/spec/examples/by_set_pos_spec.rb index c9d0a124..6f5a03ba 100644 --- a/spec/examples/by_set_pos_spec.rb +++ b/spec/examples/by_set_pos_spec.rb @@ -14,6 +14,19 @@ module IceCube ]) end + context 'when billing occurs at the end of the month' do + it 'should return the correct end of the months dates' do + schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=-1" + schedule.start_time = Time.new(2022, 11, 1, 12, 0, 0) + expect(schedule.occurrences_between(Time.new(2022, 10, 01), Time.new(2023, 10, 31))).to eq([ + Time.new(2022,11,30,12,0,0), + Time.new(2022,12,31,12,0,0), + Time.new(2023,01,31,12,0,0), + Time.new(2023,02,28,12,0,0) + ]) + end + end + end describe YearlyRule, 'BYSETPOS' do diff --git a/spec/examples/time_util_spec.rb b/spec/examples/time_util_spec.rb index 43491a8b..4d7834bb 100644 --- a/spec/examples/time_util_spec.rb +++ b/spec/examples/time_util_spec.rb @@ -92,6 +92,12 @@ module IceCube end end + describe :serialize_time do + it "supports ISO8601 time strings" do + expect(TimeUtil.serialize_time(Time.utc(2014, 4, 4, 10, 30, 0))).to eq("2014-04-04T18:30:00+08:00") + end + end + describe :match_zone do let(:date) { Date.new(2014, 1, 1) } diff --git a/spec/examples/to_yaml_spec.rb b/spec/examples/to_yaml_spec.rb index 599b85d3..28bd9d83 100644 --- a/spec/examples/to_yaml_spec.rb +++ b/spec/examples/to_yaml_spec.rb @@ -134,7 +134,7 @@ module IceCube end it "should be able to make a round-trip to YAML whilst preserving exception rules" do - original_schedule = Schedule.new(Time.now) + original_schedule = Schedule.new(Time.zone.now) original_schedule.add_recurrence_rule Rule.daily.day(:monday, :wednesday) original_schedule.add_exception_rule Rule.daily.day(:wednesday) From 32527ad9d67a7e3bab307056b94c7851fa21e81a Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Wed, 22 Feb 2023 14:55:41 +1100 Subject: [PATCH 05/11] NEB-1737 Updating changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0022b92..ffc1259e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Indonesian translations. ([#505](https://github.com/seejohnrun/ice_cube/pull/505)) by [@achmiral](https://github.com/achmiral) +- Support for BYSETPOS ### Changed - 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) From 6a28df4d21165ec9a1f754aac31cfa89451d5d98 Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Wed, 22 Feb 2023 17:12:08 +1100 Subject: [PATCH 06/11] NEB-1737 Linted the code, added more bysetops tests --- ice_cube.gemspec | 1 - lib/ice_cube.rb | 4 +- lib/ice_cube/builders/string_builder.rb | 2 +- lib/ice_cube/input_alignment.rb | 4 +- lib/ice_cube/null_i18n.rb | 2 +- lib/ice_cube/occurrence.rb | 2 +- lib/ice_cube/parsers/ical_parser.rb | 2 +- lib/ice_cube/schedule.rb | 2 +- lib/ice_cube/time_util.rb | 30 ++-- lib/ice_cube/validations/day_of_week.rb | 4 +- lib/ice_cube/validations/day_of_year.rb | 4 +- lib/ice_cube/validations/fixed_value.rb | 6 +- lib/ice_cube/validations/lock.rb | 6 +- .../validations/monthly_by_set_pos.rb | 14 +- lib/ice_cube/validations/yearly_by_set_pos.rb | 17 +-- spec/examples/by_set_pos_spec.rb | 143 ++++++++++++++---- spec/examples/from_ical_spec.rb | 2 +- spec/examples/ice_cube_spec.rb | 2 +- spec/examples/time_util_spec.rb | 5 +- 19 files changed, 160 insertions(+), 92 deletions(-) diff --git a/ice_cube.gemspec b/ice_cube.gemspec index 5191c6fa..b1924c18 100644 --- a/ice_cube.gemspec +++ b/ice_cube.gemspec @@ -17,7 +17,6 @@ Gem::Specification.new do |s| s.version = IceCube::VERSION s.platform = Gem::Platform::RUBY s.files = Dir["lib/**/*.rb", "config/**/*.yml"] - s.test_files = Dir.glob("spec/*.rb") s.require_paths = ["lib"] s.add_development_dependency("rake") diff --git a/lib/ice_cube.rb b/lib/ice_cube.rb index 5ef72e4a..12ede342 100644 --- a/lib/ice_cube.rb +++ b/lib/ice_cube.rb @@ -49,8 +49,8 @@ module Validations autoload :YearlyInterval, "ice_cube/validations/yearly_interval" autoload :HourlyInterval, "ice_cube/validations/hourly_interval" - autoload :MonthlyBySetPos, 'ice_cube/validations/monthly_by_set_pos' - autoload :YearlyBySetPos, 'ice_cube/validations/yearly_by_set_pos' + autoload :MonthlyBySetPos, "ice_cube/validations/monthly_by_set_pos" + autoload :YearlyBySetPos, "ice_cube/validations/yearly_by_set_pos" autoload :HourOfDay, "ice_cube/validations/hour_of_day" autoload :MonthOfYear, "ice_cube/validations/month_of_year" diff --git a/lib/ice_cube/builders/string_builder.rb b/lib/ice_cube/builders/string_builder.rb index c87e97ed..07703631 100644 --- a/lib/ice_cube/builders/string_builder.rb +++ b/lib/ice_cube/builders/string_builder.rb @@ -61,7 +61,7 @@ def ordinal(number) ord = IceCube::I18n.t("ice_cube.integer.ordinals")[number] || IceCube::I18n.t("ice_cube.integer.ordinals")[number % 10] || IceCube::I18n.t("ice_cube.integer.ordinals")[:default] - number >= 0 ? ord : IceCube::I18n.t("ice_cube.integer.negative", ordinal: ord) + (number >= 0) ? ord : IceCube::I18n.t("ice_cube.integer.negative", ordinal: ord) end end diff --git a/lib/ice_cube/input_alignment.rb b/lib/ice_cube/input_alignment.rb index 8900fa15..3cdd5070 100644 --- a/lib/ice_cube/input_alignment.rb +++ b/lib/ice_cube/input_alignment.rb @@ -28,12 +28,12 @@ def interval_validation end def interval_value - @interval_value ||= rule_part == :interval ? value : interval_validation.interval + @interval_value ||= (rule_part == :interval) ? value : interval_validation.interval end def fixed_validations @fixed_validations ||= @rule.validations.values.flatten.select { |v| - interval_type = (v.type == :wday ? :day : v.type) + interval_type = ((v.type == :wday) ? :day : v.type) v.class < Validations::FixedValue && interval_type == rule.base_interval_validation.type } diff --git a/lib/ice_cube/null_i18n.rb b/lib/ice_cube/null_i18n.rb index 028ab2c5..0ec25e23 100644 --- a/lib/ice_cube/null_i18n.rb +++ b/lib/ice_cube/null_i18n.rb @@ -5,7 +5,7 @@ module NullI18n def self.t(key, options = {}) base = key.to_s.split(".").reduce(config) { |hash, current_key| hash[current_key] } - base = base[options[:count] == 1 ? "one" : "other"] if options[:count] + base = base[(options[:count] == 1) ? "one" : "other"] if options[:count] case base when Hash diff --git a/lib/ice_cube/occurrence.rb b/lib/ice_cube/occurrence.rb index 4c51f962..3892f6c6 100644 --- a/lib/ice_cube/occurrence.rb +++ b/lib/ice_cube/occurrence.rb @@ -90,7 +90,7 @@ def to_s(format = nil) else t0, t1 = start_time.to_s, end_time.to_s end - duration > 0 ? "#{t0} - #{t1}" : t0 + (duration > 0) ? "#{t0} - #{t1}" : t0 end def overnight? diff --git a/lib/ice_cube/parsers/ical_parser.rb b/lib/ice_cube/parsers/ical_parser.rb index 3b00fa1a..36a0a548 100644 --- a/lib/ice_cube/parsers/ical_parser.rb +++ b/lib/ice_cube/parsers/ical_parser.rb @@ -75,7 +75,7 @@ def self.rule_from_ical(ical) when "BYYEARDAY" validations[:day_of_year] = value.split(",").map(&:to_i) when "BYSETPOS" - params[:validations][:by_set_pos] = value.split(',').collect(&:to_i) + params[:validations][:by_set_pos] = value.split(",").collect(&:to_i) else validations[name] = nil # invalid type end diff --git a/lib/ice_cube/schedule.rb b/lib/ice_cube/schedule.rb index 68f01cc9..036c9f2b 100644 --- a/lib/ice_cube/schedule.rb +++ b/lib/ice_cube/schedule.rb @@ -191,7 +191,7 @@ def previous_occurrences(num, from) from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{from.inspect}" return [] if from <= start_time a = enumerate_occurrences(start_time, from - 1).to_a - a.size > num ? a[-1 * num, a.size] : a + (a.size > num) ? a[-1 * num, a.size] : a end # The remaining occurrences (same requirements as all_occurrences) diff --git a/lib/ice_cube/time_util.rb b/lib/ice_cube/time_util.rb index b6669573..d14c74bf 100644 --- a/lib/ice_cube/time_util.rb +++ b/lib/ice_cube/time_util.rb @@ -1,7 +1,7 @@ -require 'date' -require 'time' -require 'active_support' -require 'active_support/core_ext' +require "date" +require "time" +require "active_support" +require "active_support/core_ext" module IceCube module TimeUtil @@ -53,7 +53,7 @@ def self.match_zone(input_time, reference) else time.getlocal(reference.utc_offset) end - Date === input_time ? beginning_of_date(time, reference) : time + (Date === input_time) ? beginning_of_date(time, reference) : time end # Ensure that this is either nil, or a time @@ -318,12 +318,12 @@ def to_time def add(type, val) type = :day if type == :wday @time += case type - when :year then TimeUtil.days_in_n_years(@time, val) * ONE_DAY - when :month then TimeUtil.days_in_n_months(@time, val) * ONE_DAY - when :day then val * ONE_DAY - when :hour then val * ONE_HOUR - when :min then val * ONE_MINUTE - when :sec then val + when :year then TimeUtil.days_in_n_years(@time, val) * ONE_DAY + when :month then TimeUtil.days_in_n_months(@time, val) * ONE_DAY + when :day then val * ONE_DAY + when :hour then val * ONE_HOUR + when :min then val * ONE_MINUTE + when :sec then val end end @@ -350,20 +350,20 @@ def sec=(value) end def clear_sec - @time.sec > 0 ? @time -= @time.sec : @time + (@time.sec > 0) ? @time -= @time.sec : @time end def clear_min - @time.min > 0 ? @time -= (@time.min * ONE_MINUTE) : @time + (@time.min > 0) ? @time -= (@time.min * ONE_MINUTE) : @time end def clear_hour - @time.hour > 0 ? @time -= (@time.hour * ONE_HOUR) : @time + (@time.hour > 0) ? @time -= (@time.hour * ONE_HOUR) : @time end # Move to the first of the month, 0 hours def clear_day - @time.day > 1 ? @time -= (@time.day - 1) * ONE_DAY : @time + (@time.day > 1) ? @time -= (@time.day - 1) * ONE_DAY : @time end # Clear to january 1st diff --git a/lib/ice_cube/validations/day_of_week.rb b/lib/ice_cube/validations/day_of_week.rb index e00193b7..c04695bc 100644 --- a/lib/ice_cube/validations/day_of_week.rb +++ b/lib/ice_cube/validations/day_of_week.rb @@ -29,12 +29,12 @@ def dst_adjust? def validate(step_time, start_time) wday = step_time.wday - offset = day < wday ? (7 - wday + day) : (day - wday) + offset = (day < wday) ? (7 - wday + day) : (day - wday) wrapper = TimeUtil::TimeWrapper.new(step_time) wrapper.add :day, offset loop do which_occ, num_occ = TimeUtil.which_occurrence_in_month(wrapper.to_time, day) - this_occ = occ < 0 ? (num_occ + occ + 1) : occ + this_occ = (occ < 0) ? (num_occ + occ + 1) : occ break offset if which_occ == this_occ wrapper.add :day, 7 offset += 7 diff --git a/lib/ice_cube/validations/day_of_year.rb b/lib/ice_cube/validations/day_of_year.rb index 239f241e..c9efecdb 100644 --- a/lib/ice_cube/validations/day_of_year.rb +++ b/lib/ice_cube/validations/day_of_year.rb @@ -28,9 +28,9 @@ def dst_adjust? def validate(step_time, start_time) days_in_year = TimeUtil.days_in_year(step_time) - yday = day < 0 ? day + days_in_year + 1 : day + yday = (day < 0) ? day + days_in_year + 1 : day offset = yday - step_time.yday - offset >= 0 ? offset : offset + days_in_year + (offset >= 0) ? offset : offset + days_in_year end def build_s(builder) diff --git a/lib/ice_cube/validations/fixed_value.rb b/lib/ice_cube/validations/fixed_value.rb index 2cfdd5fb..243d4b8c 100644 --- a/lib/ice_cube/validations/fixed_value.rb +++ b/lib/ice_cube/validations/fixed_value.rb @@ -26,7 +26,7 @@ def validate(time, start_time) def validate_interval_lock(time, start_time) t0 = starting_unit(start_time) t1 = time.send(type) - t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0 + (t0 >= t1) ? t0 - t1 : INTERVALS[type] - t1 + t0 end # Lock the hour if explicitly set by hour_of_day, but allow for the nearest @@ -73,11 +73,11 @@ def validate_day_lock(time, start_time) if value && value > 0 until_next_month = days_in_month + sleeps else - until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date) + until_next_month = (start < 28) ? days_in_month : TimeUtil.days_to_next_month(date) until_next_month += sleeps - month_overflow end - sleeps >= 0 ? sleeps : until_next_month + (sleeps >= 0) ? sleeps : until_next_month end def starting_unit(start_time) diff --git a/lib/ice_cube/validations/lock.rb b/lib/ice_cube/validations/lock.rb index 954463c1..33249382 100644 --- a/lib/ice_cube/validations/lock.rb +++ b/lib/ice_cube/validations/lock.rb @@ -26,7 +26,7 @@ def validate(time, start_time) def validate_interval_lock(time, start_time) t0 = starting_unit(start_time) t1 = time.send(type) - t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0 + (t0 >= t1) ? t0 - t1 : INTERVALS[type] - t1 + t0 end # Lock the hour if explicitly set by hour_of_day, but allow for the nearest @@ -73,11 +73,11 @@ def validate_day_lock(time, start_time) if value && value > 0 until_next_month = days_in_month + sleeps else - until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date) + until_next_month = (start < 28) ? days_in_month : TimeUtil.days_to_next_month(date) until_next_month += sleeps - month_overflow end - sleeps >= 0 ? sleeps : until_next_month + (sleeps >= 0) ? sleeps : until_next_month end def starting_unit(start_time) diff --git a/lib/ice_cube/validations/monthly_by_set_pos.rb b/lib/ice_cube/validations/monthly_by_set_pos.rb index 3bc887be..04098430 100644 --- a/lib/ice_cube/validations/monthly_by_set_pos.rb +++ b/lib/ice_cube/validations/monthly_by_set_pos.rb @@ -1,7 +1,5 @@ module IceCube - module Validations::MonthlyBySetPos - def by_set_pos(*by_set_pos) return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Integer) @@ -22,11 +20,9 @@ def by_set_pos(*by_set_pos) end class Validation - attr_reader :rule, :by_set_pos def initialize(by_set_pos, rule) - @by_set_pos = by_set_pos @rule = rule end @@ -43,14 +39,13 @@ def validate(step_time, schedule) start_of_month = TimeUtil.start_of_month step_time end_of_month = TimeUtil.end_of_month step_time - new_schedule = IceCube::Schedule.new(TimeUtil.previous_month(step_time)) do |s| - s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k}) + s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)) end occurrences = new_schedule.occurrences_between(start_of_month, end_of_month) index = occurrences.index(step_time) - if index == nil + if index.nil? 1 else positive_set_pos = index + 1 @@ -64,7 +59,6 @@ def validate(step_time, schedule) end end - def build_s(builder) builder.piece(:by_set_pos) << by_set_pos end @@ -74,12 +68,10 @@ def build_hash(builder) end def build_ical(builder) - builder['BYSETPOS'] << by_set_pos + builder["BYSETPOS"] << by_set_pos end nil end - end - end diff --git a/lib/ice_cube/validations/yearly_by_set_pos.rb b/lib/ice_cube/validations/yearly_by_set_pos.rb index 7ecb807e..d31c57af 100644 --- a/lib/ice_cube/validations/yearly_by_set_pos.rb +++ b/lib/ice_cube/validations/yearly_by_set_pos.rb @@ -1,7 +1,5 @@ module IceCube - module Validations::YearlyBySetPos - def by_set_pos(*by_set_pos) return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Integer) @@ -22,11 +20,9 @@ def by_set_pos(*by_set_pos) end class Validation - attr_reader :rule, :by_set_pos def initialize(by_set_pos, rule) - @by_set_pos = by_set_pos @rule = rule end @@ -43,15 +39,14 @@ def validate(step_time, schedule) start_of_year = TimeUtil.start_of_year step_time end_of_year = TimeUtil.end_of_year step_time - new_schedule = IceCube::Schedule.new(TimeUtil.previous_year(step_time)) do |s| - s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k}) + s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)) end occurrences = new_schedule.occurrences_between(start_of_year, end_of_year) index = occurrences.index(step_time) - if index == nil + if index.nil? 1 else positive_set_pos = index + 1 @@ -63,12 +58,8 @@ def validate(step_time, schedule) 1 end end - - - end - def build_s(builder) builder.piece(:by_set_pos) << by_set_pos end @@ -78,12 +69,10 @@ def build_hash(builder) end def build_ical(builder) - builder['BYSETPOS'] << by_set_pos + builder["BYSETPOS"] << by_set_pos end nil end - end - end diff --git a/spec/examples/by_set_pos_spec.rb b/spec/examples/by_set_pos_spec.rb index 6f5a03ba..8ca84e3f 100644 --- a/spec/examples/by_set_pos_spec.rb +++ b/spec/examples/by_set_pos_spec.rb @@ -1,42 +1,127 @@ -require File.dirname(__FILE__) + '/../spec_helper' +require File.dirname(__FILE__) + "/../spec_helper" module IceCube + describe MonthlyRule, "BYSETPOS" do + subject(:schedule) { IceCube::Schedule.from_ical(from_ical) } + before(:each) do + schedule.start_time = schedule_start + end + let(:occurrences) { schedule.occurrences_between(from_time, to_time) } + context "when the rule is the first 4 Wednesdays ..." do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=4" } + let(:schedule_start) { Time.new(2015, 5, 28, 12, 0, 0) } + let(:from_time) { Time.new(2015, 1, 1) } + let(:to_time) { Time.new(2017, 1, 1) } + it "returns the first 4 Wednesdays ..." do + expect(occurrences).to eq( + [ + Time.new(2015, 6, 24, 12, 0, 0), + Time.new(2015, 7, 22, 12, 0, 0), + Time.new(2015, 8, 26, 12, 0, 0), + Time.new(2015, 9, 23, 12, 0, 0) + ] + ) + end + end - describe MonthlyRule, 'BYSETPOS' do - it 'should behave correctly' do - schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=4" - schedule.start_time = Time.new(2015, 5, 28, 12, 0, 0) - expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([ - Time.new(2015,6,24,12,0,0), - Time.new(2015,7,22,12,0,0), - Time.new(2015,8,26,12,0,0), - Time.new(2015,9,23,12,0,0) - ]) + context "when event occurs on a leap year" do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=-1" } + let(:schedule_start) { Time.new(2019, 11, 1, 12, 0, 0) } + let(:from_time) { Time.new(2019, 10, 1) } + let(:to_time) { Time.new(2020, 10, 31) } + it "returns the correct end of the months date including the leap month" do + expect(occurrences).to eq( + [ + Time.new(2019, 11, 30, 12, 0, 0), + Time.new(2019, 12, 31, 12, 0, 0), + Time.new(2020, 1, 31, 12, 0, 0), + Time.new(2020, 2, 29, 12, 0, 0) + ] + ) + end + end + + context "when the rule is the first 4 last days of the month by set pos" do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=-1" } + let(:schedule_start) { Time.new(2022, 11, 1, 12, 0, 0) } + let(:from_time) { Time.new(2022, 10, 1) } + let(:to_time) { Time.new(2023, 10, 31) } + it "returns the first 4 last days of the month by set pos" do + expect(occurrences).to eq( + [ + Time.new(2022, 11, 30, 12, 0, 0), + Time.new(2022, 12, 31, 12, 0, 0), + Time.new(2023, 1, 31, 12, 0, 0), + Time.new(2023, 2, 28, 12, 0, 0) + ] + ) + end end - context 'when billing occurs at the end of the month' do - it 'should return the correct end of the months dates' do - schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=-1" - schedule.start_time = Time.new(2022, 11, 1, 12, 0, 0) - expect(schedule.occurrences_between(Time.new(2022, 10, 01), Time.new(2023, 10, 31))).to eq([ - Time.new(2022,11,30,12,0,0), - Time.new(2022,12,31,12,0,0), - Time.new(2023,01,31,12,0,0), - Time.new(2023,02,28,12,0,0) - ]) + context "when the rule is for the first 4 second last days of the month by set pos" do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=27,28,29,30,31;BYSETPOS=-2" } + let(:schedule_start) { Time.new(2022, 11, 1, 12, 0, 0) } + let(:from_time) { Time.new(2022, 10, 1) } + let(:to_time) { Time.new(2023, 10, 31) } + it "returns the first 4 previous last days of the month by set pos" do + expect(occurrences).to eq( + [ + Time.new(2022, 11, 29, 12, 0, 0), + Time.new(2022, 12, 30, 12, 0, 0), + Time.new(2023, 1, 30, 12, 0, 0), + Time.new(2023, 2, 27, 12, 0, 0) + ] + ) end end + context "when the rule is the first 4 after the last days of the month by set pos" do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=4" } + let(:schedule_start) { Time.new(2023, 02, 22, 12, 0, 0) } + let(:from_time) { Time.new(2022, 10, 1) } + let(:to_time) { Time.new(2023, 10, 31) } + it "returns the first 4 previous last days of the month by set pos" do + expect(occurrences).to eq( + [ + Time.new(2023, 3, 31, 12, 0, 0), + Time.new(2023, 5, 31, 12, 0, 0), + Time.new(2023, 7, 31, 12, 0, 0), + Time.new(2023, 8, 31, 12, 0, 0) + ] + ) + end + end + + context "when the rule is the first 4 after the last wednesday of the month" do + let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=-1" } + let(:schedule_start) { Time.new(2023, 02, 22, 12, 0, 0) } + let(:from_time) { Time.new(2022, 10, 1) } + let(:to_time) { Time.new(2023, 10, 31) } + it "returns the first 4 previous last days of the month by set pos" do + expect(occurrences).to eq( + [ + Time.new(2023, 2, 22, 12, 0, 0), + Time.new(2023, 3, 29, 12, 0, 0), + Time.new(2023, 4, 26, 12, 0, 0), + Time.new(2023, 5, 31, 12, 0, 0) + ] + ) + end + end end - describe YearlyRule, 'BYSETPOS' do - it 'should behave correctly' do - schedule = IceCube::Schedule.from_ical "RRULE:FREQ=YEARLY;BYMONTH=7;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" - schedule.start_time = Time.new(1966,7,5) - expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([ - Time.new(2015, 7, 31), - Time.new(2016, 7, 31) - ]) + describe YearlyRule, "BYSETPOS" do + subject(:schedule) { IceCube::Schedule.from_ical(from_ical) } + let(:from_ical) { "RRULE:FREQ=YEARLY;BYMONTH=7;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" } + before(:each) do + schedule.start_time = schedule_start_time + end + let(:occurrences) { schedule.occurrences_between(from_time, to_time) } + let(:schedule_start_time) { Time.new(1966, 7, 5) } + let(:from_time) { Time.new(2015, 1, 1) } + let(:to_time) { Time.new(2017, 1, 1) } + it "returns only the last day of each July in 2015 and 2016" do + expect(occurrences).to eq([Time.new(2015, 7, 31), Time.new(2016, 7, 31)]) end end end diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index b7fd3d36..3d287310 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -84,7 +84,7 @@ module IceCube expect(rule).to eq(IceCube::Rule.weekly(2, :monday)) end - it 'should be able to parse by_set_pos start (BYSETPOS)' do + it "should be able to parse by_set_pos start (BYSETPOS)" do rule = IceCube::Rule.from_ical("FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1") expect(rule).to eq(IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1])) end diff --git a/spec/examples/ice_cube_spec.rb b/spec/examples/ice_cube_spec.rb index 354d2e48..d925f63d 100644 --- a/spec/examples/ice_cube_spec.rb +++ b/spec/examples/ice_cube_spec.rb @@ -668,7 +668,7 @@ def quick_attempt_test time = Time.now 10.times do - (yield).next_occurrence(Time.now) + yield.next_occurrence(Time.now) end total = Time.now - time expect(total).to be < 0.1 diff --git a/spec/examples/time_util_spec.rb b/spec/examples/time_util_spec.rb index 4d7834bb..ce2918c9 100644 --- a/spec/examples/time_util_spec.rb +++ b/spec/examples/time_util_spec.rb @@ -93,8 +93,11 @@ module IceCube end describe :serialize_time do + subject(:serialize_time) { TimeUtil.serialize_time(time) } + let(:time) { Time.utc(2014, 4, 4, 10, 30, 0) } + let(:iso_time_str) { "2014-04-04T18:30:00+08:00" } it "supports ISO8601 time strings" do - expect(TimeUtil.serialize_time(Time.utc(2014, 4, 4, 10, 30, 0))).to eq("2014-04-04T18:30:00+08:00") + expect(serialize_time).to eq(iso_time_str) end end From fa960dcb046d77c0d43502a9933f9aed68d45e1e Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Wed, 22 Feb 2023 17:15:23 +1100 Subject: [PATCH 07/11] NEB-1737 Linted the code more --- spec/examples/by_set_pos_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/examples/by_set_pos_spec.rb b/spec/examples/by_set_pos_spec.rb index 8ca84e3f..4f5e718d 100644 --- a/spec/examples/by_set_pos_spec.rb +++ b/spec/examples/by_set_pos_spec.rb @@ -77,7 +77,7 @@ module IceCube context "when the rule is the first 4 after the last days of the month by set pos" do let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=4" } - let(:schedule_start) { Time.new(2023, 02, 22, 12, 0, 0) } + let(:schedule_start) { Time.new(2023, 0o2, 22, 12, 0, 0) } let(:from_time) { Time.new(2022, 10, 1) } let(:to_time) { Time.new(2023, 10, 31) } it "returns the first 4 previous last days of the month by set pos" do @@ -94,7 +94,7 @@ module IceCube context "when the rule is the first 4 after the last wednesday of the month" do let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=-1" } - let(:schedule_start) { Time.new(2023, 02, 22, 12, 0, 0) } + let(:schedule_start) { Time.new(2023, 0o2, 22, 12, 0, 0) } let(:from_time) { Time.new(2022, 10, 1) } let(:to_time) { Time.new(2023, 10, 31) } it "returns the first 4 previous last days of the month by set pos" do From ec5e91fa75fd75f38eb55586b457af1aac348be3 Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Thu, 23 Feb 2023 09:48:28 +1100 Subject: [PATCH 08/11] NEB-1737 Updating missing require library in the test --- spec/examples/occurrence_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/examples/occurrence_spec.rb b/spec/examples/occurrence_spec.rb index cfbec38e..1496993f 100644 --- a/spec/examples/occurrence_spec.rb +++ b/spec/examples/occurrence_spec.rb @@ -1,4 +1,6 @@ require File.dirname(__FILE__) + "/../spec_helper" +require "active_support/all" +require "active_support/core_ext" describe Occurrence do it "reports as a Time" do From 184b06d0533741b62397c22fc20a1af94b15b615 Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Thu, 23 Feb 2023 10:44:40 +1100 Subject: [PATCH 09/11] NEB-1737 Refined rspec tests --- spec/examples/from_ical_spec.rb | 10 +++++++--- spec/examples/occurrence_spec.rb | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/examples/from_ical_spec.rb b/spec/examples/from_ical_spec.rb index 3d287310..0912becd 100644 --- a/spec/examples/from_ical_spec.rb +++ b/spec/examples/from_ical_spec.rb @@ -84,9 +84,13 @@ module IceCube expect(rule).to eq(IceCube::Rule.weekly(2, :monday)) end - it "should be able to parse by_set_pos start (BYSETPOS)" do - rule = IceCube::Rule.from_ical("FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1") - expect(rule).to eq(IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1])) + context "when from_ical contains BYSETPOS" do + let(:from_ical) { "FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1" } + let(:rule) { IceCube::Rule.from_ical(from_ical) } + it "parses BYSETPOS input" do + expect(rule.to_ical).to eq(from_ical) + expect(rule).to eq(IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1])) + end end it "should return no occurrences after daily interval with count is over" do diff --git a/spec/examples/occurrence_spec.rb b/spec/examples/occurrence_spec.rb index 1496993f..3a64ccde 100644 --- a/spec/examples/occurrence_spec.rb +++ b/spec/examples/occurrence_spec.rb @@ -30,7 +30,7 @@ time_now = Time.current occurrence = Occurrence.new(time_now) - expect(occurrence.to_fs(:short)).to eq time_now.to_fs(:short) + expect(occurrence.strftime('%Y-%m-%d')).to eq time_now.strftime('%Y-%m-%d') end end From 1f2e6df18b965a1c04e639991c41ef002e28d182 Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Thu, 23 Feb 2023 10:46:30 +1100 Subject: [PATCH 10/11] NEB-1737 Refined rspec tests --- spec/examples/occurrence_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/examples/occurrence_spec.rb b/spec/examples/occurrence_spec.rb index 3a64ccde..be358104 100644 --- a/spec/examples/occurrence_spec.rb +++ b/spec/examples/occurrence_spec.rb @@ -30,7 +30,7 @@ time_now = Time.current occurrence = Occurrence.new(time_now) - expect(occurrence.strftime('%Y-%m-%d')).to eq time_now.strftime('%Y-%m-%d') + expect(occurrence.strftime("%Y-%m-%d")).to eq time_now.strftime("%Y-%m-%d") end end From d09b34dd3a2fccf6bf03469a5dcbc94a686013de Mon Sep 17 00:00:00 2001 From: Juan Deniz Date: Thu, 23 Feb 2023 11:21:42 +1100 Subject: [PATCH 11/11] NEB-1737 Refined rspec tests --- spec/examples/by_set_pos_spec.rb | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/spec/examples/by_set_pos_spec.rb b/spec/examples/by_set_pos_spec.rb index 4f5e718d..af3b51ca 100644 --- a/spec/examples/by_set_pos_spec.rb +++ b/spec/examples/by_set_pos_spec.rb @@ -77,7 +77,7 @@ module IceCube context "when the rule is the first 4 after the last days of the month by set pos" do let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYMONTHDAY=28,29,30,31;BYSETPOS=4" } - let(:schedule_start) { Time.new(2023, 0o2, 22, 12, 0, 0) } + let(:schedule_start) { Time.new(2023, 2, 22, 12, 0, 0) } let(:from_time) { Time.new(2022, 10, 1) } let(:to_time) { Time.new(2023, 10, 31) } it "returns the first 4 previous last days of the month by set pos" do @@ -94,7 +94,7 @@ module IceCube context "when the rule is the first 4 after the last wednesday of the month" do let(:from_ical) { "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=-1" } - let(:schedule_start) { Time.new(2023, 0o2, 22, 12, 0, 0) } + let(:schedule_start) { Time.new(2023, 2, 22, 12, 0, 0) } let(:from_time) { Time.new(2022, 10, 1) } let(:to_time) { Time.new(2023, 10, 31) } it "returns the first 4 previous last days of the month by set pos" do @@ -110,6 +110,34 @@ module IceCube end end + describe MonthlyRule, "BYSETPOS and BYDAY" do + context "when the rules include more varied set of BYDAY values" do + let(:start_date) { Date.new(2023, 1, 1) } + + it "generates the expected dates" do + rrules = [ + "RRULE:FREQ=MONTHLY;BYDAY=1FR,3FR;BYSETPOS=1,3", + "RRULE:FREQ=MONTHLY;BYDAY=1MO,-1MO;BYSETPOS=1,-1", + "RRULE:FREQ=MONTHLY;BYDAY=2WE,4WE;BYSETPOS=2,4", + "RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1", + "RRULE:FREQ=MONTHLY;BYDAY=2FR;BYMONTHDAY=16,17,18,19,20,21,22;BYSETPOS=2" + ] + + expected_dates = [ + Date.new(2023, 3, 3), Date.new(2023, 2, 27), + Date.new(2023, 3, 22), Date.new(2023, 2, 28), + nil + ] + + rrules.each_with_index do |rrule, i| + schedule = IceCube::Schedule.from_ical(rrule) + dates = schedule.occurrences_between(start_date, start_date + 6.months) + expect(dates.map(&:to_date).first).to eq(expected_dates[i]) + end + end + end + end + describe YearlyRule, "BYSETPOS" do subject(:schedule) { IceCube::Schedule.from_ical(from_ical) } let(:from_ical) { "RRULE:FREQ=YEARLY;BYMONTH=7;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1" }