lib/fugit/cron.rb in fugit-1.1.8 vs lib/fugit/cron.rb in fugit-1.1.9

- old
+ new

@@ -62,11 +62,12 @@ end end class TimeCursor - def initialize(t) + def initialize(cron, t) + @cron = cron @t = t.is_a?(TimeCursor) ? t.time : t @t.seconds = @t.seconds.to_i end def time; @t; end @@ -86,33 +87,54 @@ m = @t.month + 1 if m == 13; m = 1; y += 1; end @t = ::EtOrbi.make(y, m, @t.zone) self end - def inc_day; inc((24 - @t.hour) * 3600 - @t.min * 60 - @t.sec); end - def inc_hour; inc((60 - @t.min) * 60 - @t.sec); end - def inc_min; inc(60 - @t.sec); end - def inc_sec(seconds) - if sec = seconds.find { |s| s > @t.sec } + def inc_day + inc((24 - @t.hour) * 3600 - @t.min * 60 - @t.sec) + end + def inc_hour + inc((60 - @t.min) * 60 - @t.sec) + end + def inc_min + inc(60 - @t.sec) + end + + def inc_sec + if sec = @cron.seconds.find { |s| s > @t.sec } inc(sec - @t.sec) else - inc(60 - @t.sec + seconds.first) + inc(60 - @t.sec + @cron.seconds.first) end end def dec_month - dec(@t.day * 24 * 3600 + @t.hour * 3600 + @t.min * 60 + @t.sec + 1) + + #dec(@t.day * 24 * 3600 + @t.hour * 3600 + @t.min * 60 + @t.sec + 1) + # + # gh-18, so that '0 9 29 feb *' doesn't get skipped (over and over) + # + dec(@t.day * 24 * 3600 + 1) end - def dec_day; dec(@t.hour * 3600 + @t.min * 60 + @t.sec + 1); end - def dec_hour; dec(@t.min * 60 + @t.sec + 1); end - def dec_min; dec(@t.sec + 1); end - def dec_sec(seconds) - target = seconds.reverse.find { |s| s < @t.sec } || seconds.last - inc(target - @t.sec) + def dec_day + dec(@t.hour * 3600 + @t.min * 60 + @t.sec + 1) end + def dec_hour + dec(@t.min * 60 + @t.sec + 1) + end + def dec_min + dec(@t.sec + 1) + end + + def dec_sec + target = + @cron.seconds.reverse.find { |s| s < @t.sec } || + @cron.seconds.last + inc(target - @t.sec - (@t.sec > target ? 0 : 60)) + end end def month_match?(nt); ( ! @months) || @months.include?(nt.month); end def hour_match?(nt); ( ! @hours) || @hours.include?(nt.hour); end def min_match?(nt); ( ! @minutes) || @minutes.include?(nt.min); end @@ -138,11 +160,11 @@ def monthday_match?(nt) return true if @monthdays.nil? - last = (TimeCursor.new(nt).inc_month.time - 24 * 3600).day + 1 + last = (TimeCursor.new(self, nt).inc_month.time - 24 * 3600).day + 1 @monthdays .collect { |d| d < 1 ? last + d : d } .include?(nt.day) end @@ -164,51 +186,47 @@ month_match?(t) && day_match?(t) && hour_match?(t) && min_match?(t) && sec_match?(t) end - BREAKER_S = 41 * (365 + 1) * 24 * 3600 - # 41 years and a few days... there wont'be a next or a previous time + MAX_ITERATION_COUNT = 2048 + # + # See gh-15 and tst/iteration_count.rb + # + # Initially set to 1024 after seeing the worst case for #next_time + # at 167 iterations, I placed it at 2048 after experimenting with + # gh-18 and noticing some > 1024 for some experiments. 2048 should + # be ok. def next_time(from=::EtOrbi::EoTime.now) from = ::EtOrbi.make_time(from) - sfrom = from.strftime('%F/%T') + sfrom = from.strftime('%F|%T') ifrom = from.to_i - t = TimeCursor.new(from.translate(@timezone)) + i = 0 + t = TimeCursor.new(self, from.translate(@timezone)) # # the translation occurs in the timezone of # this Fugit::Cron instance - ti = 0 - stalling = false - loop do - ti1 = t.to_i - fail RuntimeError.new( - "loop stalled for #{@original.inspect} #next_time, breaking" - ) if stalling && ti == ti1 + "too many loops for #{@original.inspect} #next_time, breaking, " + + "please fill an issue at https://git.io/fjJC9" + ) if (i += 1) > MAX_ITERATION_COUNT - stalling = (ti == ti1) - ti = ti1 - - fail RuntimeError.new( - "too many loops for #{@original.inspect} #next_time, breaking" - ) if (ti - ifrom).abs > BREAKER_S - - (ifrom == ti) && (t.inc(1); next) + (ifrom == t.to_i) && (t.inc(1); next) month_match?(t) || (t.inc_month; next) day_match?(t) || (t.inc_day; next) hour_match?(t) || (t.inc_hour; next) min_match?(t) || (t.inc_min; next) - sec_match?(t) || (t.inc_sec(@seconds); next) + sec_match?(t) || (t.inc_sec; next) - st = t.time.strftime('%F/%T') - (from, sfrom, ifrom = t.time, st, t.time.to_i; next) if st == sfrom + st = t.time.strftime('%F|%T') + (from, sfrom, ifrom = t.time, st, t.to_i; next) if st == sfrom # # when transitioning out of DST, this prevents #next_time from # yielding the same literal time twice in a row, see gh-6 break @@ -221,36 +239,25 @@ end def previous_time(from=::EtOrbi::EoTime.now) from = ::EtOrbi.make_time(from) - ti = 0 - ifrom = from.to_i - t = TimeCursor.new(from.translate(@timezone)) - stalling = false + i = 0 + t = TimeCursor.new(self, (from - 1).translate(@timezone)) loop do - ti1 = t.to_i - fail RuntimeError.new( - "loop stalled for #{@original.inspect} #previous_time, breaking" - ) if stalling && ti == ti1 + "too many loops for #{@original.inspect} #previous_time, breaking, " + + "please fill an issue at https://git.io/fjJCQ" + ) if (i += 1) > MAX_ITERATION_COUNT - stalling = (ti == ti1) - ti = ti1 - - fail RuntimeError.new( - "too many loops for #{@original.inspect} #previous_time, breaking" - ) if (ifrom - ti).abs > BREAKER_S - - (ifrom == ti) && (t.inc(-1); next) month_match?(t) || (t.dec_month; next) day_match?(t) || (t.dec_day; next) hour_match?(t) || (t.dec_hour; next) min_match?(t) || (t.dec_min; next) - sec_match?(t) || (t.dec_sec(@seconds); next) + sec_match?(t) || (t.dec_sec; next) break end t.time.translate(from.zone) end