lib/rufus/scheduler/cronline.rb in rufus-scheduler-3.0.9 vs lib/rufus/scheduler/cronline.rb in rufus-scheduler-3.1.0

- old
+ new

@@ -1,7 +1,7 @@ #-- -# Copyright (c) 2006-2014, John Mettraux, jmettraux@gmail.com +# Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell @@ -20,11 +20,13 @@ # THE SOFTWARE. # # Made in Japan. #++ +require 'set' + class Rufus::Scheduler # # A 'cron line' is a line in the sense of a crontab # (man 5 crontab) file line. @@ -52,12 +54,11 @@ @original = line items = line.split - @timezone = (TZInfo::Timezone.get(items.last) rescue nil) - items.pop if @timezone + @timezone = items.pop if ZoTime.is_timezone?(items.last) raise ArgumentError.new( "not a valid cronline : '#{line}'" ) unless items.length == 5 or items.length == 6 @@ -80,14 +81,12 @@ # Returns true if the given time matches this cron line. # def matches?(time) - time = Time.at(time) unless time.kind_of?(Time) + time = ZoTime.new(time.to_f, @timezone || ENV['TZ']).time - time = @timezone.utc_to_local(time.getutc) if @timezone - return false unless sub_match?(time, :sec, @seconds) return false unless sub_match?(time, :min, @minutes) return false unless sub_match?(time, :hour, @hours) return false unless date_match?(time) true @@ -120,111 +119,122 @@ # # (Thanks to K Liu for the note and the examples) # def next_time(from=Time.now) - time = local_time(from) - time = round_to_seconds(time) + time = nil + zotime = ZoTime.new(from.to_i + 1, @timezone || ENV['TZ']) - # start at the next second - time = time + 1 - loop do + + time = zotime.time + unless date_match?(time) - dst = time.isdst - time += (24 - time.hour) * 3600 - time.min * 60 - time.sec - time -= 3600 if time.isdst != dst # not necessary for winter, but... + zotime.add((24 - time.hour) * 3600 - time.min * 60 - time.sec) next end unless sub_match?(time, :hour, @hours) - time += (60 - time.min) * 60 - time.sec; next + zotime.add((60 - time.min) * 60 - time.sec) + next end unless sub_match?(time, :min, @minutes) - time += 60 - time.sec; next + zotime.add(60 - time.sec) + next end unless sub_match?(time, :sec, @seconds) - time += 1; next + zotime.add(next_second(time)) + next end break end - global_time(time, from.utc?) - - rescue TZInfo::PeriodNotFound - - next_time(from + 3600) + time end # Returns the previous time the cronline matched. It's like next_time, but # for the past. # def previous_time(from=Time.now) - time = local_time(from) - time = round_to_seconds(time) + time = nil + zotime = ZoTime.new(from.to_i - 1, @timezone || ENV['TZ']) - # start at the previous second - time = time - 1 - loop do + + time = zotime.time + unless date_match?(time) - time -= time.hour * 3600 + time.min * 60 + time.sec + 1; next + zotime.substract(time.hour * 3600 + time.min * 60 + time.sec + 1) + next end unless sub_match?(time, :hour, @hours) - time -= time.min * 60 + time.sec + 1; next + zotime.substract(time.min * 60 + time.sec + 1) + next end unless sub_match?(time, :min, @minutes) - time -= time.sec + 1; next + zotime.substract(time.sec + 1) + next end unless sub_match?(time, :sec, @seconds) - time -= 1; next + zotime.substract(prev_second(time)) + next end break end - global_time(time, from.utc?) + time + end - rescue TZInfo::PeriodNotFound - - previous_time(time) + if RUBY_VERSION >= '1.9' + def toa(item) + item == nil ? nil : item.to_a + end + else + def toi(item); item.is_a?(String) ? item.hash.abs : item.to_i; end + protected :toi + def toa(item) + item.is_a?(Set) ? item.to_a.sort_by { |e| toi(e) } : item + end end + protected :toa # Returns an array of 6 arrays (seconds, minutes, hours, days, # months, weekdays). # This method is used by the cronline unit tests. # def to_array [ - @seconds, - @minutes, - @hours, - @days, - @months, - @weekdays, - @monthdays, - @timezone ? @timezone.name : nil + toa(@seconds), + toa(@minutes), + toa(@hours), + toa(@days), + toa(@months), + toa(@weekdays), + toa(@monthdays), + @timezone ] end # Returns a quickly computed approximation of the frequency for this # cron line. # # #brute_frequency, on the other hand, will compute the frequency by - # examining a whole, that can take more than seconds for a seconds + # examining a whole year, that can take more than seconds for a seconds # level cron... # def frequency return brute_frequency unless @seconds && @seconds.length > 1 delta = 60 - prev = @seconds[0] + secs = toa(@seconds) + prev = secs[0] - @seconds[1..-1].each do |sec| + secs[1..-1].each do |sec| d = sec - prev delta = d if d < delta end delta @@ -268,20 +278,40 @@ t1 = next_time(t0) d = t1 - t0 delta = d if d < delta break if @months == nil && t1.month == 2 - break if t1.year == 2001 + break if t1.year >= 2001 t0 = t1 end delta end protected + def next_second(time) + + secs = @seconds.sort + + return secs.last + 60 - time.sec if time.sec > secs.last + + secs.shift while secs.first < time.sec + + secs.first - time.sec + end + + def prev_second(time) + + secs = @seconds.sort + + secs.pop while time.sec < secs.last + + time.sec - secs.last + end + WEEKDAYS = %w[ sun mon tue wed thu fri sat ] DAY_S = 24 * 3600 WEEK_S = 7 * DAY_S def parse_weekdays(item) @@ -334,11 +364,11 @@ raise ArgumentError.new( "found duplicates in #{item.inspect}" ) if r.uniq.size < r.size - r + Set.new(r) end RANGE_REGEX = /^(\*|\d{1,2})(?:-(\d{1,2}))?(?:\/(\d{1,2}))?$/ def parse_range(item, min, max) @@ -432,35 +462,9 @@ break if d.month != date.month neg = neg - 1 end [ "#{WEEKDAYS[date.wday]}##{pos}", "#{WEEKDAYS[date.wday]}##{neg}" ] - end - - def local_time(time) - - @timezone ? @timezone.utc_to_local(time.getutc) : time - end - - def global_time(time, from_in_utc) - - if @timezone - time = - begin - @timezone.local_to_utc(time) - rescue TZInfo::AmbiguousTime - @timezone.local_to_utc(time, time.isdst) - end - time = time.getlocal unless from_in_utc - end - - time - end - - def round_to_seconds(time) - - # Ruby 1.8 doesn't have #round - time.respond_to?(:round) ? time.round : time - time.usec * 1e-6 end end end