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