lib/chronic/repeaters/repeater_season.rb



class Time
  def to_minidate
    MiniDate.new(self.month, self.day)
  end
end

class Season
  attr_reader :start, :end

  def initialize(myStart, myEnd)
    @start = myStart
    @end = myEnd
  end

  def self.find_next_season(season, pointer)
    lookup = {:spring => 0, :summer => 1, :autumn => 2, :winter => 3}
    next_season_num = (lookup[season]+1*pointer) % 4
    lookup.invert[next_season_num]
  end

  def self.season_after(season); find_next_season(season, +1); end
  def self.season_before(season); find_next_season(season, -1); end
end

class MiniDate
  attr_accessor :month, :day

  def initialize(month, day)
    @month = month
    @day = day
  end

  def is_between?(md_start, md_end)
    return true if (@month == md_start.month and @day >= md_start.day) ||
                   (@month == md_end.month and @day <= md_end.day)

    i = md_start.month + 1
    until i == md_end.month
      return true if @month == i
      i = (i+1) % 12
    end

    return false
  end

  def equals?(other)
    @month == other.month and day == other.day
  end
end

class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
  YEAR_SEASONS = 4
  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
  SEASONS = { :spring => Season.new( MiniDate.new(3,20),MiniDate.new(6,20) ),
              :summer => Season.new( MiniDate.new(6,21),MiniDate.new(9,22) ),
              :autumn => Season.new( MiniDate.new(9,23),MiniDate.new(12,21) ),
              :winter => Season.new( MiniDate.new(12,22),MiniDate.new(3,19) ) }

  def initialize(type)
    super
    @next_season_start = nil
  end

  def next(pointer)
    super

    direction = pointer == :future ? 1 : -1
    next_season = Season.find_next_season(find_current_season(@now.to_minidate), direction)

    find_next_season_span(direction, next_season)
  end

  def this(pointer = :future)
    super

    direction = pointer == :future ? 1 : -1

    today = Time.construct(@now.year, @now.month, @now.day)
    this_ssn = find_current_season(@now.to_minidate)
    case pointer
    when :past
      this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
      this_ssn_end = today
    when :future
      this_ssn_start = today + Chronic::RepeaterDay::DAY_SECONDS
      this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
    when :none
      this_ssn_start = today + direction * num_seconds_til_start(this_ssn, direction)
      this_ssn_end = today + direction * num_seconds_til_end(this_ssn, direction)
    end

    Chronic::Span.new(this_ssn_start, this_ssn_end)
  end

  def offset(span, amount, pointer)
    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
  end

  def offset_by(time, amount, pointer)
    direction = pointer == :future ? 1 : -1
    time + amount * direction * SEASON_SECONDS
  end

  def width
    SEASON_SECONDS
  end

  def to_s
    super << '-season'
  end

  private

  def find_next_season_span(direction, next_season)
    if !@next_season_start or !@next_season_end
      @next_season_start = Time.construct(@now.year, @now.month, @now.day)
      @next_season_end = Time.construct(@now.year, @now.month, @now.day)
    end

    @next_season_start += direction * num_seconds_til_start(next_season, direction)
    @next_season_end += direction * num_seconds_til_end(next_season, direction)

    Chronic::Span.new(@next_season_start, @next_season_end)
  end

  def find_current_season(md)
    [:spring, :summer, :autumn, :winter].each do |season|
      return season if md.is_between?(SEASONS[season].start, SEASONS[season].end)
    end
  end

  def num_seconds_til(goal, direction)
    start = Time.construct(@now.year, @now.month, @now.day)
    seconds = 0

    until (start + direction * seconds).to_minidate.equals?(goal)
      seconds += Chronic::RepeaterDay::DAY_SECONDS
    end

    seconds
  end

  def num_seconds_til_start(season_symbol, direction)
    num_seconds_til(SEASONS[season_symbol].start, direction)
  end

  def num_seconds_til_end(season_symbol, direction)
    num_seconds_til(SEASONS[season_symbol].end, direction)
  end
end