lib/fugit/cron.rb in fugit-1.0.0 vs lib/fugit/cron.rb in fugit-1.1.0

- old
+ new

@@ -12,58 +12,64 @@ '@daily' => '0 0 * * *', '@midnight' => '0 0 * * *', '@hourly' => '0 * * * *', } - attr_reader :original + attr_reader :original, :zone + attr_reader :minutes, :hours, :monthdays, :months, :weekdays, :timezone - attr_reader :minutes, :hours, :monthdays, :months, :weekdays + class << self - def self.new(original) + def new(original) - parse(original) - end + parse(original) + end - def to_cron_s + def parse(s) - @cron_s ||= - [ - @seconds == [ 0 ] ? nil : (@seconds || [ '*' ]).join(','), - (@minutes || [ '*' ]).join(','), - (@hours || [ '*' ]).join(','), - (@monthdays || [ '*' ]).join(','), - (@months || [ '*' ]).join(','), - (@weekdays || [ [ '*' ] ]).map { |d| d.compact.join('#') }.join(',') - ].compact.join(' ') - end + return s if s.is_a?(self) - def self.parse(s) + original = s + s = SPECIALS[s] || s - return s if s.is_a?(self) + return nil unless s.is_a?(String) - original = s - s = SPECIALS[s] || s +#p s; Raabro.pp(Parser.parse(s, debug: 3), colors: true) + h = Parser.parse(s) - return nil unless s.is_a?(String) + return nil unless h -#p s; Raabro.pp(Parser.parse(s, debug: 3)) - h = Parser.parse(s) + self.allocate.send(:init, s, h) + end - return nil unless h + def do_parse(s) - self.allocate.send(:init, s, h) + parse(s) || + fail(ArgumentError.new("not a cron string #{s.inspect}")) + end end - def self.do_parse(s) + def to_cron_s - parse(s) || fail(ArgumentError.new("not a cron string #{s.inspect}")) + @cron_s ||= begin + [ + @seconds == [ 0 ] ? nil : (@seconds || [ '*' ]).join(','), + (@minutes || [ '*' ]).join(','), + (@hours || [ '*' ]).join(','), + (@monthdays || [ '*' ]).join(','), + (@months || [ '*' ]).join(','), + (@weekdays || [ [ '*' ] ]).map { |d| d.compact.join('#') }.join(','), + @timezone ? @timezone.to_s : nil + ].compact.join(' ') + end end class TimeCursor def initialize(t) - @t = t.is_a?(TimeCursor) ? t.time : ::EtOrbi.make_time(t) + @t = t.is_a?(TimeCursor) ? t.time : t + @t.seconds = @t.seconds.to_i end def time; @t; end def to_i; @t.to_i; end @@ -161,11 +167,12 @@ hour_match?(t) && min_match?(t) && sec_match?(t) end def next_time(from=::EtOrbi::EoTime.now) - t = TimeCursor.new(from) + from = ::EtOrbi.make_time(from) + t = TimeCursor.new(from.translate(@timezone)) loop do #p [ :l, Fugit.time_to_s(t.time) ] (from.to_i == t.to_i) && (t.inc(1); next) month_match?(t) || (t.inc_month; next) @@ -174,16 +181,17 @@ min_match?(t) || (t.inc_min; next) sec_match?(t) || (t.inc_sec(@seconds); next) break end - t.time + t.time.translate(from.zone) end def previous_time(from=::EtOrbi::EoTime.now) - t = TimeCursor.new(from) + from = ::EtOrbi.make_time(from) + t = TimeCursor.new(from.translate(@timezone)) loop do #p [ :l, Fugit.time_to_s(t.time) ] (from.to_i == t.to_i) && (t.inc(-1); next) month_match?(t) || (t.dec_month; next) @@ -192,17 +200,18 @@ min_match?(t) || (t.dec_min; next) sec_match?(t) || (t.dec_sec(@seconds); next) break end - t.time + t.time.translate(from.zone) end # 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) + # 2017 is a non leap year (though it is preceded by + # a leap second on 2016-12-31) # # Nota bene: cron with seconds are not supported. # def brute_frequency(year=2017) @@ -221,20 +230,42 @@ break if deltas.any? && t1.year > year break if t1.year - t0.year > 7 t = t1 end - 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 ] + Frequency.new(deltas, t1 - t0) end end + class Frequency + + attr_reader :span, :delta_min, :delta_max, :occurrences + attr_reader :span_years, :yearly_occurrences + + def initialize(deltas, span) + + @span = span + + @delta_min = deltas.min; @delta_max = deltas.max + @occurrences = deltas.size + @span_years = span / (365 * 24 * 3600) + @yearly_occurrences = @occurrences.to_f / @span_years + end + + def to_debug_s + + { + dmin: Fugit::Duration.new(delta_min).deflate.to_plain_s, + dmax: Fugit::Duration.new(delta_max).deflate.to_plain_s, + ocs: occurrences, + spn: Fugit::Duration.new(span.to_i).deflate.to_plain_s, + spnys: span_years.to_i, + yocs: yearly_occurrences.to_i + }.collect { |k, v| "#{k}: #{v}" }.join(', ') + end + end + def to_a [ @seconds, @minutes, @hours, @monthdays, @months, @weekdays ] end @@ -262,10 +293,11 @@ determine_minutes(h[:min]) determine_hours(h[:hou]) determine_monthdays(h[:dom]) determine_months(h[:mon]) determine_weekdays(h[:dow]) + determine_timezone(h[:tz]) self end def expand(min, max, r) @@ -343,10 +375,15 @@ @weekdays.each { |wd| wd[0] = 0 if wd[0] == 7 } # turn sun7 into sun0 @weekdays.uniq! @weekdays = nil if @weekdays.empty? end + def determine_timezone(z) + + @zone, @timezone = z + end + module Parser include Raabro WEEKDAYS = %w[ sunday monday tuesday wednesday thursday friday saturday ] WEEKDS = WEEKDAYS.collect { |d| d[0, 3] } @@ -418,15 +455,23 @@ 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 _tz_name(i) + rex(nil, i, / +[A-Z][a-zA-Z0-9]+(\/[A-Z][a-zA-Z0-9_]+){0,2}/) + end + def _tz_delta(i) + rex(nil, i, / +[-+]([01][0-9]|2[0-4]):?(00|15|30|45)/) + end + def _tz(i); alt(:tz, i, :_tz_delta, :_tz_name); end + def classic_cron(i) - seq(:ccron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow) + seq(:ccron, i, :lmin_, :lhou_, :ldom_, :lmon_, :ldow, :_tz, '?') end def second_cron(i) - seq(:scron, i, :lsec_, :lmin_, :lhou_, :ldom_, :lmon_, :ldow) + seq(:scron, i, :lsec_, :lmin_, :lhou_, :ldom_, :lmon_, :ldow, :_tz, '?') end def cron(i) alt(:cron, i, :second_cron, :classic_cron) end @@ -467,15 +512,29 @@ t .subgather(:elt) .collect { |et| rewrite_elt(t.name, et) } end + def rewrite_tz(t) + + s = t.string.strip + z = EtOrbi.get_tzone(s) + + [ s, z ] + end + def rewrite_cron(t) - t + hcron = t .sublookup(nil) # go to :ccron or :scron .subgather(nil) # list min, hou, mon, ... - .inject({}) { |h, tt| h[tt.name] = rewrite_entry(tt); h } + .inject({}) { |h, tt| + h[tt.name] = tt.name == :tz ? rewrite_tz(tt) : rewrite_entry(tt) + h } + + z, tz = hcron[:tz]; return nil if z && ! tz + + hcron end end end end