class IceCube::Schedule

Attributes

end_time[R]

Get the end time

start_time[R]

Get the start time

Public Class Methods

dump(schedule) click to toggle source
# File lib/ice_cube/schedule.rb, line 387
def self.dump(schedule)
  return schedule if schedule.nil? || schedule == ""
  schedule.to_yaml
end
from_hash(original_hash, options = {}) { |schedule| ... } click to toggle source

Load the schedule from a hash

# File lib/ice_cube/schedule.rb, line 374
def self.from_hash(original_hash, options = {})
  HashParser.new(original_hash).to_schedule do |schedule|
    Deprecated.schedule_options(schedule, options)
    yield schedule if block_given?
  end
end
from_ical(ical, options = {}) click to toggle source

Load the schedule from ical

# File lib/ice_cube/schedule.rb, line 337
def self.from_ical(ical, options = {})
  IcalParser.schedule_from_ical(ical, options)
end
from_yaml(yaml, options = {}) { |schedule| ... } click to toggle source

Load the schedule from yaml

# File lib/ice_cube/schedule.rb, line 347
def self.from_yaml(yaml, options = {})
  YamlParser.new(yaml).to_schedule do |schedule|
    Deprecated.schedule_options(schedule, options)
    yield schedule if block_given?
  end
end
load(yaml) click to toggle source
# File lib/ice_cube/schedule.rb, line 392
def self.load(yaml)
  return yaml if yaml.nil? || yaml == ""
  from_yaml(yaml)
end
new(start_time = nil, options = {}) { |self| ... } click to toggle source

Create a new schedule

# File lib/ice_cube/schedule.rb, line 18
def initialize(start_time = nil, options = {})
  self.start_time = start_time || TimeUtil.now
  self.end_time = self.start_time + options[:duration] if options[:duration]
  self.end_time = options[:end_time] if options[:end_time]
  @all_recurrence_rules = []
  @all_exception_rules = []
  yield self if block_given?
end

Public Instance Methods

add_exception_rule(rule) click to toggle source

Add an exception rule to the schedule

# File lib/ice_cube/schedule.rb, line 82
def add_exception_rule(rule)
  @all_exception_rules << rule unless @all_exception_rules.include?(rule)
end
Also aliased as: exrule
add_exception_time(time) click to toggle source

Add an exception time to the schedule

# File lib/ice_cube/schedule.rb, line 59
def add_exception_time(time)
  return nil if time.nil?
  rule = SingleOccurrenceRule.new(time)
  add_exception_rule rule
  time
end
Also aliased as: extime
add_recurrence_rule(rule) click to toggle source

Add a recurrence rule to the schedule

# File lib/ice_cube/schedule.rb, line 70
def add_recurrence_rule(rule)
  @all_recurrence_rules << rule unless @all_recurrence_rules.include?(rule)
end
Also aliased as: rrule
add_recurrence_time(time) click to toggle source

Add a recurrence time to the schedule

# File lib/ice_cube/schedule.rb, line 48
def add_recurrence_time(time)
  return nil if time.nil?
  rule = SingleOccurrenceRule.new(time)
  add_recurrence_rule rule
  time
end
Also aliased as: rtime
all_occurrences() click to toggle source

All of the occurrences

# File lib/ice_cube/schedule.rb, line 152
def all_occurrences
  require_terminating_rules
  enumerate_occurrences(start_time).to_a
end
all_occurrences_enumerator() click to toggle source

Emit an enumerator based on the start time

# File lib/ice_cube/schedule.rb, line 158
def all_occurrences_enumerator
  enumerate_occurrences(start_time)
end
conflicts_with?(other_schedule, closing_time = nil) click to toggle source

Determine if this schedule conflicts with another schedule @param [IceCube::Schedule] other_schedule - The schedule to compare to @param [Time] closing_time - the last time to consider @return [Boolean] whether or not the schedules conflict at all

