class Enumdate::DateEnumerator::Base

Base class for DateEnumerator

Public Class Methods

new(first_date:, interval: 1, wkst: 1) click to toggle source
# File lib/enumdate/date_enumerator.rb, line 10
def initialize(first_date:, interval: 1, wkst: 1)
  @first_date, @interval, @wkst = first_date, interval, wkst
  @frame_manager = frame_manager.new(first_date, interval, wkst)
  @duration_begin = first_date
  @duration_until = nil
end

Public Instance Methods

each() { |first_date| ... } click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/enumdate/date_enumerator.rb, line 18
def each
  return enum_for(:each) unless block_given?

  # ~first_date~ value always counts as the first occurrence
  # even if it does not match the specified rule.
  # cf. DTSTART vs RRULE in RFC5445.
  yield @first_date if between_duration?(@first_date)

  @frame_manager.each do |frame|
    # Avoid inifinit loops even if the rule emits no occurrences
    # such as "31st April in every year".
    # (Every ~occurrence_in_frame(frame)~ returns nil)
    break if @duration_until && @duration_until < frame

    # In some cases, ~occurrence_in_frame~ returns nil.
    # For example, a recurrence that returns 31st of each month
    # will return nil for short months such as April and June.
    next unless (date = occurrence_in_frame(frame))

    break if @duration_until && @duration_until < date

    # ~occurrence_in_frame~ may return a date earlier than
    # ~first_date~ in the first iteration.  This is because
    # ~first_date~ does not necessarily follow the repetetion
    # rule.  For example, if the rule is "every August 1st" and
    # ~first_date~ is August 15th, The first occurrence calculated
    # by the rule returns "August 1st", which is earlier than
    # August 15th. In this context, ~@duration_begin~ is the matter.
    next if date < @duration_begin

    # Ignore ~first_date~ not to yield twice.
    next if date == @first_date

    yield date
  end
end
forward_to(date) click to toggle source

Set the new beginning of duration for the recurrence rule.

It also controls the underlying frame manager. Since the frame manager is enough smart to avoid unnecessary repetition, there is no problem in setting the date hundred years later.

Note that the meaning of calling ~forward_to~ is different from that of setting the ~first_date~ parameter on creation. For example, if a yearly event has two-years ~interval~:

1) if first_date is 2021-08-01 and forward_to 2022-08-01,

it will create [2021-08-01 2023-08-01 ...]

2) if first_date is 2022-08-01,

it will create [2022-08-01 2024-08-01 ...]
# File lib/enumdate/date_enumerator.rb, line 72
def forward_to(date)
  @frame_manager.forward_to(date)
  @duration_begin = date
  self
end
rewind() click to toggle source

Imprement rewind for Enumrator class

# File lib/enumdate/date_enumerator.rb, line 79
def rewind
  @frame_manager.rewind
  self
end
until(date) click to toggle source

Set the new end of duration for the recurrence rule.

# File lib/enumdate/date_enumerator.rb, line 85
def until(date)
  @duration_until = date
  self
end

Private Instance Methods

between_duration?(date) click to toggle source
# File lib/enumdate/date_enumerator.rb, line 92
def between_duration?(date)
  (!@duration_begin || @duration_begin <= date) &&
    (!@duration_until || date <= @duration_until)
end
frame_manager() click to toggle source
# File lib/enumdate/date_enumerator.rb, line 97
def frame_manager
  raise NotImplementedError
end
occurrence_in_frame(date) click to toggle source
# File lib/enumdate/date_enumerator.rb, line 101
def occurrence_in_frame(date)
  raise NotImplementedError
end