lib/fugit/cron.rb in fugit-0.9.3 vs lib/fugit/cron.rb in fugit-0.9.4
- old
+ new
@@ -49,16 +49,17 @@
def to_cron_s
@cron_s ||=
[
+ @seconds == [ 0 ] ? nil : (@seconds || [ '*' ]).join(','),
(@minutes || [ '*' ]).join(','),
(@hours || [ '*' ]).join(','),
(@monthdays || [ '*' ]).join(','),
(@months || [ '*' ]).join(','),
(@weekdays || [ [ '*' ] ]).map { |d| d.compact.join('#') }.join(',')
- ].join(' ')
+ ].compact.join(' ')
end
def self.parse(s)
return s if s.is_a?(self)
@@ -84,10 +85,11 @@
def initialize(t)
@t = t.is_a?(NextTime) ? t.time : t
end
def time; @t; end
+ def to_i; @t.to_i; end
%w[ year month day wday hour min sec ]
.collect(&:to_sym).each { |k| define_method(k) { @t.send(k) } }
def inc(i)
@@ -107,18 +109,30 @@
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 s = seconds.find { |s| s > @t.sec }
+ inc(s - @t.sec)
+ else
+ inc(60 - @t.sec + seconds.first)
+ end
+ end
+
def dec_month
dec(@t.day * 24 * 3600 + @t.hour * 3600 + @t.min * 60 + @t.sec + 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; dec(@t.sec); end
+ def dec_sec(seconds)
+ target = seconds.reverse.find { |s| s < @t.sec } || seconds.last
+ inc(target - @t.sec)
+ end
+
def count_weeks(inc)
c = 0
t = @t
until t.month != @t.month
c += 1
@@ -133,15 +147,14 @@
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
+ def sec_match?(nt); ( ! @seconds) || @seconds.include?(nt.sec); end
def weekday_match?(nt)
-#p @weekdays
-#p [ nt.day, nt.wday ]
return true if @weekdays.nil?
wd, hsh = @weekdays.find { |wd, hsh| wd == nt.wday }
return false unless wd
@@ -181,23 +194,26 @@
def match?(t)
t = Fugit.do_parse_at(t)
t = NextTime.new(t)
- month_match?(t) && day_match?(t) && hour_match?(t) && min_match?(t)
+ month_match?(t) && day_match?(t) &&
+ hour_match?(t) && min_match?(t) && sec_match?(t)
end
def next_time(from=Time.now)
nt = NextTime.new(from)
loop do
-#p Fugit.time_to_s(nt.time)
+#p [ :l, Fugit.time_to_s(nt.time) ]
+ (from.to_i == nt.to_i) && (nt.inc(1); next)
month_match?(nt) || (nt.inc_month; next)
day_match?(nt) || (nt.inc_day; next)
hour_match?(nt) || (nt.inc_hour; next)
min_match?(nt) || (nt.inc_min; next)
+ sec_match?(nt) || (nt.inc_sec(@seconds); next)
break
end
nt.time
end
@@ -205,46 +221,62 @@
def previous_time(from=Time.now)
nt = NextTime.new(from)
loop do
-#p Fugit.time_to_s(nt.time)
+#p [ :l, Fugit.time_to_s(nt.time) ]
+ (from.to_i == nt.to_i) && (nt.inc(-1); next)
month_match?(nt) || (nt.dec_month; next)
day_match?(nt) || (nt.dec_day; next)
hour_match?(nt) || (nt.dec_hour; next)
min_match?(nt) || (nt.dec_min; next)
- nt.dec_sec
+ sec_match?(nt) || (nt.dec_sec(@seconds); next)
break
end
nt.time
end
- # Returns [ min delta, max delta, occurence count ]
- # Computes for a non leap year (2017).
+ # Mostly used as a #next_time sanity check.
+ # Avoid for "business" use, it's slow.
#
+ # 2017 is non leap year (though it is preceded by a leap second)
+ #
+ # Nota bene: cron with seconds are not supported.
+ #
def brute_frequency(year=2017)
FREQUENCY_CACHE["#{to_cron_s}|#{year}"] ||=
begin
+
deltas = []
+ t = Time.parse("#{year}-01-01") - 1
t0 = nil
+ t1 = nil
loop do
- t1 = next_time(t0 || Time.parse("#{year}-01-01"))
- deltas << (t1 - t0).to_i + 60 if t0
- break if t1.year > year
- t0 = t1 + 60
+ t1 = next_time(t)
+ deltas << (t1 - t).to_i if t0
+ t0 ||= t1
+ break if deltas.any? && t1.year > year
+ break if t1.year - t0.year > 7
+ t = t1
end
- [ deltas.min, deltas.max, deltas.size ]
+ occurences = deltas.size
+ span = t1 - t0
+ span_years = span / (365 * 24 * 3600)
+ yearly_occurences = occurences.to_f / span_years
+
+ [ deltas.min, deltas.max, occurences,
+ span.to_i, span_years.to_i, yearly_occurences.to_i ]
end
end
def to_a
- [ @minutes, @hours, @monthdays, @months, @weekdays ]
+ [ @seconds, @minutes, @hours, @monthdays, @months, @weekdays ]
end
def ==(o)
o.is_a?(::Fugit::Cron) && o.to_a == to_a
@@ -261,13 +293,13 @@
FREQUENCY_CACHE = {}
def init(original, h)
@original = original
+ @cron_s = nil # just to be sure
- h ||= {}
-
+ determine_seconds(h[:sec])
determine_minutes(h[:min])
determine_hours(h[:hou])
determine_monthdays(h[:dom])
determine_months(h[:mon])
determine_weekdays(h[:dow])
@@ -301,57 +333,57 @@
arr.uniq!
arr.sort!
end
- def determine_minutes(mins)
- return @minutes = nil unless mins
- @minutes = mins.inject([]) { |a, r| a.concat(expand(0, 59, r)) }
+ def determine_seconds(a)
+ @seconds = (a || [ 0 ]).inject([]) { |a, r| a.concat(expand(0, 59, r)) }
+ compact(:@seconds)
+ end
+
+ def determine_minutes(a)
+ @minutes = a.inject([]) { |a, r| a.concat(expand(0, 59, r)) }
compact(:@minutes)
end
- def determine_hours(hous)
- return @hours = nil unless hous
- @hours = hous.inject([]) { |a, r| a.concat(expand(0, 23, r)) }
+ def determine_hours(a)
+ @hours = a.inject([]) { |a, r| a.concat(expand(0, 23, r)) }
@hours = @hours.collect { |h| h == 24 ? 0 : h }
compact(:@hours)
end
- def determine_monthdays(doms)
- return @monthdays = nil unless doms
- @monthdays = doms.inject([]) { |a, r| a.concat(expand(1, 31, r)) }
+ def determine_monthdays(a)
+ @monthdays = a.inject([]) { |a, r| a.concat(expand(1, 31, r)) }
compact(:@monthdays)
end
- def determine_months(mons)
- return @months = nil unless mons
- @months = mons.inject([]) { |a, r| a.concat(expand(1, 12, r)) }
+ def determine_months(a)
+ @months = a.inject([]) { |a, r| a.concat(expand(1, 12, r)) }
compact(:@months)
end
- def determine_weekdays(dows)
+ def determine_weekdays(a)
- return @weekdays = nil unless dows
+ @weekdays = []
- @weekdays = dows.inject([]) { |a, r|
- aa = expand(0, 7, r)
- if hsh = r[3]
- a.concat([ [ aa.first, hsh ] ])
- else
- a.concat(aa.collect { |i| [ i, nil ] })
+ a.each do |a, z, s, h| # a to z, slash and hash
+ if h
+ @weekdays << [ a, h ]
+ elsif s
+ ((a || 0)..(z || (a ? a : 6))).step(s < 1 ? 1 : s)
+ .each { |i| @weekdays << [ i ] }
+ elsif z
+ (a..z).each { |i| @weekdays << [ i ] }
+ elsif a
+ @weekdays << [ a ]
+ #else
end
- }
+ end
- @weekdays =
- if @weekdays.include?([ nil, nil ])
- nil
- else
- @weekdays
- .collect { |d, h| [ d == 7 ? 0 : d, h ] }
- .uniq { |d| d.join('#') }
- .sort_by { |d| d.join('#') }
- end
+ @weekdays.each { |wd| wd[0] = 0 if wd[0] == 7 } # turn sun7 into sun0
+ @weekdays.uniq!
+ @weekdays = nil if @weekdays.empty?
end
module Parser include Raabro
WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
@@ -359,117 +391,130 @@
MONTHS = %w[ - jan feb mar apr may jun jul aug sep oct nov dec ]
# piece parsers bottom to top
- def s(i); rex(:s, i, /[ \t]+/); end
- def star(i); str(:star, i, '*'); end
+ def s(i); rex(nil, i, /[ \t]+/); end
+ def star(i); str(nil, i, '*'); end
def hyphen(i); str(nil, i, '-'); end
def comma(i); str(nil, i, ','); end
def slash(i); rex(:slash, i, /\/\d\d?/); end
- def core_min(i); rex(:min, i, /[0-5]?\d/); end
+ def core_mos(i); rex(:mos, i, /[0-5]?\d/); end # min or sec
def core_hou(i); rex(:hou, i, /(2[0-4]|[01]?[0-9])/); end
def core_dom(i); rex(:dom, i, /(-?(3[01]|[012]?[0-9])|last|l)/i); end
def core_mon(i); rex(:mon, i, /(1[0-2]|0?[0-9]|#{MONTHS[1..-1].join('|')})/i); end
def core_dow(i); rex(:dow, i, /([0-7]|#{WEEKDS.join('|')})/i); end
def dow_hash(i); rex(:hash, i, /#(-?[1-5]|last|l)/i); end
- def min(i); core_min(i); end
+ def mos(i); core_mos(i); end
def hou(i); core_hou(i); end
def dom(i); core_dom(i); end
def mon(i); core_mon(i); end
def dow(i); core_dow(i); end
- def _min(i); seq(nil, i, :hyphen, :min); end
+ def _mos(i); seq(nil, i, :hyphen, :mos); end
def _hou(i); seq(nil, i, :hyphen, :hou); end
def _dom(i); seq(nil, i, :hyphen, :dom); end
def _mon(i); seq(nil, i, :hyphen, :mon); end
def _dow(i); seq(nil, i, :hyphen, :dow); end
# r: range
- def r_min(i); seq(nil, i, :min, :_min, '?'); end
+ def r_mos(i); seq(nil, i, :mos, :_mos, '?'); end
def r_hou(i); seq(nil, i, :hou, :_hou, '?'); end
def r_dom(i); seq(nil, i, :dom, :_dom, '?'); end
def r_mon(i); seq(nil, i, :mon, :_mon, '?'); end
def r_dow(i); seq(nil, i, :dow, :_dow, '?'); end
# sor: star or range
- def sor_min(i); alt(nil, i, :star, :r_min); end
+ def sor_mos(i); alt(nil, i, :star, :r_mos); end
def sor_hou(i); alt(nil, i, :star, :r_hou); end
def sor_dom(i); alt(nil, i, :star, :r_dom); end
def sor_mon(i); alt(nil, i, :star, :r_mon); end
def sor_dow(i); alt(nil, i, :star, :r_dow); end
# sorws: star or range with[out] slash
- def sorws_min(i); seq(nil, i, :sor_min, :slash, '?'); end
- def sorws_hou(i); seq(nil, i, :sor_hou, :slash, '?'); end
- def sorws_dom(i); seq(nil, i, :sor_dom, :slash, '?'); end
- def sorws_mon(i); seq(nil, i, :sor_mon, :slash, '?'); end
- def sorws_dow(i); seq(nil, i, :sor_dow, :slash, '?'); end
+ def sorws_mos(i); seq(:elt, i, :sor_mos, :slash, '?'); end
+ def sorws_hou(i); seq(:elt, i, :sor_hou, :slash, '?'); end
+ def sorws_dom(i); seq(:elt, i, :sor_dom, :slash, '?'); end
+ def sorws_mon(i); seq(:elt, i, :sor_mon, :slash, '?'); end
+ def sorws_dow(i); seq(:elt, i, :sor_dow, :slash, '?'); end
- def h_dow(i); seq(nil, i, :core_dow, :dow_hash); end
+ def h_dow(i); seq(:elt, i, :core_dow, :dow_hash); end
def _sorws_dow(i); alt(nil, i, :h_dow, :sorws_dow); end
- def list_min(i); jseq(:min, i, :sorws_min, :comma); end
+ def list_sec(i); jseq(:sec, i, :sorws_mos, :comma); end
+ def list_min(i); jseq(:min, i, :sorws_mos, :comma); end
def list_hou(i); jseq(:hou, i, :sorws_hou, :comma); end
def list_dom(i); jseq(:dom, i, :sorws_dom, :comma); end
def list_mon(i); jseq(:mon, i, :sorws_mon, :comma); end
def list_dow(i); jseq(:dow, i, :_sorws_dow, :comma); end
+ def lsec_(i); seq(nil, i, :list_sec, :s); end
def lmin_(i); seq(nil, i, :list_min, :s); end
def lhou_(i); seq(nil, i, :list_hou, :s); end
def ldom_(i); seq(nil, i, :list_dom, :s); end
def lmon_(i); seq(nil, i, :list_mon, :s); end
alias ldow list_dow
- def cron(i); seq(:cron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow); end
+ def classic_cron(i)
+ seq(:ccron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow)
+ end
+ def second_cron(i)
+ seq(:scron, i, :lsec_, :lmin_, :lhou_, :ldom_, :lmon_, :ldow)
+ end
+ def cron(i)
+ alt(:cron, i, :second_cron, :classic_cron)
+ end
+
# rewriting the parsed tree
def to_i(k, t)
s = t.string.downcase
(k == :mon && MONTHS.index(s)) ||
(k == :dow && WEEKDS.index(s)) ||
- (k == :dom && s[0, 1] == 'l' && -1) || # L, l, last
+ ((k == :dom) && s[0, 1] == 'l' && -1) || # L, l, last
s.to_i
end
- def rewrite_entry(t)
+ def rewrite_elt(k, t)
- k = t.name
+ (a, z), others = t
+ .subgather(nil)
+ .partition { |tt| ![ :hash, :slash ].include?(tt.name) }
+ s = others.find { |tt| tt.name == :slash }
+ h = others.find { |tt| tt.name == :hash }
- t.children.select { |ct| ct.children.any? }.inject([]) { |a, ct|
+ h = h ? h.string[1..-1] : nil
+ h = -1 if h && h.upcase[0, 1] == 'L'
+ h = h.to_i if h
- xts = ct.gather(k)
-#xts.each { |xt| Raabro.pp(xt) }
- range = xts.any? ? xts.collect { |xt| to_i(k, xt) } : []
- while range.size < 2; range << nil; end
+ a = a ? to_i(k, a) : nil
+ z = z ? to_i(k, z) : nil
+ a, z = z, a if a && z && a > z
- st = ct.lookup(:slash)
- range << (st ? st.string[1..-1].to_i : nil)
+ [ a, z, s ? s.string[1..-1].to_i : nil, h ]
+ end
- if k == :dow && ht = ct.lookup(:hash)
- hs = ht.string.downcase
- range << ((hs[1, 1] == 'l') ? -1 : hs[1..-1].to_i)
- end
+ def rewrite_entry(t)
- a << range
-
- a
- }
+ t
+ .subgather(:elt)
+ .collect { |et| rewrite_elt(t.name, et) }
end
- SYMS = %w[ min hou dom mon dow ].collect(&:to_sym)
-
def rewrite_cron(t)
- SYMS.inject({}) { |h, k| h[k] = rewrite_entry(t.lookup(k)); h }
+ t
+ .sublookup(nil) # go to :ccron or :scron
+ .subgather(nil) # list min, hou, mon, ...
+ .inject({}) { |h, tt| h[tt.name] = rewrite_entry(tt); h }
end
end
end
end