diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..40a2095a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ruby +{ + "name": "IceCube", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/ruby:1-3.3-bookworm", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "ruby --version", + + // Configure tool-specific properties. + "customizations" : { + "jetbrains" : { + "backend" : "RubyMine" + } + }, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index b74ee513..59c01fe2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,21 +19,21 @@ jobs: changelog: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - id: read-version run: | echo "::set-output name=VERSION::`cat lib/ice_cube/version.rb | grep -i version | awk '{ print $3 }' | sed -e 's/\"//g'`" - - uses: dangoslen/changelog-enforcer@v2.3.1 + - uses: dangoslen/changelog-enforcer@v3 with: skipLabels: 'skip-changelog' expectedLatestVersion: ${{ steps.read-version.outputs.VERSION }} lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: '2.6' + ruby-version: '3.1' bundler-cache: true - run: bundle exec standardrb test: @@ -42,22 +42,13 @@ jobs: - lint strategy: matrix: - rails: ['5.2', '6.0.4', '6.1', '7.0'] - ruby: ['2.6', '2.7', '3.0', '3.1'] - exclude: - - rails: '5.2' - ruby: '3.0' - - rails: '5.2' - ruby: '3.1' - - rails: '7.0' - ruby: '2.6' - - rails: '7.0' - ruby: '2.7' + rails: ['6.1', '7.0', '7.1'] + ruby: ['3.1', '3.2', '3.3'] runs-on: ubuntu-latest env: RAILS_VERSION: ${{ matrix.rails }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c0022b92..3080c208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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) +- Updated supported versions of Ruby and Rails and fixed standardrb lint issues. ([#552](https://github.com/ice-cube-ruby/ice_cube/pull/552)) by [@pacso](https://github.com/pacso) +- Fixed `I18n.load_path` injection ([#546](https://github.com/ice-cube-ruby/ice_cube/pull/546)) by [@glaszig](https://github.com/glaszig) ### Fixed - Fix for weekly interval results when requesting `occurrences_between` on a narrow range ([#487](https://github.com/seejohnrun/ice_cube/pull/487)) by [@jakebrady5](https://github.com/jakebrady5) - When using a rule with hour_of_day validations, and asking for occurrences on the day that DST skips forward, valid occurrences would be missed. ([#464](https://github.com/seejohnrun/ice_cube/pull/464)) by [@jakebrady5](https://github.com/jakebrady5) - Include `exrules` when exporting a schedule to YAML, JSON or a Hash. ([#519](https://github.com/ice-cube-ruby/ice_cube/pull/519)) by [@pacso](https://github.com/pacso) +- Documentation links updated to point to the new repository location. ([#553](https://github.com/ice-cube-ruby/ice_cube/pull/553)) by [@pacso](https://github.com/pacso) +- Input validation added for day_of_week validator. Fixes infinite loops when invalid day_of_week occurrences are provided. ([#554](https://github.com/ice-cube-ruby/ice_cube/pull/554)) by [@pacso](https://github.com/pacso) ## [0.16.4] - 2021-10-21 ### Added diff --git a/ice_cube.gemspec b/ice_cube.gemspec index 5191c6fa..a2016902 100644 --- a/ice_cube.gemspec +++ b/ice_cube.gemspec @@ -8,16 +8,15 @@ Gem::Specification.new do |s| s.description = "ice_cube is a recurring date library for Ruby. It allows for quick, programatic expansion of recurring date rules." s.author = "John Crepezzi" s.email = "john@crepezzi.com" - s.homepage = "https://seejohnrun.github.io/ice_cube/" + s.homepage = "https://ice-cube-ruby.github.io/ice_cube/" s.license = "MIT" - s.metadata["changelog_uri"] = "https://github.com/seejohnrun/ice_cube/blob/master/CHANGELOG.md" - s.metadata["wiki_uri"] = "https://github.com/seejohnrun/ice_cube/wiki" + s.metadata["changelog_uri"] = "https://github.com/ice-cube-ruby/ice_cube/blob/master/CHANGELOG.md" + s.metadata["wiki_uri"] = "https://github.com/ice-cube-ruby/ice_cube/wiki" 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 3c283f53..eb301660 100644 --- a/lib/ice_cube.rb +++ b/lib/ice_cube.rb @@ -6,7 +6,8 @@ module IceCube autoload :TimeUtil, "ice_cube/time_util" autoload :FlexibleHash, "ice_cube/flexible_hash" - autoload :I18n, "ice_cube/i18n" + + require "ice_cube/i18n" autoload :Rule, "ice_cube/rule" autoload :Schedule, "ice_cube/schedule" 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/flexible_hash.rb b/lib/ice_cube/flexible_hash.rb index f23bb8ce..64edbfc0 100644 --- a/lib/ice_cube/flexible_hash.rb +++ b/lib/ice_cube/flexible_hash.rb @@ -25,9 +25,9 @@ def delete(key) def _match_key(key) return key if __getobj__.has_key? key - if Symbol == key.class + if key.instance_of?(Symbol) __getobj__.keys.detect { |k| return k if k == key.to_s } - elsif String == key.class + elsif key.instance_of?(String) __getobj__.keys.detect { |k| return k if k.to_s == key } end key diff --git a/lib/ice_cube/i18n.rb b/lib/ice_cube/i18n.rb index 6f135026..274a73b6 100644 --- a/lib/ice_cube/i18n.rb +++ b/lib/ice_cube/i18n.rb @@ -1,26 +1,15 @@ require "ice_cube/null_i18n" module IceCube - module I18n - LOCALES_PATH = File.expand_path(File.join("..", "..", "..", "config", "locales"), __FILE__) - - def self.t(*args, **kwargs) - backend.t(*args, **kwargs) - end - - def self.l(*args, **kwargs) - backend.l(*args, **kwargs) - end - - def self.backend - @backend ||= detect_backend! - end - - def self.detect_backend! - ::I18n.load_path += Dir[File.join(LOCALES_PATH, "*.yml")] - ::I18n - rescue NameError - NullI18n - end + LOCALES_PATH = File.expand_path(File.join("..", "..", "config", "locales"), __dir__) + + # rubocop:disable Naming/ConstantName + I18n = begin + require "i18n" + ::I18n.load_path += Dir[File.join(LOCALES_PATH, "*.yml")] + ::I18n + rescue LoadError + NullI18n end + # rubocop:enable Naming/ConstantName end diff --git a/lib/ice_cube/input_alignment.rb b/lib/ice_cube/input_alignment.rb index 8900fa15..2cb14c1b 100644 --- a/lib/ice_cube/input_alignment.rb +++ b/lib/ice_cube/input_alignment.rb @@ -8,16 +8,16 @@ def initialize(rule, value, rule_part) attr_reader :rule, :value, :rule_part - def verify(freq, options = {}, &block) + def verify(freq, options = {}, &) @rule.validations[:interval] or return case @rule when DailyRule - verify_wday_alignment(freq, &block) + verify_wday_alignment(freq, &) when MonthlyRule - verify_month_alignment(freq, &block) + verify_month_alignment(freq, &) else - verify_freq_alignment(freq, &block) + verify_freq_alignment(freq, &) end end @@ -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/rule.rb b/lib/ice_cube/rule.rb index 6652c3db..30689267 100644 --- a/lib/ice_cube/rule.rb +++ b/lib/ice_cube/rule.rb @@ -73,10 +73,7 @@ def from_hash(original_hash) rule = IceCube::Rule.send(interval_type, hash[:interval] || 1) - if match[1] == "Weekly" - rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0)) - end - + rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0)) if rule.is_a? WeeklyRule rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until] rule.count(hash[:count]) if hash[:count] diff --git a/lib/ice_cube/rules/weekly_rule.rb b/lib/ice_cube/rules/weekly_rule.rb index e24f77f9..fd2ffd72 100644 --- a/lib/ice_cube/rules/weekly_rule.rb +++ b/lib/ice_cube/rules/weekly_rule.rb @@ -35,7 +35,7 @@ def realign(step_time, start_time) time = TimeUtil::TimeWrapper.new(start_time) offset = wday_offset(step_time, start_time) time.add(:day, offset) - super step_time, time.to_time + super(step_time, time.to_time) end # Calculate how many days to the first wday validation in the correct diff --git a/lib/ice_cube/schedule.rb b/lib/ice_cube/schedule.rb index 68f01cc9..06b8e4dd 100644 --- a/lib/ice_cube/schedule.rb +++ b/lib/ice_cube/schedule.rb @@ -160,8 +160,8 @@ def all_occurrences_enumerator end # Iterate forever - def each_occurrence(&block) - enumerate_occurrences(start_time, &block).to_a + def each_occurrence(&) + enumerate_occurrences(start_time, &).to_a self end @@ -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 001d927d..a18f7758 100644 --- a/lib/ice_cube/time_util.rb +++ b/lib/ice_cube/time_util.rb @@ -51,7 +51,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 @@ -286,12 +286,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 @@ -318,20 +318,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..9fb9f1cd 100644 --- a/lib/ice_cube/validations/day_of_week.rb +++ b/lib/ice_cube/validations/day_of_week.rb @@ -15,6 +15,9 @@ class Validation attr_reader :day, :occ def initialize(day, occ) + raise ArgumentError, "Integer occurrence value required" unless occ.is_a?(Integer) + raise ArgumentError, "Invalid day_of_week occurrence: #{occ.inspect}" if occ.zero? || occ.abs > 5 + @day = day @occ = occ end @@ -29,12 +32,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..c06a7a95 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,16 +73,16 @@ 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) start = value || start_time.send(type) - start = start % INTERVALS[type] if start < 0 + start %= INTERVALS[type] if start < 0 start end end diff --git a/lib/ice_cube/validations/hour_of_day.rb b/lib/ice_cube/validations/hour_of_day.rb index e0a9c3be..f32f9f6f 100644 --- a/lib/ice_cube/validations/hour_of_day.rb +++ b/lib/ice_cube/validations/hour_of_day.rb @@ -28,7 +28,7 @@ def realign(opening_time, start_time) time.hour = first_hour.value end - super opening_time, time.to_time + super(opening_time, time.to_time) end class Validation < Validations::FixedValue 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/minute_of_hour.rb b/lib/ice_cube/validations/minute_of_hour.rb index f3f82448..4daebec5 100644 --- a/lib/ice_cube/validations/minute_of_hour.rb +++ b/lib/ice_cube/validations/minute_of_hour.rb @@ -20,7 +20,7 @@ def realign(opening_time, start_time) first_minute = validations[:minute_of_hour].min_by(&:value) time = TimeUtil::TimeWrapper.new(start_time, false) time.min = first_minute.value - super opening_time, time.to_time + super(opening_time, time.to_time) end class Validation < Validations::FixedValue diff --git a/lib/ice_cube/validations/second_of_minute.rb b/lib/ice_cube/validations/second_of_minute.rb index dea0b35b..dd79f2c7 100644 --- a/lib/ice_cube/validations/second_of_minute.rb +++ b/lib/ice_cube/validations/second_of_minute.rb @@ -20,7 +20,7 @@ def realign(opening_time, start_time) first_second = Array(validations[:second_of_minute]).min_by(&:value) time = TimeUtil::TimeWrapper.new(start_time, false) time.sec = first_second.value - super opening_time, time.to_time + super(opening_time, time.to_time) end class Validation < Validations::FixedValue diff --git a/spec/examples/from_hash_spec.rb b/spec/examples/from_hash_spec.rb new file mode 100644 index 00000000..7ef09632 --- /dev/null +++ b/spec/examples/from_hash_spec.rb @@ -0,0 +1,129 @@ +require File.dirname(__FILE__) + "/../spec_helper" + +module IceCube + describe Rule, "from_hash" do + describe "rule_type validations" do + it "should raise an ArgumentError when the hash is empty" do + expect { Rule.from_hash({}) } + .to raise_error ArgumentError, "Invalid rule type" + end + + it "should raise an ArgumentError when the hash[:rule_type] is invalid" do + expect { Rule.from_hash({rule_type: "IceCube::MadeUpIntervalRule"}) } + .to raise_error ArgumentError, "Invalid rule frequency type: MadeUpInterval" + end + + it "returns a SecondlyRule when the hash[:rule_type] is secondly" do + expect(Rule.from_hash({rule_type: "IceCube::SecondlyRule"})).to be_a SecondlyRule + end + + it "returns a MinutelyRule when the hash[:rule_type] is minutely" do + expect(Rule.from_hash({rule_type: "IceCube::MinutelyRule"})).to be_a MinutelyRule + end + + it "returns a HourlyRule when the hash[:rule_type] is hourly" do + expect(Rule.from_hash({rule_type: "IceCube::HourlyRule"})).to be_a HourlyRule + end + + it "returns a DailyRule when the hash[:rule_type] is daily" do + expect(Rule.from_hash({rule_type: "IceCube::DailyRule"})).to be_a DailyRule + end + + it "returns a WeeklyRule when the hash[:rule_type] is weekly" do + expect(Rule.from_hash({rule_type: "IceCube::WeeklyRule"})).to be_a WeeklyRule + end + + it "returns a MonthlyRule when the hash[:rule_type] is monthly" do + expect(Rule.from_hash({rule_type: "IceCube::MonthlyRule"})).to be_a MonthlyRule + end + + it "returns a YearlyRule when the hash[:rule_type] is yearly" do + expect(Rule.from_hash({rule_type: "IceCube::YearlyRule"})).to be_a YearlyRule + end + end + + describe "creating monthly rule" do + context "with valid day_of_week validations" do + let(:input_hash) { + { + rule_type: "IceCube::MonthlyRule", + interval: 1, + validations: { + day_of_week: { + "0": [2], + "1": [2], + "2": [2], + "3": [2], + "4": [2], + "5": [2], + "6": [1, 2] + }, + hour_of_day: 7, + minute_of_hour: 19 + } + } + } + + it "can provide the first occurrence" do + rule = Rule.from_hash(input_hash) + schedule = Schedule.new(Time.utc(2010, 1, 1, 0, 0, 0)) + schedule.add_recurrence_rule rule + expect(schedule.first(10).map(&:to_time)).to eq([ + Time.utc(2010, 1, 2, 7, 19, 0), + Time.utc(2010, 1, 8, 7, 19, 0), + Time.utc(2010, 1, 9, 7, 19, 0), + Time.utc(2010, 1, 10, 7, 19, 0), + Time.utc(2010, 1, 11, 7, 19, 0), + Time.utc(2010, 1, 12, 7, 19, 0), + Time.utc(2010, 1, 13, 7, 19, 0), + Time.utc(2010, 1, 14, 7, 19, 0), + Time.utc(2010, 2, 6, 7, 19, 0), + Time.utc(2010, 2, 8, 7, 19, 0) + ]) + end + end + + context "with invalid day_of_week validations" do + let(:input_hash_with_zeroeth_occurrence) { + { + rule_type: "IceCube::MonthlyRule", + interval: 1, + validations: { + day_of_week: { + "1": [], + "2": [0], + "3": [], + "4": [] + }, + hour_of_day: 7, + minute_of_hour: 19 + } + } + } + let(:input_hash_with_sixth_occurrence) { + { + rule_type: "IceCube::MonthlyRule", + interval: 1, + validations: { + day_of_week: { + "1": [], + "2": [6], + "3": [], + "4": [] + }, + hour_of_day: 7, + minute_of_hour: 19 + } + } + } + + it "should raise an ArgumentError" do + expect { Rule.from_hash(input_hash_with_zeroeth_occurrence) } + .to raise_error ArgumentError, "Invalid day_of_week occurrence: 0" + expect { Rule.from_hash(input_hash_with_sixth_occurrence) } + .to raise_error ArgumentError, "Invalid day_of_week occurrence: 6" + end + end + end + end +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/occurrence_spec.rb b/spec/examples/occurrence_spec.rb index 80c18421..943c5ebb 100644 --- a/spec/examples/occurrence_spec.rb +++ b/spec/examples/occurrence_spec.rb @@ -28,7 +28,12 @@ time_now = Time.current occurrence = Occurrence.new(time_now) - expect(occurrence.to_s(:short)).to eq time_now.to_s(:short) + # From Rails 7.1 onwards, support for format options was removed + if time_now.public_method(:to_s).arity != 0 + expect(occurrence.to_s(:short)).to eq time_now.to_s(:short) + else + expect(occurrence.to_s(:short)).to eq time_now.to_s + end end end diff --git a/spec/examples/time_util_spec.rb b/spec/examples/time_util_spec.rb index 43491a8b..c6a434d6 100644 --- a/spec/examples/time_util_spec.rb +++ b/spec/examples/time_util_spec.rb @@ -42,32 +42,38 @@ module IceCube end describe :wday_to_sym do - it "converts 0..6 to weekday symbols" do - expect(TimeUtil.wday_to_sym(1)).to eq(:monday) - end + [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday].each_with_index do |sym, i| + it "converts #{i} to weekday symbol :#{sym}" do + expect(TimeUtil.wday_to_sym(i)).to eq(sym) + end - it "returns weekday symbols as is" do - expect(TimeUtil.wday_to_sym(:monday)).to eq(:monday) + it "returns :#{sym} when passed :#{sym}" do + expect(TimeUtil.wday_to_sym(sym)).to eq(sym) + end end it "raises an error for bad input" do expect { TimeUtil.wday_to_sym(:anyday) }.to raise_error(ArgumentError) - expect { TimeUtil.wday_to_sym(17) }.to raise_error(ArgumentError) + expect { TimeUtil.wday_to_sym(8) }.to raise_error(ArgumentError) + expect { TimeUtil.wday_to_sym(-1) }.to raise_error(ArgumentError) end end describe :sym_to_wday do - it "converts weekday symbols to 0..6 wday numbers" do - expect(TimeUtil.sym_to_wday(:monday)).to eq(1) - end + [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday].each_with_index do |sym, i| + it "converts :#{sym} to integer #{i}" do + expect(TimeUtil.sym_to_wday(sym)).to eq(i) + end - it "returns wday numbers as is" do - expect(TimeUtil.sym_to_wday(1)).to eq(1) + it "returns #{i} when passed #{i}" do + expect(TimeUtil.sym_to_wday(i)).to eq(i) + end end it "raises an error for bad input" do expect { TimeUtil.sym_to_wday(:anyday) }.to raise_error(ArgumentError) - expect { TimeUtil.sym_to_wday(17) }.to raise_error(ArgumentError) + expect { TimeUtil.sym_to_wday(-1) }.to raise_error(ArgumentError) + expect { TimeUtil.sym_to_wday(8) }.to raise_error(ArgumentError) end end diff --git a/spec/examples/to_s_de_spec.rb b/spec/i18n/to_s_de_spec.rb similarity index 100% rename from spec/examples/to_s_de_spec.rb rename to spec/i18n/to_s_de_spec.rb diff --git a/spec/examples/to_s_en_spec.rb b/spec/i18n/to_s_en_spec.rb similarity index 99% rename from spec/examples/to_s_en_spec.rb rename to spec/i18n/to_s_en_spec.rb index c3e38a86..8be73cd1 100644 --- a/spec/examples/to_s_en_spec.rb +++ b/spec/i18n/to_s_en_spec.rb @@ -212,7 +212,7 @@ before(:each) { I18n.locale = :en } it "uses I18n" do - expect(IceCube::I18n.backend).to eq(I18n) + expect(IceCube::I18n).to eq ::I18n end it_behaves_like "to_s in English" diff --git a/spec/examples/to_s_es_spec.rb b/spec/i18n/to_s_es_spec.rb similarity index 100% rename from spec/examples/to_s_es_spec.rb rename to spec/i18n/to_s_es_spec.rb diff --git a/spec/examples/to_s_id_spec.rb b/spec/i18n/to_s_id_spec.rb similarity index 100% rename from spec/examples/to_s_id_spec.rb rename to spec/i18n/to_s_id_spec.rb diff --git a/spec/examples/to_s_ja_spec.rb b/spec/i18n/to_s_ja_spec.rb similarity index 100% rename from spec/examples/to_s_ja_spec.rb rename to spec/i18n/to_s_ja_spec.rb diff --git a/spec/examples/daily_rule_spec.rb b/spec/rules/daily_rule_spec.rb similarity index 100% rename from spec/examples/daily_rule_spec.rb rename to spec/rules/daily_rule_spec.rb diff --git a/spec/examples/hourly_rule_spec.rb b/spec/rules/hourly_rule_spec.rb similarity index 100% rename from spec/examples/hourly_rule_spec.rb rename to spec/rules/hourly_rule_spec.rb diff --git a/spec/examples/minutely_rule_spec.rb b/spec/rules/minutely_rule_spec.rb similarity index 100% rename from spec/examples/minutely_rule_spec.rb rename to spec/rules/minutely_rule_spec.rb diff --git a/spec/examples/monthly_rule_spec.rb b/spec/rules/monthly_rule_spec.rb similarity index 100% rename from spec/examples/monthly_rule_spec.rb rename to spec/rules/monthly_rule_spec.rb diff --git a/spec/examples/secondly_rule_spec.rb b/spec/rules/secondly_rule_spec.rb similarity index 100% rename from spec/examples/secondly_rule_spec.rb rename to spec/rules/secondly_rule_spec.rb diff --git a/spec/examples/weekly_rule_spec.rb b/spec/rules/weekly_rule_spec.rb similarity index 99% rename from spec/examples/weekly_rule_spec.rb rename to spec/rules/weekly_rule_spec.rb index 3e8e5719..c59de2d2 100644 --- a/spec/examples/weekly_rule_spec.rb +++ b/spec/rules/weekly_rule_spec.rb @@ -96,7 +96,7 @@ module IceCube it "should raise an error on invalid input" do schedule = Schedule.new(WEDNESDAY) - expect { schedule.add_recurrence_rule Rule.weekly.day(["1", "3"]) }.to raise_error(ArgumentError) + expect { schedule.add_recurrence_rule Rule.weekly.day(["1", "3"]) }.to raise_error(ArgumentError, "expecting Integer or Symbol value for day, got \"1\"") end it "should ignore weekday validation when no days are specified" do diff --git a/spec/examples/yearly_rule_spec.rb b/spec/rules/yearly_rule_spec.rb similarity index 96% rename from spec/examples/yearly_rule_spec.rb rename to spec/rules/yearly_rule_spec.rb index 1e3462ef..c7869917 100644 --- a/spec/examples/yearly_rule_spec.rb +++ b/spec/rules/yearly_rule_spec.rb @@ -38,7 +38,7 @@ schedule.add_recurrence_rule IceCube::Rule.yearly.month_of_year(:april).day_of_week(monday: [1, -1]) one_year = 365 * IceCube::ONE_DAY - expect(schedule.occurrences(start_time + one_year).size).to eq(2) + expect(schedule.occurrences(start_time + one_year)).to eq [Time.local(2011, 4, 4, 5, 0, 0), Time.local(2011, 4, 25, 5, 0, 0)] end it "should produce the correct number of days for @interval = 1" do