diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a94ecd7a..b74ee513 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,11 +42,17 @@ jobs: - lint strategy: matrix: - rails: ['5.2', '6.0.4', '6.1'] - ruby: ['2.6', '2.7', '3.0'] + 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' runs-on: ubuntu-latest env: RAILS_VERSION: ${{ matrix.rails }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b7d911a0..c0022b92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Indonesian translations. ([#505](https://github.com/seejohnrun/ice_cube/pull/505)) by [@achmiral](https://github.com/achmiral) +### 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) + ### 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) ## [0.16.4] - 2021-10-21 ### Added diff --git a/README.md b/README.md index 34c51ab0..db290578 100644 --- a/README.md +++ b/README.md @@ -91,17 +91,17 @@ schedule.previous_occurrence(from_time) schedule.previous_occurrences(4, from_time) # or include prior occurrences with a duration overlapping from_time -schedule.next_occurrences(4, from_time, :spans => true) -schedule.occurrences_between(from_time, to_time, :spans => true) +schedule.next_occurrences(4, from_time, spans: true) +schedule.occurrences_between(from_time, to_time, spans: true) # or give the schedule a duration and ask if occurring_at? -schedule = IceCube::Schedule.new(now, :duration => 3600) +schedule = IceCube::Schedule.new(now, duration: 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.occurring_at?(now + 1800) # true schedule.occurring_between?(t1, t2) # using end_time also sets the duration -schedule = IceCube::Schedule.new(start = Time.now, :end_time => start + 3600) +schedule = IceCube::Schedule.new(start = Time.now, end_time: start + 3600) schedule.add_recurrence_rule IceCube::Rule.daily schedule.occurring_at?(start + 3599) # true schedule.occurring_at?(start + 3600) # false @@ -162,7 +162,7 @@ ice_cube can provide ical or string representations of individual rules, or the whole schedule. ```ruby -rule = IceCube::Rule.daily(2).day_of_week(:tuesday => [1, -1], :wednesday => [2]) +rule = IceCube::Rule.daily(2).day_of_week(tuesday: [1, -1], wednesday: [2]) rule.to_ical # 'FREQ=DAILY;INTERVAL=2;BYDAY=1TU,-1TU,2WE' @@ -218,12 +218,12 @@ month (e.g. no occurrences in February for `day_of_month(31)`). ```ruby # every month on the first and last tuesdays of the month -schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_week(:tuesday => [1, -1]) +schedule.add_recurrence_rule IceCube::Rule.monthly.day_of_week(tuesday: [1, -1]) # every other month on the first monday and last tuesday schedule.add_recurrence_rule IceCube::Rule.monthly(2).day_of_week( - :monday => [1], - :tuesday => [-1] + monday: [1], + tuesday: [-1] ) # for programmatic convenience (same as above) @@ -270,7 +270,7 @@ schedule.add_recurrence_rule IceCube::Rule.hourly(2).day(:monday) schedule.add_recurrence_rule IceCube::Rule.minutely(10) # every hour and a half, on the last tuesday of the month -schedule.add_recurrence_rule IceCube::Rule.minutely(90).day_of_week(:tuesday => [-1]) +schedule.add_recurrence_rule IceCube::Rule.minutely(90).day_of_week(tuesday: [-1]) ``` ### Secondly (every N seconds) @@ -298,10 +298,7 @@ https://github.com/GetJobber/recurring_select ## Contributors -* Andrew Vit ([@avit][github-avit]) -* Mat Brown - mat@patch.com -* Philip Roberts -* @sakrafd +https://github.com/seejohnrun/ice_cube/graphs/contributors --- diff --git a/ice_cube.gemspec b/ice_cube.gemspec index d4c8f947..5191c6fa 100644 --- a/ice_cube.gemspec +++ b/ice_cube.gemspec @@ -8,9 +8,12 @@ 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 = "http://seejohnrun.github.com/ice_cube/" + s.homepage = "https://seejohnrun.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.version = IceCube::VERSION s.platform = Gem::Platform::RUBY s.files = Dir["lib/**/*.rb", "config/**/*.yml"] diff --git a/lib/ice_cube/i18n.rb b/lib/ice_cube/i18n.rb index aa879a4f..45a7f4a2 100644 --- a/lib/ice_cube/i18n.rb +++ b/lib/ice_cube/i18n.rb @@ -4,8 +4,12 @@ module IceCube module I18n LOCALES_PATH = File.expand_path(File.join("..", "..", "..", "config", "locales"), __FILE__) - class << self - delegate :t, :l, to: :backend + def self.t(*args, **kwargs) + backend.t(*args, **kwargs) + end + + def self.l(*args, **kwargs) + backend.l(*args, **kwargs) end def self.backend diff --git a/lib/ice_cube/parsers/hash_parser.rb b/lib/ice_cube/parsers/hash_parser.rb index abe7f6a6..d0d6713b 100644 --- a/lib/ice_cube/parsers/hash_parser.rb +++ b/lib/ice_cube/parsers/hash_parser.rb @@ -60,7 +60,6 @@ def apply_rrules(schedule, data) def apply_exrules(schedule, data) return unless data[:exrules] - warn "IceCube: :exrules is deprecated, and will be removed in a future release. at: #{caller(1..1).first}" data[:exrules].each do |h| rrule = h.is_a?(IceCube::Rule) ? h : IceCube::Rule.from_hash(h) diff --git a/lib/ice_cube/parsers/yaml_parser.rb b/lib/ice_cube/parsers/yaml_parser.rb index 9810b797..2b979811 100644 --- a/lib/ice_cube/parsers/yaml_parser.rb +++ b/lib/ice_cube/parsers/yaml_parser.rb @@ -7,7 +7,7 @@ class YamlParser < HashParser attr_reader :hash def initialize(yaml) - @hash = YAML.load(yaml) + @hash = YAML.safe_load(yaml, permitted_classes: [Date, Symbol, Time], aliases: true) yaml.match SERIALIZED_START do |match| start_time = hash[:start_time] || hash[:start_date] TimeUtil.restore_deserialized_offset start_time, match[:tz] diff --git a/lib/ice_cube/rule.rb b/lib/ice_cube/rule.rb index 726ce17f..6652c3db 100644 --- a/lib/ice_cube/rule.rb +++ b/lib/ice_cube/rule.rb @@ -42,7 +42,7 @@ def to_yaml(*args) # From yaml def self.from_yaml(yaml) - from_hash YAML.load(yaml) + from_hash YAML.safe_load(yaml, permitted_classes: [Date, Symbol, Time]) end def to_hash diff --git a/lib/ice_cube/schedule.rb b/lib/ice_cube/schedule.rb index 5fbb9530..68f01cc9 100644 --- a/lib/ice_cube/schedule.rb +++ b/lib/ice_cube/schedule.rb @@ -358,9 +358,7 @@ def to_hash data[:start_date] = data[:start_time] if IceCube.compatibility <= 11 data[:end_time] = TimeUtil.serialize_time(end_time) if end_time data[:rrules] = recurrence_rules.map(&:to_hash) - if IceCube.compatibility <= 11 && exception_rules.any? - data[:exrules] = exception_rules.map(&:to_hash) - end + data[:exrules] = exception_rules.map(&:to_hash) if exception_rules.any? data[:rtimes] = recurrence_times.map do |rt| TimeUtil.serialize_time(rt) end diff --git a/lib/ice_cube/validations/hour_of_day.rb b/lib/ice_cube/validations/hour_of_day.rb index 64369e53..e0a9c3be 100644 --- a/lib/ice_cube/validations/hour_of_day.rb +++ b/lib/ice_cube/validations/hour_of_day.rb @@ -20,7 +20,7 @@ def realign(opening_time, start_time) freq = base_interval_validation.interval first_hour = Array(validations[:hour_of_day]).min_by(&:value) - time = TimeUtil::TimeWrapper.new(start_time, false) + time = TimeUtil::TimeWrapper.new(start_time, true) if freq > 1 && base_interval_validation.type == :hour offset = first_hour.validate(opening_time, start_time) time.add(:hour, offset - freq) diff --git a/spec/examples/active_support_spec.rb b/spec/examples/active_support_spec.rb index a07a9081..51973415 100644 --- a/spec/examples/active_support_spec.rb +++ b/spec/examples/active_support_spec.rb @@ -1,4 +1,5 @@ require File.dirname(__FILE__) + "/../spec_helper" +require "active_support" require "active_support/time" require "active_support/version" require "tzinfo" if ActiveSupport::VERSION::MAJOR == 3 @@ -45,10 +46,10 @@ module IceCube end it "can round trip TimeWithZone to YAML" do - schedule = Schedule.new(t0 = Time.zone.parse("2010-02-05 05:00:00")) - schedule.add_recurrence_time t0 - schedule2 = Schedule.from_yaml(schedule.to_yaml) - expect(schedule.all_occurrences).to eq(schedule2.all_occurrences) + schedule1 = Schedule.new(t0 = Time.zone.parse("2010-02-05 05:00:00")) + schedule1.add_recurrence_time t0 + schedule2 = Schedule.from_yaml(schedule1.to_yaml) + expect(schedule2.all_occurrences).to eq(schedule1.all_occurrences) end it "uses local zone from start time to determine occurs_on? from the beginning of day" do diff --git a/spec/examples/daily_rule_spec.rb b/spec/examples/daily_rule_spec.rb index 5a4b3f33..53887774 100644 --- a/spec/examples/daily_rule_spec.rb +++ b/spec/examples/daily_rule_spec.rb @@ -56,6 +56,15 @@ module IceCube Time.local(2013, 3, 11, 2, 0, 0) # -0700 ]) end + + it "should not skip days where DST changes" do + start_time = Time.local(2013, 3, 10, 0, 0, 0) + schedule = Schedule.new(start_time) + schedule.add_recurrence_rule Rule.daily.hour_of_day(19) + expect(schedule.occurrences_between(start_time, start_time + ONE_DAY)).to eq([ + Time.local(2013, 3, 10, 19, 0, 0) + ]) + end end it "should update previous interval" do diff --git a/spec/examples/serialization_spec.rb b/spec/examples/serialization_spec.rb index 7886a04a..5583c23b 100644 --- a/spec/examples/serialization_spec.rb +++ b/spec/examples/serialization_spec.rb @@ -15,7 +15,7 @@ let(:start_time) { Time.now.in_time_zone("America/Vancouver") } it "serializes time as a Hash" do - hash = YAML.load(yaml) + hash = YAML.safe_load(yaml, permitted_classes: [Symbol, Time]) expect(hash[:start_time][:time]).to eq start_time.utc expect(hash[:start_time][:zone]).to eq "America/Vancouver" end diff --git a/spec/examples/to_ical_spec.rb b/spec/examples/to_ical_spec.rb index 7ee59236..39dd5209 100644 --- a/spec/examples/to_ical_spec.rb +++ b/spec/examples/to_ical_spec.rb @@ -1,4 +1,5 @@ require File.dirname(__FILE__) + "/../spec_helper" +require "active_support" require "active_support/time" describe IceCube, "to_ical" do diff --git a/spec/examples/to_yaml_spec.rb b/spec/examples/to_yaml_spec.rb index 7842621c..e7c62c59 100644 --- a/spec/examples/to_yaml_spec.rb +++ b/spec/examples/to_yaml_spec.rb @@ -78,14 +78,15 @@ module IceCube end it "should be able to make a round-trip to YAML with .day_of_year" do - schedule = Schedule.new(Time.now) - schedule.add_recurrence_rule Rule.yearly.day_of_year(100, 200) + schedule1 = Schedule.new(Time.now) + schedule1.add_recurrence_rule Rule.yearly.day_of_year(100, 200) - yaml_string = schedule.to_yaml + yaml_string = schedule1.to_yaml schedule2 = Schedule.from_yaml(yaml_string) # compare without usecs - expect(schedule.first(10).map { |r| r.to_s }).to eq(schedule2.first(10).map { |r| r.to_s }) + expect(schedule2.first(10).map { |r| r.to_s }) + .to eq(schedule1.first(10).map { |r| r.to_s }) end it "should be able to make a round-trip to YAML with .hour_of_day" do @@ -132,6 +133,18 @@ module IceCube expect(schedule.first(10).map { |r| r.to_s }).to eq(schedule2.first(10).map { |r| r.to_s }) 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.add_recurrence_rule Rule.daily.day(:monday, :wednesday) + original_schedule.add_exception_rule Rule.daily.day(:wednesday) + + yaml_string = original_schedule.to_yaml + returned_schedule = Schedule.from_yaml(yaml_string) + + # compare without usecs + expect(returned_schedule.first(10).map { |r| r.to_s }).to eq(original_schedule.first(10).map { |r| r.to_s }) + end + it "should have a to_yaml representation of a rule that does not contain ruby objects" do rule = Rule.daily.day_of_week(monday: [1, -1]).month_of_year(:april) expect(rule.to_yaml.include?("object")).to be_falsey @@ -172,7 +185,7 @@ module IceCube schedule2 = Schedule.from_yaml(schedule1.to_yaml) # round trip end_time = Time.now + ONE_DAY - expect(schedule1.occurrences(end_time)).to eq(schedule2.occurrences(end_time)) + expect(schedule2.occurrences(end_time)).to eq(schedule1.occurrences(end_time)) end it "should be able to make a round trip with an exception time" do @@ -311,7 +324,8 @@ module IceCube symbol_yaml = Schedule.from_hash(symbol_data).to_yaml string_yaml = Schedule.from_hash(string_data).to_yaml - expect(YAML.load(symbol_yaml)).to eq(YAML.load(string_yaml)) + expect(YAML.safe_load(symbol_yaml, permitted_classes: [Symbol, Time])) + .to eq(YAML.safe_load(string_yaml, permitted_classes: [Symbol, Time])) end it "should raise an ArgumentError when trying to deserialize an invalid rule type" do