require 'active_support/time'
require File.dirname(__FILE__) + '/../spec_helper'

describe IceCube::Schedule do

  it 'should work with a simple schedule' do
    rule = IceCube::Rule.daily.day(:monday)
    schedule = IceCube::Schedule.new(Time.now)
    schedule.add_recurrence_rule rule
    expect { schedule.first(3) }.not_to raise_error
  end

  it 'should respond to complex combinations (1)' do
    start_time = Time.utc(2010, 1, 1)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.yearly(2).day(:wednesday).month_of_year(:april)
    #check assumptions
    dates = schedule.occurrences(Time.utc(2011, 12, 31)) #two years
    expect(dates.size).to eq(4)
    dates.each do |date|
      expect(date.wday).to eq(3)
      expect(date.month).to eq(4)
      expect(date.year).to eq(start_time.year) #since we're doing every other
    end
  end

  it 'should return an added occurrence time' do
    schedule = IceCube::Schedule.new(t0 = Time.now)
    schedule.add_recurrence_time(t0 + 2)
    expect(schedule.occurrences(t0 + 50)).to eq([t0, t0 + 2])
  end

  it 'should not return an occurrence time that is excluded' do
    schedule = IceCube::Schedule.new(t0 = Time.now)
    schedule.add_recurrence_time(t0 + 2)
    schedule.add_exception_time(t0 + 2)
    expect(schedule.occurrences(t0 + 50)).to eq([t0])
  end

  it 'should return properly with a combination of a recurrence and exception rule' do
    schedule = IceCube::Schedule.new(DAY)
    schedule.add_recurrence_rule IceCube::Rule.daily # every day
    schedule.add_exception_rule IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday) # except these
    #check assumption - in 2 weeks, we should have 8 days
    expect(schedule.occurrences(DAY + 13 * IceCube::ONE_DAY).size).to eq(8)
  end

  it 'should be able to exclude a certain date from a range' do
    start_time = Time.local 2012, 3, 1
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    schedule.add_exception_time(start_time + 1 * IceCube::ONE_DAY) # all days except tomorrow
    # check assumption
    dates = schedule.occurrences(start_time + 13 * IceCube::ONE_DAY) # 2 weeks
    expect(dates.size).to eq(13) # 2 weeks minus 1 day
    expect(dates).not_to include(start_time + 1 * IceCube::ONE_DAY)
  end

  it 'make a schedule with a start_time not included in a rule, and make sure that count behaves properly' do
    start_time = WEDNESDAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5)
    dates = schedule.all_occurrences
    expect(dates.uniq.size).to eq(5)
    dates.each { |d| expect(d.wday).to eq(4) }
    expect(dates).not_to include(WEDNESDAY)
  end

  it 'make a schedule with a start_time included in a rule, and make sure that count behaves properly' do
    start_time = WEDNESDAY + IceCube::ONE_DAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.weekly.day(:thursday).count(5)
    dates = schedule.all_occurrences
    expect(dates.uniq.size).to eq(5)
    dates.each { |d| expect(d.wday).to eq(4) }
    expect(dates).to include(WEDNESDAY + IceCube::ONE_DAY)
  end

  it 'should work as expected with a second_of_minute rule specified' do
    start_time = DAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.weekly.second_of_minute(30)
    dates = schedule.occurrences(start_time + 30 * 60)
    dates.each { |date| expect(date.sec).to eq(30) }
  end

  it 'ensure that when count on a rule is set to 0, 0 occurrences come back' do
    start_time = DAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.count(0)
    expect(schedule.all_occurrences).to eq([])
  end

  it 'should be able to schedule at hour 1,2 with start min/sec every day' do
    start_time = Time.utc(2007, 9, 2, 9, 15, 25)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).count(6)
    dates = schedule.all_occurrences
    expect(dates).to eq([Time.utc(2007, 9, 3, 1, 15, 25), Time.utc(2007, 9, 3, 2, 15, 25),
                     Time.utc(2007, 9, 4, 1, 15, 25), Time.utc(2007, 9, 4, 2, 15, 25),
                     Time.utc(2007, 9, 5, 1, 15, 25), Time.utc(2007, 9, 5, 2, 15, 25)])
  end

  it 'should be able to schedule at hour 1,2 at min 0 with start sec every day' do
    start_time = Time.utc(2007, 9, 2, 9, 15, 25)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.hour_of_day(1, 2).minute_of_hour(0).count(6)
    dates = schedule.all_occurrences
    expect(dates).to eq([Time.utc(2007, 9, 3, 1, 0, 25), Time.utc(2007, 9, 3, 2, 0, 25),
                     Time.utc(2007, 9, 4, 1, 0, 25), Time.utc(2007, 9, 4, 2, 0, 25),
                     Time.utc(2007, 9, 5, 1, 0, 25), Time.utc(2007, 9, 5, 2, 0, 25)])
  end

  it 'will only return count# if you specify a count and use .first' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.count(10)
    dates = schedule.first(200)
    expect(dates.size).to eq(10)
  end

  it 'occurs yearly' do
    start_time = DAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.yearly
    dates = schedule.first(10)
    dates.each do |date|
      expect(date.month).to eq(start_time.month)
      expect(date.day).to eq(start_time.day)
      expect(date.hour).to eq(start_time.hour)
      expect(date.min).to eq(start_time.min)
      expect(date.sec).to eq(start_time.sec)
    end
  end

  it 'occurs daily' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    dates = schedule.first(10)
    dates.each do |date|
      expect(date.hour).to eq(start_time.hour)
      expect(date.min).to eq(start_time.min)
      expect(date.sec).to eq(start_time.sec)
    end
  end

  it 'occurs hourly' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.hourly
    dates = schedule.first(10)
    dates.each do |date|
      expect(date.min).to eq(start_time.min)
      expect(date.sec).to eq(start_time.sec)
    end
  end

  it 'occurs minutely' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.minutely
    dates = schedule.first(10)
    dates.each do |date|
      expect(date.sec).to eq(start_time.sec)
    end
  end

  it 'occurs every second for an hour' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.secondly.count(60)
    # build the expectation list
    expectation = []
    0.upto(59) { |i| expectation << start_time + i }
    # compare with what we get
    dates = schedule.all_occurrences
    expect(dates.size).to eq(60)
    expect(schedule.all_occurrences).to eq(expectation)
  end

  it 'perform a every day LOCAL and make sure we get back LOCAL' do
    Time.zone = 'Eastern Time (US & Canada)'
    start_time = Time.zone.local(2010, 9, 2, 5, 0, 0)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    schedule.first(10).each do |d|
      expect(d.utc?).to eq(false)
      expect(d.hour).to eq(5)
      expect(d.utc_offset == -5 * IceCube::ONE_HOUR || d.utc_offset == -4 * IceCube::ONE_HOUR).to be_truthy
    end
  end

  it 'perform a every day LOCAL and make sure we get back LOCAL' do
    start_time = Time.utc(2010, 9, 2, 5, 0, 0)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    schedule.first(10).each do |d|
      expect(d.utc?).to eq(true)
      expect(d.utc_offset).to eq(0)
      expect(d.hour).to eq(5)
    end
  end

  # here we purposely put a UTC time that is before the range ends, to
  # verify ice_cube is properly checking until bounds
  it 'works with a until date that is UTC, but the start date is local' do
    Time.zone = 'Eastern Time (US & Canada)'
    start_time = Time.zone.local(2010, 11, 6, 5, 0, 0)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.utc(2010, 11, 10, 8, 0, 0)) #4 o clocal local
    #check assumptions
    dates = schedule.all_occurrences
    dates.each { |d| expect(d.utc?).to eq(false) }
    expect(dates).to eq([Time.zone.local(2010, 11, 6, 5, 0, 0),
      Time.zone.local(2010, 11, 7, 5, 0, 0), Time.zone.local(2010, 11, 8, 5, 0, 0),
      Time.zone.local(2010, 11, 9, 5, 0, 0)])
  end

  # here we purposely put a local time that is before the range ends, to
  # verify ice_cube is properly checking until bounds
  it 'works with a until date that is local, but the start date is UTC' do
    start_time = Time.utc(2010, 11, 6, 5, 0, 0)
    Time.zone = 'Eastern Time (US & Canada)'
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(Time.zone.local(2010, 11, 9, 23, 0, 0)) #4 o UTC time
    #check assumptions
    dates = schedule.all_occurrences
    dates.each { |d| expect(d.utc?).to eq(true) }
    expect(dates).to eq([Time.utc(2010, 11, 6, 5, 0, 0),
      Time.utc(2010, 11, 7, 5, 0, 0), Time.utc(2010, 11, 8, 5, 0, 0),
      Time.utc(2010, 11, 9, 5, 0, 0)])
  end

  WORLD_TIME_ZONES.each do |zone|
    context "in #{zone}", :system_time_zone => zone do
      it 'works with a until date that is a Date, but the start date is UTC' do
        start_time = Time.utc(2016, 1, 1, 0, 0, 0)
        schedule = IceCube::Schedule.new(start_time)
        schedule.add_recurrence_rule IceCube::Rule.daily.until(Date.new(2016, 1, 2))
        times = schedule.all_occurrences
        expect(times).to eq [
                          Time.utc(2016, 1, 1, 0, 0, 0),
                          Time.utc(2016, 1, 2, 0, 0, 0)
                        ]
      end
    end
  end

  it 'works with a monthly rule iterating on UTC' do
    start_time = Time.utc(2010, 4, 24, 15, 45, 0)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.monthly
    dates = schedule.first(10)
    dates.each do |d|
      expect(d.day).to eq(24)
      expect(d.hour).to eq(15)
      expect(d.min).to eq(45)
      expect(d.sec).to eq(0)
      expect(d.utc?).to be_truthy
    end
  end

  it 'can retrieve rrules from a schedule' do
    schedule = IceCube::Schedule.new(Time.now)
    rules = [IceCube::Rule.daily, IceCube::Rule.monthly, IceCube::Rule.yearly]
    rules.each { |r| schedule.add_recurrence_rule(r) }
    # pull the rules back out of the schedule and compare
    expect(schedule.rrules).to eq(rules)
  end

  it 'can retrieve exrules from a schedule' do
    schedule = IceCube::Schedule.new(Time.now)
    rules = [IceCube::Rule.daily, IceCube::Rule.monthly, IceCube::Rule.yearly]
    rules.each { |r| schedule.add_exception_rule(r) }
    # pull the rules back out of the schedule and compare
    expect(schedule.exrules).to eq(rules)
  end

  it 'can retrieve recurrence times from a schedule' do
    schedule = IceCube::Schedule.new(Time.now)
    times = [Time.now, Time.now + 5, Time.now + 10]
    times.each { |d| schedule.add_recurrence_time(d) }
    # pull the dates back out of the schedule and compare
    expect(schedule.rtimes).to eq(times)
  end

  it 'can retrieve exception_times from a schedule' do
    schedule = IceCube::Schedule.new(Time.now)
    times = [Time.now, Time.now + 5, Time.now + 10]
    times.each { |d| schedule.add_exception_time(d) }
    # pull the dates back out of the schedule and compare
    expect(schedule.extimes).to eq(times)
  end

  it 'can reuse the same rule' do
    schedule = IceCube::Schedule.new(Time.now)
    rule = IceCube::Rule.daily
    schedule.add_recurrence_rule rule
    result1 = schedule.first(10)
    rule.day(:monday)
    # check to make sure the change affected the rule
    expect(schedule.first(10)).not_to eq(result1)
  end

  it 'ensures that month of year (3) is march' do
    schedule = IceCube::Schedule.new(DAY)
    schedule.add_recurrence_rule IceCube::Rule.daily.month_of_year(:march)

    schedule2 = IceCube::Schedule.new(DAY)
    schedule2.add_recurrence_rule IceCube::Rule.daily.month_of_year(3)

    expect(schedule.first(10)).to eq(schedule2.first(10))
  end

  it 'ensures that day of week (1) is monday' do
    schedule = IceCube::Schedule.new(DAY)
    schedule.add_recurrence_rule IceCube::Rule.daily.day(:monday)

    schedule2 = IceCube::Schedule.new(DAY)
    schedule2.add_recurrence_rule IceCube::Rule.daily.day(1)

    expect(schedule.first(10)).to eq(schedule2.first(10))
  end

  it 'should be able to find occurrences between two dates which are both in the future' do
    start_time = Time.local(2012, 5, 1)
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    dates = schedule.occurrences_between(start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 4)
    expect(dates).to eq([start_time + IceCube::ONE_DAY * 2, start_time + IceCube::ONE_DAY * 3, start_time + IceCube::ONE_DAY * 4])
  end

  it 'should be able to tell us when there is at least one occurrence between two dates' do
    start_time = WEDNESDAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday)
    expect(true).to eq(schedule.occurs_between?(start_time, start_time + IceCube::ONE_DAY * 3))
  end

  it 'should be able to tell us when there is no occurrence between two dates' do
    start_time = WEDNESDAY
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday)
    expect(false).to eq(schedule.occurs_between?(start_time, start_time + IceCube::ONE_DAY))
  end

  it 'should be able to get back rtimes from a schedule' do
    schedule = IceCube::Schedule.new DAY
    schedule.add_recurrence_time DAY
    schedule.add_recurrence_time(DAY + 2)
    expect(schedule.rtimes).to eq([DAY, DAY + 2])
  end

  it 'should be able to get back exception times from a schedule' do
    schedule = IceCube::Schedule.new DAY
    schedule.add_exception_time DAY
    schedule.add_exception_time(DAY + 2)
    expect(schedule.extimes).to eq([DAY, DAY + 2])
  end

  it 'should allow calling of .first on a schedule with no arguments' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_time start_time
    expect(schedule.first).to eq(start_time)
  end

  it 'should be able to ignore nil dates that are inserted as part of a collection to add_recurrence_time' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_time start_time
    schedule.add_recurrence_time start_time + IceCube::ONE_DAY
    schedule.add_recurrence_time nil
    expect(schedule.all_occurrences).to eq([start_time, start_time + IceCube::ONE_DAY])
  end

  it 'should be able to use all_occurrences with no rules' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_time start_time
    expect do
      expect(schedule.all_occurrences).to eq([start_time])
    end.not_to raise_error
  end

  it 'should use occurs_at? when calling occurring_at? with no duration' do
    schedule = IceCube::Schedule.new
    expect(schedule).to receive(:occurs_at?)
    schedule.occurring_at?(Time.now)
  end

  it 'should be able to specify a duration on a schedule use occurring_at? on the schedule
      to find out if a given time is included' do
    start_time = Time.local 2010, 5, 6, 10, 0, 0
    schedule = IceCube::Schedule.new(start_time, :duration => 3600)
    schedule.add_recurrence_rule IceCube::Rule.daily
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 30, 0))).to be_truthy #true
  end

  it 'should be able to specify a duration on a schedule and use occurring_at? on that schedule
      to make sure a time is not included' do
    start_time = Time.local 2010, 5, 6, 10, 0, 0
    schedule = IceCube::Schedule.new(start_time, :duration => 3600)
    schedule.add_recurrence_rule IceCube::Rule.daily
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 9, 59, 0))).to be_falsey
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 11, 0, 0))).to be_falsey
  end

  it 'should be able to specify a duration on a schedule and use occurring_at? on that schedule
      to make sure the outer bounds are included' do
    start_time = Time.local 2010, 5, 6, 10, 0, 0
    schedule = IceCube::Schedule.new(start_time, :duration => 3600)
    schedule.add_recurrence_rule IceCube::Rule.daily
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 0, 0))).to be_truthy
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 59, 59))).to be_truthy
  end

  it 'should be able to explicity remove a certain minute from a duration' do
    start_time = Time.local 2010, 5, 6, 10, 0, 0
    schedule = IceCube::Schedule.new(start_time, :duration => 3600)
    schedule.add_recurrence_rule IceCube::Rule.daily
    schedule.add_exception_time Time.local(2010, 5, 6, 10, 21, 30)
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 29))).to be_truthy
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 30))).to be_falsey
    expect(schedule.occurring_at?(Time.local(2010, 5, 6, 10, 21, 31))).to be_truthy
  end

  it 'should be able to specify an end time for the schedule' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time)
    expect(schedule.all_occurrences).to eq([DAY, DAY + 1*IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY])
  end

  it 'should be able to specify an end time for the schedule and only get those on .first' do
    start_time = DAY
    # ensure proper response without the end time
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    expect(schedule.first(5)).to eq([DAY, DAY + 1*IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY, DAY + 3*IceCube::ONE_DAY, DAY + 4*IceCube::ONE_DAY])
    # and then ensure that with the end time it stops it at the right day
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(DAY + IceCube::ONE_DAY * 2 + 1)
    expect(schedule.first(5)).to eq([DAY, DAY + 1 * IceCube::ONE_DAY, DAY + 2 * IceCube::ONE_DAY])
  end

  it 'should be able to specify an end date and go to/from yaml' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time, :end_time => end_time)
    schedule.add_recurrence_rule IceCube::Rule.daily
    schedule2 = IceCube::Schedule.from_yaml schedule.to_yaml
    expect(schedule2.end_time).to eq(end_time)
  end

  it 'should be able to specify an end date for the schedule and only get those on .occurrences_between' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time)
    expectation = [DAY, DAY + IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY]
    expect(schedule.occurrences_between(start_time - IceCube::ONE_DAY, start_time + 4 * IceCube::ONE_DAY)).to eq(expectation)
  end

  it 'should be able to specify an end date for the schedule and only get those on .occurrences' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time)
    expectation = [DAY, DAY + IceCube::ONE_DAY, DAY + 2*IceCube::ONE_DAY]
    expect(schedule.occurrences(start_time + 4 * IceCube::ONE_DAY)).to eq(expectation)
  end

  it 'should be able to work with an end date and .occurs_at' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time)
    expect(schedule.occurs_at?(DAY + 4*IceCube::ONE_DAY)).to be_falsey # out of range
  end

  it 'should be able to work with an end date and .occurring_at' do
    start_time = DAY
    end_time = DAY + IceCube::ONE_DAY * 2
    schedule = IceCube::Schedule.new(start_time, :duration => 20)
    schedule.add_recurrence_rule IceCube::Rule.daily.until(end_time)
    expect(schedule.occurring_at?((DAY + 2*IceCube::ONE_DAY + 10))).to be_truthy # in range
    expect(schedule.occurring_at?((DAY + 4*IceCube::ONE_DAY + 10))).to be_falsey # out of range
  end

  it 'should not create an infinite loop crossing over february - github issue 6' do
    schedule = IceCube::Schedule.new(Time.parse('2010-08-30'))
    schedule.add_recurrence_rule IceCube::Rule.monthly(6)
    schedule.occurrences_between(Time.parse('2010-07-01'), Time.parse('2010-09-01'))
  end

  it 'should be able to exist on the 28th of each month crossing over february - github issue 6a' do
    schedule = IceCube::Schedule.new(Time.local(2010, 1, 28))
    schedule.add_recurrence_rule IceCube::Rule.monthly
    expect(schedule.first(3)).to eq([Time.local(2010, 1, 28), Time.local(2010, 2, 28), Time.local(2010, 3, 28)])
  end

  it 'should be able to exist on the 29th of each month crossing over february - github issue 6a' do
    schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 29))
    schedule.add_recurrence_rule IceCube::Rule.monthly
    expect(schedule.first(3)).to eq([Time.zone.local(2010, 1, 29), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 29)])
  end

  it 'should be able to exist on the 30th of each month crossing over february - github issue 6a' do
    schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 30))
    schedule.add_recurrence_rule IceCube::Rule.monthly
    expect(schedule.first(3)).to eq([Time.zone.local(2010, 1, 30), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 30)])
  end

  it 'should be able to exist ont he 31st of each month crossing over february - github issue 6a' do
    schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 31))
    schedule.add_recurrence_rule IceCube::Rule.monthly
    expect(schedule.first(3)).to eq([Time.zone.local(2010, 1, 31), Time.zone.local(2010, 2, 28), Time.zone.local(2010, 3, 31)])
  end

  it 'should deal with a yearly rule that has februaries with different mdays' do
    schedule = IceCube::Schedule.new(Time.local(2008, 2, 29))
    schedule.add_recurrence_rule IceCube::Rule.yearly
    expect(schedule.first(3)).to eq([Time.local(2008, 2, 29), Time.local(2009, 2, 28), Time.local(2010, 2, 28)])
  end

  it 'should work with every other month even when the day of the month iterating on does not exist' do
    schedule = IceCube::Schedule.new(Time.zone.local(2010, 1, 31))
    schedule.add_recurrence_rule IceCube::Rule.monthly(2)
    expect(schedule.first(6)).to eq([Time.zone.local(2010, 1, 31), Time.zone.local(2010, 3, 31), Time.zone.local(2010, 5, 31), Time.zone.local(2010, 7, 31), Time.zone.local(2010, 9, 30), Time.zone.local(2010, 11, 30)])
  end

  it 'should be able to go into february and stay on the same day' do
    schedule = IceCube::Schedule.new(Time.local(2010, 1, 5))
    schedule.add_recurrence_rule IceCube::Rule.monthly
    expect(schedule.first(2)).to eq([Time.local(2010, 1, 5), Time.local(2010, 2, 5)])
  end

  it 'should be able to know when to stop with an end date and a rule that misses a few times' do
    schedule = IceCube::Schedule.new(Time.local(2010, 2, 29))
    schedule.add_recurrence_rule IceCube::Rule.yearly.until(Time.local(2010, 10, 30))
    expect(schedule.first(10)).to eq([Time.local(2010, 2, 29)])
  end

  it 'should be able to know when to stop with an end date and a rule that misses a few times' do
    schedule = IceCube::Schedule.new(Time.local(2010, 2, 29))
    schedule.add_recurrence_rule IceCube::Rule.yearly.until(Time.local(2010, 10, 30))
    expect(schedule.first(10)).to eq([Time.local(2010, 2, 29)])
  end

  it 'should be able to know when to stop with an end date and a rule that misses a few times' do
    schedule = IceCube::Schedule.new(Time.local(2010, 2, 29))
    schedule.add_recurrence_rule IceCube::Rule.yearly.count(1)
    expect(schedule.first(10)).to eq([Time.local(2010, 2, 29)])
  end

  it 'should have some convenient aliases' do
    start_time = Time.now
    schedule = IceCube::Schedule.new(start_time)

    expect(schedule.start_time).to eq(schedule.start_time)
    expect(schedule.end_time).to eq(schedule.end_time)
  end

  it 'should have some convenient alias for rrules' do
    schedule = IceCube::Schedule.new(Time.now)
    daily = IceCube::Rule.daily; monthly = IceCube::Rule.monthly
    schedule.add_recurrence_rule daily
    schedule.rrule monthly
    expect(schedule.rrules).to eq([daily, monthly])
  end

  it 'should have some convenient alias for exrules' do
    schedule = IceCube::Schedule.new(Time.now)
    daily = IceCube::Rule.daily; monthly = IceCube::Rule.monthly
    schedule.add_exception_rule daily
    schedule.exrule monthly
    expect(schedule.exrules).to eq([daily, monthly])
  end

  it 'should have some convenient alias for recurrence_times' do
    schedule = IceCube::Schedule.new(Time.now)
    schedule.add_recurrence_time Time.local(2010, 8, 13)
    schedule.rtime Time.local(2010, 8, 14)
    expect(schedule.rtimes).to eq([Time.local(2010, 8, 13), Time.local(2010, 8, 14)])
  end

  it 'should have some convenient alias for extimes' do
    schedule = IceCube::Schedule.new(Time.now)
    schedule.add_exception_time Time.local(2010, 8, 13)
    schedule.extime Time.local(2010, 8, 14)
    expect(schedule.extimes).to eq([Time.local(2010, 8, 13), Time.local(2010, 8, 14)])
  end

  it 'should be able to have a rule and an exrule' do
    schedule = IceCube::Schedule.new(Time.local(2010, 8, 27, 10))
    schedule.rrule IceCube::Rule.daily
    schedule.exrule IceCube::Rule.daily.day(:friday)
    expect(schedule.occurs_on?(Date.new(2010, 8, 27))).to be_falsey
    expect(schedule.occurs_on?(Date.new(2010, 8, 28))).to be_truthy
  end

  it 'should always generate the correct number of days for .first' do
    s = IceCube::Schedule.new(Time.zone.parse('1-1-1985'))
    r = IceCube::Rule.weekly(3).day(:monday, :wednesday, :friday)
    s.add_recurrence_rule(r)
    # test sizes
    expect(s.first(3).size).to eq(3)
    expect(s.first(4).size).to eq(4)
    expect(s.first(5).size).to eq(5)
  end

  it 'should use current date as start date when invoked with a nil parameter' do
    schedule = IceCube::Schedule.new nil
    expect(Time.now - schedule.start_time).to be < 100
  end

  it 'should be able to get the occurrence count for a rule' do
    rule = IceCube::Rule.daily.count(5)
    expect(rule.occurrence_count).to eq(5)
  end

  it 'should be able to remove a count validation from a rule' do
    rule = IceCube::Rule.daily.count(5)
    expect(rule.occurrence_count).to eq(5)
    rule.count(nil)
    expect(rule.occurrence_count).to be_nil
  end

  it 'should be able to remove a count validation from a rule' do
    rule = IceCube::Rule.daily.count(5)
    expect(rule.to_hash[:count]).to eq(5)
    rule.count nil
    expect(rule.to_hash[:count]).to be_nil
  end

  it 'should be able to remove an until validation from a rule' do
    rule = IceCube::Rule.daily.until(Time.now + IceCube::ONE_DAY)
    expect(rule.to_hash[:until]).not_to be_nil
    rule.until nil
    expect(rule.to_hash).not_to have_key(:until)
  end

  it 'should not have ridiculous load times for minutely on next_occurrence (from sidetiq)' do
    quick_attempt_test do
      IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s|
        s.add_recurrence_rule(IceCube::Rule.minutely(1800))
      end
    end
  end

  it 'should not have ridiculous load times for every 10 on next_occurrence #210' do
    quick_attempt_test do
      IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s|
        s.add_recurrence_rule(IceCube::Rule.hourly.minute_of_hour(0, 10, 20, 30, 40, 50))
      end
    end
    quick_attempt_test do
      IceCube::Schedule.new(Time.utc(2010, 1, 1)) do |s|
        s.add_recurrence_rule(IceCube::Rule.daily)
      end
    end
  end

  def quick_attempt_test
    time = Time.now
    10.times do
      (yield).next_occurrence(Time.now)
    end
    total = Time.now - time
    expect(total).to be < 0.1
  end

end