# File lib/ice_cube/schedule.rb, line 255
def conflicts_with?(other_schedule, closing_time = nil)
  closing_time = TimeUtil.ensure_time(closing_time)
  unless terminating? || other_schedule.terminating? || closing_time
    raise ArgumentError, "One or both schedules must be terminating to use #conflicts_with?"
  end
  # Pick the terminating schedule, and other schedule
  # No need to reverse if terminating? or there is a closing time
  terminating_schedule = self
  unless terminating? || closing_time
    terminating_schedule, other_schedule = other_schedule, terminating_schedule
  end
  # Go through each occurrence of the terminating schedule and determine
  # if the other occurs at that time
  #
  last_time = nil
  terminating_schedule.each_occurrence do |time|
    if closing_time && time > closing_time
      last_time = closing_time
      break
    end
    last_time = time
    return true if other_schedule.occurring_at?(time)
  end
  # Due to durations, we need to walk up to the end time, and verify in the
  # other direction
  if last_time
    last_time += terminating_schedule.duration
    other_schedule.each_occurrence do |time|
      break if time > last_time
      return true if terminating_schedule.occurring_at?(time)
    end
  end
  # No conflict, return false
  false
end
duration() click to toggle source
# File lib/ice_cube/schedule.rb, line 39
def duration
  end_time ? end_time - start_time : 0
end
duration=(seconds) click to toggle source
# File lib/ice_cube/schedule.rb, line 43
def duration=(seconds)
  @end_time = start_time + seconds
end
each_occurrence(&block) click to toggle source

Iterate forever

# File lib/ice_cube/schedule.rb, line 163
def each_occurrence(&block)
  enumerate_occurrences(start_time, &block).to_a
  self
end
end_time=(end_time) click to toggle source

Set #end_time

# File lib/ice_cube/schedule.rb, line 34
def end_time=(end_time)
  @end_time = TimeUtil.ensure_time end_time
end
exception_rules() click to toggle source

Get the exception rules

# File lib/ice_cube/schedule.rb, line 100
def exception_rules
  @all_exception_rules.reject { |r| r.is_a?(SingleOccurrenceRule) }
end
Also aliased as: exrules
exception_times() click to toggle source

Get the exception times that are on the schedule

# File lib/ice_cube/schedule.rb, line 126
def exception_times
  @all_exception_rules.select { |r| r.is_a?(SingleOccurrenceRule) }.map(&:time)
end
Also aliased as: extimes
exrule(rule)
Alias for: add_exception_rule
exrules()
Alias for: exception_rules
extime(time)
Alias for: add_exception_time
extimes()
Alias for: exception_times
first(n = nil) click to toggle source

Get the first n occurrences, or the first occurrence if n is skipped

# File lib/ice_cube/schedule.rb, line 297
def first(n = nil)
  occurrences = enumerate_occurrences(start_time).take(n || 1)
  n.nil? ? occurrences.first : occurrences
end
last(n = nil) click to toggle source

Get the final n occurrences of a terminating schedule or the final one if no n is given

# File lib/ice_cube/schedule.rb, line 304
def last(n = nil)
  require_terminating_rules
  occurrences = enumerate_occurrences(start_time).to_a
  n.nil? ? occurrences.last : occurrences[-n..-1]
end
next_occurrence(from = nil, options = {}) click to toggle source

The next occurrence after now (overridable)

# File lib/ice_cube/schedule.rb, line 175
def next_occurrence(from = nil, options = {})
  from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
  enumerate_occurrences(from + 1, nil, options).next
rescue StopIteration
  nil
end
next_occurrences(num, from = nil, options = {}) click to toggle source

The next n occurrences after now

# File lib/ice_cube/schedule.rb, line 169
def next_occurrences(num, from = nil, options = {})
  from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
  enumerate_occurrences(from + 1, nil, options).take(num)
end
occurrences(closing_time) click to toggle source

Get all of the occurrences from the #start_time up until a given Time

# File lib/ice_cube/schedule.rb, line 147
def occurrences(closing_time)
  enumerate_occurrences(start_time, closing_time).to_a
end
occurrences_between(begin_time, closing_time, options = {}) click to toggle source

Occurrences between two times

# File lib/ice_cube/schedule.rb, line 211
def occurrences_between(begin_time, closing_time, options = {})
  enumerate_occurrences(begin_time, closing_time, options).to_a
end
occurring_at?(time) click to toggle source

Determine if the schedule is occurring at a given time

