diff --git a/lib/crono.rb b/lib/crono.rb index 81632ca..596f997 100644 --- a/lib/crono.rb +++ b/lib/crono.rb @@ -6,6 +6,8 @@ require 'active_support/all' require 'crono/version' require 'crono/logging' require 'crono/period' +require 'crono/time_of_day' +require 'crono/interval' require 'crono/job' require 'crono/scheduler' require 'crono/config' diff --git a/lib/crono/interval.rb b/lib/crono/interval.rb new file mode 100644 index 0000000..b534daf --- /dev/null +++ b/lib/crono/interval.rb @@ -0,0 +1,43 @@ +module Crono + # Interval describes a period between two specific times of day + class Interval + attr_accessor :from, :to + + def self.parse(value) + from_to = + case value + when Array then value + when Hash then value.values_at(:from, :to) + when String then value.split('-') + else + fail "Unknown interval format: #{value.inspect}" + end + from, to = from_to.map { |v| TimeOfDay.parse(v) } + new from, to + end + + def initialize(from, to) + @from, @to = from, to + end + + def within?(value) + tod = ((value.is_a? TimeOfDay) ? value : TimeOfDay.parse(value)) + if @from <= @to + tod >= @from && tod < @to + else + tod >= @from || tod < @to + end + end + + def next_within(time, period) + begin + time = period.since(time) + end until within? TimeOfDay.parse(time) + time + end + + def to_s + "#{@from}-#{@to}" + end + end +end diff --git a/lib/crono/period.rb b/lib/crono/period.rb index 92b7d9e..3c7a436 100644 --- a/lib/crono/period.rb +++ b/lib/crono/period.rb @@ -4,15 +4,25 @@ module Crono DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday] - def initialize(period, at: nil, on: nil) + def initialize(period, at: nil, on: nil, within: nil) @period = period @at_hour, @at_min = parse_at(at) if at + @interval = Interval.parse(within) if within @on = parse_on(on) if on end def next(since: nil) - return initial_next unless since - @next = @period.since(since) + if @interval + if since + @next = @interval.next_within(since, @period) + else + return initial_next if @interval.within?(initial_next) + @next = @interval.next_within(initial_next, @period) + end + else + return initial_next unless since + @next = @period.since(since) + end @next = @next.beginning_of_week.advance(days: @on) if @on @next = @next.change(time_atts) return @next if @next.future? @@ -21,6 +31,7 @@ module Crono def description desc = "every #{@period.inspect}" + desc += " between #{@interval.from} and #{@interval.to} UTC" if @interval desc += format(' at %.2i:%.2i', @at_hour, @at_min) if @at_hour && @at_min desc += " on #{DAYS[@on].capitalize}" if @on desc diff --git a/lib/crono/time_of_day.rb b/lib/crono/time_of_day.rb new file mode 100644 index 0000000..63c541d --- /dev/null +++ b/lib/crono/time_of_day.rb @@ -0,0 +1,36 @@ +module Crono + # TimeOfDay describes a certain hour and minute (on any day) + class TimeOfDay + include Comparable + + attr_accessor :hour, :min + + def self.parse(value) + time = + case value + when String then Time.parse(value).utc + when Hash then Time.now.change(value).utc + when Time then value.utc + else + fail "Unknown TimeOfDay format: #{value.inspect}" + end + new time.hour, time.min + end + + def initialize(hour, min) + @hour, @min = hour, min + end + + def to_i + @hour * 60 + @min + end + + def to_s + '%02d:%02d' % [@hour, @min] + end + + def <=>(other) + to_i <=> other.to_i + end + end +end diff --git a/spec/period_spec.rb b/spec/period_spec.rb index 688238c..ea3e7dd 100644 --- a/spec/period_spec.rb +++ b/spec/period_spec.rb @@ -110,6 +110,17 @@ describe Crono::Period do expect(@period.next.utc.to_s).to be_eql Time.now.beginning_of_hour.advance(minutes: 15).utc.to_s end end + + it 'should return next hour minutes within the given interval' do + Timecop.freeze(Time.now.change(hour: 16, min: 10)) do + @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '08:00-16:00') + expect(@period.next.utc.to_s).to be_eql Time.now.tomorrow.change(hour: 8, min: 15).utc.to_s + end + Timecop.freeze(Time.now.change(hour: 16, min: 10)) do + @period = Crono::Period.new(1.hour, at: { min: 15 }, within: '23:00-07:00') + expect(@period.next.utc.to_s).to be_eql Time.now.change(hour: 23, min: 15).utc.to_s + end + end end end end