# File lib/vpim/rrule.rb, line 142
    def each(dountil = nil) #:yield: ytime
      t = @dtstart.clone

      # Time.to_a => [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]

      # Every event occurs at its start time, but only if the start time is
      # earlier than DOUNTIL...
      if !dountil || t < dountil
        yield t
      end
      count = 1

      # With no recurrence, DTSTART is the only occurrence.
      if !@rrule
        return self
      end

      loop do
        # Build the set of times to yield within this interval (and after
        # DTSTART)

        days  = DaySet.new(t)
        hour  = nil
        min   = nil
        sec   = nil

        # Need to make a Dates class, and make month an instance of it, and add
        # the "intersect" operator.

        case @freq
          #when 'YEARLY' then
          # Don't need to keep track of year, all occurrences are within t's
          # year.
        when 'MONTHLY'  then  days.month = t.month
        when 'WEEKLY'   then  #days.month = t.month
          # TODO - WEEKLY
        when 'DAILY'    then  days.mday = t.month, t.mday
        when 'HOURLY'   then  hour  = [t.hour]
        when 'MINUTELY' then  min   = [t.min]
        when 'SECONDLY' then  sec   = [t.sec]
        end

  #      debug [t, days]
        # Process the BY* modifiers in RFC defined order:
        #  BYMONTH,
        #  BYWEEKNO,
        #  BYYEARDAY,
        #  BYMONTHDAY,
        #  BYDAY,
        #  BYHOUR,
        #  BYMINUTE,
        #  BYSECOND,
        #  BYSETPOS

        bymon = [nil]

        if @by['BYMONTH']
          bymon = @by['BYMONTH'].split(',')
          bymon = bymon.map { |m| m.to_i }
  #        debug bymon

          # In yearly, at  this point, month will always be nil. At other
          # frequencies, it will not.
          days.intersect_bymon(bymon)

  #        debug days
        end

        # TODO - BYWEEKNO

        if @by['BYYEARDAY']
          byyday = @by['BYYEARDAY'].scan(/,?([+-]?[1-9]\d*)/)
  #        debug byyday
          dates = byyearday(t.year, byyday)
          days.intersect_dates(dates)
        end

        if @by['BYMONTHDAY']
          bymday = @by['BYMONTHDAY'].scan(/,?([+-]?[1-9]\d*)/)
  #        debug bymday
          # Generate all days matching this for all months. For yearly, this
          # is what we want, for anything of monthly or higher frequency, it
          # is too many days, but that's OK, since  the month will already
          # be specified and intersection will eliminate the out-of-range
          # dates.
          dates = bymonthday(t.year, bymday)
  #        debug dates
          days.intersect_dates(dates)
  #        debug days
        end

        if @by['BYDAY']
          byday = @by['BYDAY'].scan(/,?([+-]?[1-9]?\d*)?(SU|MO|TU|WE|TH|FR|SA)/i)

          # BYDAY means different things in different frequencies. The +n+
          # is only meaningful when freq is yearly or monthly.

          case @freq
            when 'YEARLY'
              dates = bymon.map { |m| byday_in_monthly(t.year, m, byday) }.flatten
            when 'MONTHLY'
              dates = byday_in_monthly(t.year, t.month, byday)
            when 'WEEKLY'
              dates = byday_in_weekly(t.year, t.month, t.mday, @wkst, byday)
            when 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'
              # Reuse the byday_in_monthly. Current day is already specified,
              # so this will just eliminate the current day if its not allowed
              # in BYDAY.
              dates = byday_in_monthly(t.year, t.month, byday)
          end

  #        debug dates
          days.intersect_dates(dates)
  #        debug days
        end

        # TODO - BYHOUR, BYMINUTE, BYSECOND
        
        hour   = [@dtstart.hour]   if !hour 
        min    = [@dtstart.min]    if !min  
        sec    = [@dtstart.sec]    if !sec 

  #      debug days

        # Generate the yield set so BYSETPOS can be evaluated.
        yset = []

        days.each do |m,d|
          hour.each do |h|
            min.each do |n|
              sec.each do |s|
                y = Time.local(t.year, m, d, h, n, s, 0)

                next if y.hour != h

                yset << y
              end
            end
          end
        end

        if @by['BYSETPOS']
          bysetpos = @by['BYSETPOS'].split(',')
          yset = bysetpos.map do |i|
            i = i.to_i
            case
            when i < 0
              # yset[-1] is last
              yset[i]
            when i > 0
              # yset[1] is first
              yset[i-1]
            else
              # ignore invalid syntax
            end
          end.compact # set positions out of scope will be nil, RFC says ignore them
        end

        # Yield the occurrence, if we haven't gone over COUNT, or past UNTIL, or
        # past the end of representable time.

        yset.each do |y|
          # The generated set can sometimes generate results earlier
          # than the DTSTART, skip them. Also, we already yielded
          # DTSTART, skip it.
          next if y <= @dtstart

          count += 1

          # We are done if current count is past @count.
          if(@count && (count > @count))
            return self
          end

          # We are done if current time is past @until.
          if @until && (y > @until)
            return self
          end
          # We are also done if current time is past the
          # caller-requested until.
          if dountil && (y >= dountil)
            return self
          end
          yield y
        end

        # Add @interval to @freq component

        # Note - when we got past representable time, the error is:
        #   time out of range (ArgumentError)
        # Finish when we see this.
        begin
          case @freq
            when 'YEARLY' then
              t = t.plus_year(@interval)

            when 'MONTHLY' then
              t = t.plus_month(@interval)

            when 'WEEKLY' then
              t = t.plus_day(@interval * 7)

            when 'DAILY' then
              t = t.plus_day(@interval)

            when 'HOURLY' then
              t += @interval * 60 * 60

            when 'MINUTELY' then
              t += @interval * 60

            when 'SECONDLY' then
              t += @interval

            when nil
              return self
          end
        rescue ArgumentError
          return self if $!.message =~ /^time out of range$/

          raise ArgumentError, "#{$!.message} while adding interval to #{t.inspect}"
        end

        return self if dountil && (t > dountil)
      end
    end