# File lib/ice_cube/schedule.rb, line 241
def occurring_at?(time)
  time = TimeUtil.match_zone(time, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
  if duration > 0
    return false if exception_time?(time)
    occurs_between?(time - duration + 1, time)
  else
    occurs_at?(time)
  end
end
occurring_between?(opening_time, closing_time) click to toggle source

Return a boolean indicating if an occurrence is occurring between two times, inclusive of its duration. This counts zero-length occurrences that intersect the start of the range and within the range, but not occurrences at the end of the range since none of their duration intersects the range.

# File lib/ice_cube/schedule.rb, line 228
def occurring_between?(opening_time, closing_time)
  occurs_between?(opening_time, closing_time, :spans => true)
end
occurs_at?(time) click to toggle source

Determine if the schedule occurs at a specific time

# File lib/ice_cube/schedule.rb, line 292
def occurs_at?(time)
  occurs_between?(time, time)
end
occurs_between?(begin_time, closing_time, options = {}) click to toggle source

Return a boolean indicating if an occurrence falls between two times

# File lib/ice_cube/schedule.rb, line 216
def occurs_between?(begin_time, closing_time, options = {})
  enumerate_occurrences(begin_time, closing_time, options).next
  true
rescue StopIteration
  false
end
occurs_on?(date) click to toggle source

Return a boolean indicating if an occurrence falls on a certain date

# File lib/ice_cube/schedule.rb, line 233
def occurs_on?(date)
  date = TimeUtil.ensure_date(date)
  begin_time = TimeUtil.beginning_of_date(date, start_time)
  closing_time = TimeUtil.end_of_date(date, start_time)
  occurs_between?(begin_time, closing_time)
end
previous_occurrence(from) click to toggle source

The previous occurrence from a given time

# File lib/ice_cube/schedule.rb, line 183
def previous_occurrence(from)
  from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
  return nil if from <= start_time
  enumerate_occurrences(start_time, from - 1).to_a.last
end
previous_occurrences(num, from) click to toggle source

The previous n occurrences before a given time

# File lib/ice_cube/schedule.rb, line 190
def previous_occurrences(num, from)
  from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
  return [] if from <= start_time
  a = enumerate_occurrences(start_time, from - 1).to_a
  a.size > num ? a[-1*num,a.size] : a
end
recurrence_rules() click to toggle source

Get the recurrence rules

# File lib/ice_cube/schedule.rb, line 94
def recurrence_rules
  @all_recurrence_rules.reject { |r| r.is_a?(SingleOccurrenceRule) }
end
Also aliased as: rrules
recurrence_times() click to toggle source

Get the recurrence times that are on the schedule

# File lib/ice_cube/schedule.rb, line 106
def recurrence_times
  @all_recurrence_rules.select { |r| r.is_a?(SingleOccurrenceRule) }.map(&:time)
end
Also aliased as: rtimes
remaining_occurrences(from = nil, options = {}) click to toggle source

The remaining occurrences (same requirements as #all_occurrences)

# File lib/ice_cube/schedule.rb, line 198
def remaining_occurrences(from = nil, options = {})
  require_terminating_rules
  from ||= TimeUtil.now(@start_time)
  enumerate_occurrences(from, nil, options).to_a
end
remaining_occurrences_enumerator(from = nil, options = {}) click to toggle source

Returns an enumerator for all remaining occurrences

# File lib/ice_cube/schedule.rb, line 205
def remaining_occurrences_enumerator(from = nil, options = {})
  from ||= TimeUtil.now(@start_time)
  enumerate_occurrences(from, nil, options)
end
remove_exception_rule(rule) click to toggle source

Remove an exception rule

# File lib/ice_cube/schedule.rb, line 88
def remove_exception_rule(rule)
  res = @all_exception_rules.delete(rule)
  res.nil? ? [] : [res]
end
remove_exception_time(time) click to toggle source

Remove an exception time

# File lib/ice_cube/schedule.rb, line 134
def remove_exception_time(time)
  found = false
  @all_exception_rules.delete_if do |rule|
    found = true if rule.is_a?(SingleOccurrenceRule) && rule.time == time
  end
  time if found
end
Also aliased as: remove_extime
remove_extime(time)
remove_recurrence_rule(rule) click to toggle source

Remove a recurrence rule

# File lib/ice_cube/schedule.rb, line 76
def remove_recurrence_rule(rule)
  res = @all_recurrence_rules.delete(rule)
  res.nil? ? [] : [res]
end
remove_recurrence_time(time) click to toggle source

Remove a recurrence time

# File lib/ice_cube/schedule.rb, line 114
def remove_recurrence_time(time)
  found = false
  @all_recurrence_rules.delete_if do |rule|
    found = true if rule.is_a?(SingleOccurrenceRule) && rule.time == time
  end
  time if found
end
Also aliased as: remove_rtime
remove_rtime(time)
rrule(rule)
Alias for: add_recurrence_rule
rrules()
Alias for: recurrence_rules
rtime(time)
Alias for: add_recurrence_time
rtimes()
Alias for: recurrence_times
start_time=(start_time) click to toggle source

Set #start_time

# File lib/ice_cube/schedule.rb, line 28
def start_time=(start_time)
  @start_time = TimeUtil.ensure_time start_time
end
terminating?() click to toggle source

Determine if the schedule will end @return [Boolean] true if ending, false if repeating forever

# File lib/ice_cube/schedule.rb, line 383
def terminating?
  recurrence_rules.empty? || recurrence_rules.all?(&:terminating?)
end
to_hash() click to toggle source

Convert the schedule to a hash

# File lib/ice_cube/schedule.rb, line 355
def to_hash
  data = {}
  data[:start_time] = TimeUtil.serialize_time(start_time)
  data[:start_date] = data[:start_time] if IceCube.compatibility <= 11
  data[:end_time] = TimeUtil.serialize_time(end_time) if end_time
  data[:rrules] = recurrence_rules.map(&:to_hash)
  if IceCube.compatibility <= 11 && exception_rules.any?
    data[:exrules] = exception_rules.map(&:to_hash)
  end
  data[:rtimes] = recurrence_times.map do |rt|
    TimeUtil.serialize_time(rt)
  end
  data[:extimes] = exception_times.map do |et|
    TimeUtil.serialize_time(et)
  end
  data
end
to_ical(force_utc = false) click to toggle source

Serialize this schedule #to_ical

# File lib/ice_cube/schedule.rb, line 325
def to_ical(force_utc = false)
  pieces = []
  pieces << "DTSTART#{IcalBuilder.ical_format(start_time, force_utc)}"
  pieces.concat recurrence_rules.map { |r| "RRULE:#{r.to_ical}" }
  pieces.concat exception_rules.map  { |r| "EXRULE:#{r.to_ical}" }
  pieces.concat recurrence_times_without_start_time.map { |t| "RDATE#{IcalBuilder.ical_format(t, force_utc)}" }
  pieces.concat exception_times.map  { |t| "EXDATE#{IcalBuilder.ical_format(t, force_utc)}" }
  pieces << "DTEND#{IcalBuilder.ical_format(end_time, force_utc)}" if end_time
  pieces.join("\n")
end
to_s() click to toggle source

String serialization

# File lib/ice_cube/schedule.rb, line 311
def to_s
  pieces = []
  rd = recurrence_times_with_start_time - extimes
  pieces.concat rd.sort.map { |t| IceCube::I18n.l(t, format: IceCube.to_s_time_format) }
  pieces.concat rrules.map  { |t| t.to_s }
  pieces.concat exrules.map { |t| IceCube::I18n.t('ice_cube.not', target: t.to_s) }
  pieces.concat extimes.sort.map { |t|
    target = IceCube::I18n.l(t, format: IceCube.to_s_time_format)
    IceCube::I18n.t('ice_cube.not_on', target: target)
  }
  pieces.join(IceCube::I18n.t('ice_cube.pieces_connector'))
end
to_yaml(*args) click to toggle source

Convert the schedule to yaml

# File lib/ice_cube/schedule.rb, line 342
def to_yaml(*args)
  YAML::dump(to_hash, *args)
end

Private Instance Methods

enumerate_occurrences(opening_time, closing_time = nil, options = {}, &block) click to toggle source

Find all of the occurrences for the schedule between opening_time and closing_time Iteration is unrolled in pairs to skip duplicate times in end of DST

# File lib/ice_cube/schedule.rb, line 408
def enumerate_occurrences(opening_time, closing_time = nil, options = {}, &block)
  opening_time = TimeUtil.match_zone(opening_time, start_time)
  closing_time = TimeUtil.match_zone(closing_time, start_time)
  opening_time += start_time.subsec - opening_time.subsec rescue 0
  opening_time = start_time if opening_time < start_time
  spans = options[:spans] == true && duration != 0
  Enumerator.new do |yielder|
    reset
    t1 = full_required? ? start_time : realign((spans ? opening_time - duration : opening_time))
    loop do
      break unless (t0 = next_time(t1, closing_time))
      break if closing_time && t0 > closing_time
      if (spans ? (t0.end_time > opening_time) : (t0 >= opening_time))
        yielder << (block_given? ? block.call(t0) : t0)
      end
      break unless (t1 = next_time(t0 + 1, closing_time))
      break if closing_time && t1 > closing_time
      if TimeUtil.same_clock?(t0, t1) && recurrence_rules.any?(&:dst_adjust?)
        wind_back_dst
        next (t1 += 1)
      end
      if (spans ? (t1.end_time > opening_time) : (t1 >= opening_time))
        yielder << (block_given? ? block.call(t1) : t1)
      end
      next (t1 += 1)
    end
  end
end
exception_time?(time) click to toggle source

Return a boolean indicating whether or not a specific time is excluded from the schedule

# File lib/ice_cube/schedule.rb, line 463
def exception_time?(time)
  @all_exception_rules.any? do |rule|
    rule.on?(time, self)
  end
end
full_required?() click to toggle source

Indicate if any rule needs to be run from the start of time If we have rules with counts, we need to walk from the beginning of time

# File lib/ice_cube/schedule.rb, line 456
def full_required?
  @all_recurrence_rules.any?(&:full_required?) ||
  @all_exception_rules.any?(&:full_required?)
end
implicit_start_occurrence_rule() click to toggle source
# File lib/ice_cube/schedule.rb, line 475
def implicit_start_occurrence_rule
  SingleOccurrenceRule.new(start_time)
end
next_time(time, closing_time) click to toggle source

Get the next time after (or including) a specific time

# File lib/ice_cube/schedule.rb, line 438
def next_time(time, closing_time)
  loop do
    min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |min_time, rule|
      begin
        new_time = rule.next_time(time, self, min_time || closing_time)
        [min_time, new_time].compact.min
      rescue StopIteration
        min_time
      end
    end
    break nil unless min_time
    next (time = min_time + 1) if exception_time?(min_time)
    break Occurrence.new(min_time, min_time + duration)
  end
end
realign(opening_time) click to toggle source

If any rule has validations for values within the period, (overriding the interval from start time, e.g. `day`), and the opening time is offset from the interval multiplier such that it might miss the first correct occurrence (e.g. repeat is every N weeks, but selecting from end of week N-1, the first jump would go to end of week N and miss any earlier validations in the week). This realigns the opening time to the start of the interval's correct period (e.g. move to start of week N) TODO: check if this is needed for validations other than `:wday`

# File lib/ice_cube/schedule.rb, line 514
def realign(opening_time)
  time = TimeUtil::TimeWrapper.new(opening_time)
  recurrence_rules.each do |rule|
    wday_validations = rule.other_interval_validations.select { |v| v.type == :wday } or next
    interval = rule.base_interval_validation.validate(opening_time, self).to_i
    offset = wday_validations
      .map { |v| v.validate(opening_time, self).to_i }
      .reduce(0) { |least, i| i > 0 && i <= interval && (i < least || least == 0) ? i : least }
    time.add(rule.base_interval_type, 7 - time.to_time.wday) if offset > 0
  end
  time.to_time
end
recurrence_rules_with_implicit_start_occurrence() click to toggle source
# File lib/ice_cube/schedule.rb, line 491
def recurrence_rules_with_implicit_start_occurrence
  if recurrence_rules.empty?
    [implicit_start_occurrence_rule].concat @all_recurrence_rules
  else
    @all_recurrence_rules
  end
end
recurrence_times_with_start_time() click to toggle source
# File lib/ice_cube/schedule.rb, line 483
def recurrence_times_with_start_time
  if recurrence_rules.empty?
    [start_time].concat recurrence_times_without_start_time
  else
    recurrence_times
  end
end
recurrence_times_without_start_time() click to toggle source
# File lib/ice_cube/schedule.rb, line 479
def recurrence_times_without_start_time
  recurrence_times.reject { |t| t == start_time }
end
require_terminating_rules() click to toggle source
# File lib/ice_cube/schedule.rb, line 469
def require_terminating_rules
  return true if terminating?
  method_name = caller[0].split(' ').last
  raise ArgumentError, "All recurrence rules must specify .until or .count to use #{method_name}"
end
reset() click to toggle source

Reset all rules for another run

# File lib/ice_cube/schedule.rb, line 400
def reset
  @all_recurrence_rules.each(&:reset)
  @all_exception_rules.each(&:reset)
end
wind_back_dst() click to toggle source
# File lib/ice_cube/schedule.rb, line 499
def wind_back_dst
  recurrence_rules.each do |rule|
    rule.skipped_for_dst
  end
end