ice_cube 0.6.14 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/ice_cube.rb +63 -37
 - data/lib/ice_cube/builders/hash_builder.rb +27 -0
 - data/lib/ice_cube/builders/ical_builder.rb +59 -0
 - data/lib/ice_cube/builders/string_builder.rb +74 -0
 - data/lib/ice_cube/errors/count_exceeded.rb +7 -0
 - data/lib/ice_cube/errors/until_exceeded.rb +7 -0
 - data/lib/ice_cube/rule.rb +85 -147
 - data/lib/ice_cube/rules/daily_rule.rb +5 -27
 - data/lib/ice_cube/rules/hourly_rule.rb +6 -26
 - data/lib/ice_cube/rules/minutely_rule.rb +5 -25
 - data/lib/ice_cube/rules/monthly_rule.rb +6 -30
 - data/lib/ice_cube/rules/secondly_rule.rb +5 -26
 - data/lib/ice_cube/rules/weekly_rule.rb +5 -36
 - data/lib/ice_cube/rules/yearly_rule.rb +8 -34
 - data/lib/ice_cube/schedule.rb +257 -229
 - data/lib/ice_cube/single_occurrence_rule.rb +28 -0
 - data/lib/ice_cube/time_util.rb +202 -76
 - data/lib/ice_cube/validated_rule.rb +107 -0
 - data/lib/ice_cube/validations/count.rb +56 -0
 - data/lib/ice_cube/validations/daily_interval.rb +51 -0
 - data/lib/ice_cube/validations/day.rb +45 -31
 - data/lib/ice_cube/validations/day_of_month.rb +44 -44
 - data/lib/ice_cube/validations/day_of_week.rb +60 -47
 - data/lib/ice_cube/validations/day_of_year.rb +48 -44
 - data/lib/ice_cube/validations/hour_of_day.rb +42 -30
 - data/lib/ice_cube/validations/hourly_interval.rb +50 -0
 - data/lib/ice_cube/validations/lock.rb +47 -0
 - data/lib/ice_cube/validations/minute_of_hour.rb +42 -31
 - data/lib/ice_cube/validations/minutely_interval.rb +50 -0
 - data/lib/ice_cube/validations/month_of_year.rb +39 -30
 - data/lib/ice_cube/validations/monthly_interval.rb +47 -0
 - data/lib/ice_cube/validations/schedule_lock.rb +41 -0
 - data/lib/ice_cube/validations/second_of_minute.rb +39 -30
 - data/lib/ice_cube/validations/secondly_interval.rb +50 -0
 - data/lib/ice_cube/validations/until.rb +49 -0
 - data/lib/ice_cube/validations/weekly_interval.rb +50 -0
 - data/lib/ice_cube/validations/yearly_interval.rb +45 -0
 - data/lib/ice_cube/version.rb +2 -2
 - data/spec/spec_helper.rb +13 -0
 - metadata +50 -9
 - data/lib/ice_cube/rule_occurrence.rb +0 -94
 - data/lib/ice_cube/validation.rb +0 -44
 - data/lib/ice_cube/validation_types.rb +0 -137
 
| 
         @@ -0,0 +1,51 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module IceCube
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              module Validations::DailyInterval
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                # Add a new interval validation
         
     | 
| 
      
 6 
     | 
    
         
            +
                def interval(interval)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  validations_for(:interval) << Validation.new(interval)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  clobber_base_validations(:wday, :day)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  self
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # A validation for checking to make sure that a time
         
     | 
| 
      
 13 
     | 
    
         
            +
                # is inside of a certain DailyInterval
         
     | 
| 
      
 14 
     | 
    
         
            +
                class Validation
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  attr_reader :interval
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def initialize(interval)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @interval = interval
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def build_s(builder)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    builder.base = interval == 1 ? 'Daily' : "Every #{interval} days"
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def build_hash(builder)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    builder.validations[:interval] = interval
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def build_ical(builder)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    builder['FREQ'] << 'DAILY'
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 35 
     | 
    
         
            +
                    :day
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def validate(time, schedule)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    time_date = Date.new(time.year, time.month, time.day)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    start_date = Date.new(schedule.start_time.year, schedule.start_time.month, schedule.start_time.day)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    days = time_date - start_date
         
     | 
| 
      
 42 
     | 
    
         
            +
                    unless days % interval === 0
         
     | 
| 
      
 43 
     | 
    
         
            +
                      interval - (days % interval)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,41 +1,55 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            require 'date'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            module IceCube
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
              
         
     | 
| 
       7 
     | 
    
         
            -
                def initialize(rule)
         
     | 
| 
       8 
     | 
    
         
            -
                  @days = rule.validations[:day]
         
     | 
| 
       9 
     | 
    
         
            -
                  @rule = rule
         
     | 
| 
       10 
     | 
    
         
            -
                end
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Validations::Day
         
     | 
| 
       11 
6 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
                def  
     | 
| 
       13 
     | 
    
         
            -
                   
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
              
         
     | 
| 
       17 
     | 
    
         
            -
                def closest(date)
         
     | 
| 
       18 
     | 
    
         
            -
                  return nil if !@days || @days.empty?
         
     | 
| 
       19 
     | 
    
         
            -
                  # turn days into distances
         
     | 
| 
       20 
     | 
    
         
            -
                  days = @days.map do |d| 
         
     | 
| 
       21 
     | 
    
         
            -
                    d > date.wday ? (d - date.wday) : (7 - date.wday + d)
         
     | 
| 
      
 7 
     | 
    
         
            +
                def day(*days)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  days.each do |day|
         
     | 
| 
      
 9 
     | 
    
         
            +
                    day = TimeUtil.symbol_to_day(day) if day.is_a?(Symbol)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    validations_for(:day) << Validation.new(day)
         
     | 
| 
       22 
11 
     | 
    
         
             
                  end
         
     | 
| 
       23 
     | 
    
         
            -
                   
     | 
| 
       24 
     | 
    
         
            -
                   
     | 
| 
       25 
     | 
    
         
            -
                  goal = date + days.min * IceCube::ONE_DAY
         
     | 
| 
       26 
     | 
    
         
            -
                  self.class.adjust(goal, date)
         
     | 
| 
       27 
     | 
    
         
            -
                end
         
     | 
| 
       28 
     | 
    
         
            -
              
         
     | 
| 
       29 
     | 
    
         
            -
                def to_s
         
     | 
| 
       30 
     | 
    
         
            -
                  days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
         
     | 
| 
       31 
     | 
    
         
            -
                  'on ' << self.class.sentence(days_dup.map { |d| Date::DAYNAMES[d] + 's' }) unless days_dup.empty?
         
     | 
| 
      
 12 
     | 
    
         
            +
                  clobber_base_validations(:wday, :day)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self
         
     | 
| 
       32 
14 
     | 
    
         
             
                end
         
     | 
| 
       33 
15 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                   
     | 
| 
      
 16 
     | 
    
         
            +
                class Validation
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  include Validations::Lock
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  attr_reader :day
         
     | 
| 
      
 21 
     | 
    
         
            +
                  alias :value :day
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(day)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @day = day
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def build_s(builder)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    builder.piece(:day) << "#{Date::DAYNAMES[day]}s"
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def build_hash(builder)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    builder.validations_array(:day) << day
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def build_ical(builder)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    ical_day = IcalBuilder.fixnum_to_ical_day(day)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # Only add if there aren't others from day_of_week that override
         
     | 
| 
      
 38 
     | 
    
         
            +
                    if builder['BYDAY'].none? { |b| b.end_with?(ical_day) }
         
     | 
| 
      
 39 
     | 
    
         
            +
                      builder['BYDAY'] << ical_day
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 44 
     | 
    
         
            +
                    :wday
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  StringBuilder.register_formatter(:day) do |segments|
         
     | 
| 
      
 48 
     | 
    
         
            +
                    "on #{StringBuilder.sentence(segments)}"
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       37 
51 
     | 
    
         
             
                end
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
       39 
53 
     | 
    
         
             
              end
         
     | 
| 
       40 
54 
     | 
    
         | 
| 
       41 
55 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,52 +1,52 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module IceCube
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
               
     | 
| 
      
 3 
     | 
    
         
            +
              module Validations::DayOfMonth
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                def  
     | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
                
         
     | 
| 
       11 
     | 
    
         
            -
                def validate(date)
         
     | 
| 
       12 
     | 
    
         
            -
                  return true if !@days_of_month || @days_of_month.empty?
         
     | 
| 
       13 
     | 
    
         
            -
                  @days_of_month.include?(date.mday) || @days_of_month.include?(date.mday - TimeUtil.days_in_month(date) - 1)
         
     | 
| 
       14 
     | 
    
         
            -
                end
         
     | 
| 
       15 
     | 
    
         
            -
              
         
     | 
| 
       16 
     | 
    
         
            -
                def closest(date)
         
     | 
| 
       17 
     | 
    
         
            -
                  return nil if !@days_of_month || @days_of_month.empty?
         
     | 
| 
       18 
     | 
    
         
            -
                  #get some variables we need
         
     | 
| 
       19 
     | 
    
         
            -
                  days_in_month = TimeUtil.days_in_month(date)
         
     | 
| 
       20 
     | 
    
         
            -
                  days_left_in_this_month = days_in_month - date.mday
         
     | 
| 
       21 
     | 
    
         
            -
                  next_month, next_year = date.month == 12 ? [1, date.year + 1] : [date.month + 1, date.year] #clean way to wrap over years
         
     | 
| 
       22 
     | 
    
         
            -
                  days_in_next_month = TimeUtil.days_in_month(Time.utc(next_year, next_month, 1))
         
     | 
| 
       23 
     | 
    
         
            -
                  # create a list of distances
         
     | 
| 
       24 
     | 
    
         
            -
                  distances = []
         
     | 
| 
       25 
     | 
    
         
            -
                  @days_of_month.each do |d|
         
     | 
| 
       26 
     | 
    
         
            -
                    if d > 0
         
     | 
| 
       27 
     | 
    
         
            -
                      distances << d - date.mday #today is 1, we want 20 (19)
         
     | 
| 
       28 
     | 
    
         
            -
                      distances << days_left_in_this_month + d #(364 + 20)
         
     | 
| 
       29 
     | 
    
         
            -
                    elsif d < 0
         
     | 
| 
       30 
     | 
    
         
            -
                      distances << (days_in_month + d + 1) - date.mday #today is 30, we want -1
         
     | 
| 
       31 
     | 
    
         
            -
                      distances << (days_in_next_month + d + 1) + days_left_in_this_month #today is 300, we want -70
         
     | 
| 
       32 
     | 
    
         
            -
                    end
         
     | 
| 
      
 5 
     | 
    
         
            +
                include Validations::Lock
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def day_of_month(*days)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  days.each do |day|
         
     | 
| 
      
 9 
     | 
    
         
            +
                    validations_for(:day_of_month) << Validation.new(day)
         
     | 
| 
       33 
10 
     | 
    
         
             
                  end
         
     | 
| 
       34 
     | 
    
         
            -
                   
     | 
| 
       35 
     | 
    
         
            -
                   
     | 
| 
       36 
     | 
    
         
            -
                  return nil if distances.empty?
         
     | 
| 
       37 
     | 
    
         
            -
                  # return the start of the proper day
         
     | 
| 
       38 
     | 
    
         
            -
                  goal = date + distances.min * IceCube::ONE_DAY
         
     | 
| 
       39 
     | 
    
         
            -
                  self.class.adjust(goal, date)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  clobber_base_validations(:day, :wday)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  self
         
     | 
| 
       40 
13 
     | 
    
         
             
                end
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
                 
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                class Validation
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  include Validations::Lock
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  StringBuilder.register_formatter(:day_of_month) do |entries|
         
     | 
| 
      
 20 
     | 
    
         
            +
                    str = "on the #{StringBuilder.sentence(entries)} "
         
     | 
| 
      
 21 
     | 
    
         
            +
                    str << (entries.size == 1 ? 'day of the month' : 'days of the month')
         
     | 
| 
      
 22 
     | 
    
         
            +
                    str
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  attr_reader :day
         
     | 
| 
      
 26 
     | 
    
         
            +
                  alias :value :day
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def initialize(day)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @day = day
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def build_s(builder)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    builder.piece(:day_of_month) << StringBuilder.nice_number(day)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def build_hash(builder)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    builder.validations_array(:day_of_month) << day
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def build_ical(builder)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    builder['BYMONTHDAY'] << day
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 45 
     | 
    
         
            +
                    :day
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
       48 
48 
     | 
    
         
             
                end
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
       50 
50 
     | 
    
         
             
              end
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
       52 
52 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,57 +1,70 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module IceCube
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
               
     | 
| 
      
 3 
     | 
    
         
            +
              module Validations::DayOfWeek
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
                 
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
              
         
     | 
| 
       12 
     | 
    
         
            -
                def validate(date)
         
     | 
| 
       13 
     | 
    
         
            -
                  # is it even one of the valid days?
         
     | 
| 
       14 
     | 
    
         
            -
                  return true if !@days_of_week || @days_of_week.empty?
         
     | 
| 
       15 
     | 
    
         
            -
                  return false unless @days_of_week.has_key?(date.wday) #shortcut
         
     | 
| 
       16 
     | 
    
         
            -
                  # does this fall on one of the occurrences?
         
     | 
| 
       17 
     | 
    
         
            -
                  first_occurrence = ((7 - Time.utc(date.year, date.month, 1).wday) + date.wday) % 7 + 1 #day of first occurrence of a wday in a month
         
     | 
| 
       18 
     | 
    
         
            -
                  this_weekday_in_month_count = ((TimeUtil.days_in_month(date) - first_occurrence + 1) / 7.0).ceil #how many of these in the month
         
     | 
| 
       19 
     | 
    
         
            -
                  nth_occurrence_of_weekday = (date.mday - first_occurrence) / 7 + 1 #what occurrence of the weekday is +date+
         
     | 
| 
       20 
     | 
    
         
            -
                  @days_of_week[date.wday].include?(nth_occurrence_of_weekday) || @days_of_week[date.wday].include?(nth_occurrence_of_weekday - this_weekday_in_month_count - 1)
         
     | 
| 
       21 
     | 
    
         
            -
                end
         
     | 
| 
       22 
     | 
    
         
            -
              
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                # note - temporary implementation
         
     | 
| 
       25 
     | 
    
         
            -
                # instead - once we know what weekday starts the month, we should be able to figure out
         
     | 
| 
       26 
     | 
    
         
            -
                # the rest with basic math
         
     | 
| 
       27 
     | 
    
         
            -
                def closest(date)
         
     | 
| 
       28 
     | 
    
         
            -
                  return nil if !@days_of_week || @days_of_week.empty?
         
     | 
| 
       29 
     | 
    
         
            -
                  goal = date
         
     | 
| 
       30 
     | 
    
         
            -
                  while (next_date = goal + IceCube::ONE_DAY)
         
     | 
| 
       31 
     | 
    
         
            -
                    # DST hack.  If our day starts at midnight, when DST ends it will be pushed to 11 PM on the previous day.
         
     | 
| 
       32 
     | 
    
         
            -
                    check_date = next_date.day == goal.day ? next_date + IceCube::ONE_HOUR : next_date
         
     | 
| 
       33 
     | 
    
         
            -
                    return self.class.adjust(next_date, date) if validate(check_date)
         
     | 
| 
       34 
     | 
    
         
            -
                    goal = next_date
         
     | 
| 
      
 5 
     | 
    
         
            +
                def day_of_week(dows)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  dows.each do |day, occs|
         
     | 
| 
      
 7 
     | 
    
         
            +
                    occs.each do |occ|
         
     | 
| 
      
 8 
     | 
    
         
            +
                      day = TimeUtil.symbol_to_day(day) if day.is_a?(Symbol)
         
     | 
| 
      
 9 
     | 
    
         
            +
                      validations_for(:day_of_week) << Validation.new(day, occ)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    end
         
     | 
| 
       35 
11 
     | 
    
         
             
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
                  clobber_base_validations :day, :wday
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self
         
     | 
| 
       36 
14 
     | 
    
         
             
                end
         
     | 
| 
       37 
15 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
                 
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                   
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                   
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                   
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                   
     | 
| 
      
 16 
     | 
    
         
            +
                class Validation
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  attr_reader :day, :occ
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  StringBuilder.register_formatter(:day_of_week) do |segments|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    'on the ' + segments.join(' when it is the ')
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 25 
     | 
    
         
            +
                    :day
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def build_s(builder)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    builder.piece(:day_of_week) << "#{StringBuilder.nice_number(occ)} #{Date::DAYNAMES[day]}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def build_ical(builder)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    ical_day = IcalBuilder.fixnum_to_ical_day(day)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    # Delete any with this day and no occ first
         
     | 
| 
      
 35 
     | 
    
         
            +
                    builder['BYDAY'].delete_if { |d| d == ical_day }
         
     | 
| 
      
 36 
     | 
    
         
            +
                    builder['BYDAY'] << "#{occ}#{ical_day}"
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def build_hash(builder)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    builder.validations[:day_of_week] ||= {}
         
     | 
| 
      
 41 
     | 
    
         
            +
                    arr = (builder.validations[:day_of_week][day] ||= [])
         
     | 
| 
      
 42 
     | 
    
         
            +
                    arr << occ
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def initialize(day, occ)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @day = day
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @occ = occ
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def validate(time, schedule)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    # count the days to the weekday
         
     | 
| 
      
 52 
     | 
    
         
            +
                    sum = day >= time.wday ? day - time.wday : 7 - time.wday + day
         
     | 
| 
      
 53 
     | 
    
         
            +
                    wrapper = TimeUtil::TimeWrapper.new(time)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    wrapper.add :day, sum
         
     | 
| 
      
 55 
     | 
    
         
            +
                    # and then count the week until a viable occ
         
     | 
| 
      
 56 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 57 
     | 
    
         
            +
                      which_occ, num_occ = TimeUtil.which_occurrence_in_month(wrapper.to_time, day)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      this_occ = occ < 0 ? num_occ + occ + 1 : occ
         
     | 
| 
      
 59 
     | 
    
         
            +
                      break if which_occ == this_occ
         
     | 
| 
      
 60 
     | 
    
         
            +
                      sum += 7
         
     | 
| 
      
 61 
     | 
    
         
            +
                      wrapper.add :day, 7 # one week
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                    sum
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       53 
66 
     | 
    
         
             
                end
         
     | 
| 
       54 
67 
     | 
    
         | 
| 
       55 
68 
     | 
    
         
             
              end
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
       57 
70 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,51 +1,55 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module IceCube
         
     | 
| 
       2 
     | 
    
         
            -
              
         
     | 
| 
       3 
     | 
    
         
            -
              class DayOfYearValidation < Validation
         
     | 
| 
       4 
2 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
     | 
    
         
            -
                def  
     | 
| 
       8 
     | 
    
         
            -
                   
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                   
     | 
| 
       13 
     | 
    
         
            -
                  @days_of_year.include?(date.yday) || @days_of_year.include?(date.yday - TimeUtil.days_in_year(date) - 1)
         
     | 
| 
       14 
     | 
    
         
            -
                end
         
     | 
| 
       15 
     | 
    
         
            -
              
         
     | 
| 
       16 
     | 
    
         
            -
                def closest(date)
         
     | 
| 
       17 
     | 
    
         
            -
                  return nil if !@days_of_year || @days_of_year.empty?
         
     | 
| 
       18 
     | 
    
         
            -
                  #get some variables we need
         
     | 
| 
       19 
     | 
    
         
            -
                  days_in_year = TimeUtil.days_in_year(date)
         
     | 
| 
       20 
     | 
    
         
            -
                  days_left_in_this_year = days_in_year - date.yday
         
     | 
| 
       21 
     | 
    
         
            -
                  days_in_next_year = TimeUtil.days_in_year(Time.utc(date.year + 1, 1, 1))
         
     | 
| 
       22 
     | 
    
         
            -
                  # create a list of distances
         
     | 
| 
       23 
     | 
    
         
            -
                  distances = []
         
     | 
| 
       24 
     | 
    
         
            -
                  @days_of_year.each do |d|
         
     | 
| 
       25 
     | 
    
         
            -
                    if d > 0
         
     | 
| 
       26 
     | 
    
         
            -
                      distances << d - date.yday #today is 1, we want 20 (19)
         
     | 
| 
       27 
     | 
    
         
            -
                      distances << days_left_in_this_year + d #(364 + 20)
         
     | 
| 
       28 
     | 
    
         
            -
                    elsif d < 0
         
     | 
| 
       29 
     | 
    
         
            -
                      distances << (days_in_year + d + 1) - date.yday #today is 300, we want -1
         
     | 
| 
       30 
     | 
    
         
            -
                      distances << (days_in_next_year + d + 1) + days_left_in_this_year #today is 300, we want -70
         
     | 
| 
       31 
     | 
    
         
            -
                    end
         
     | 
| 
       32 
     | 
    
         
            -
                  end
         
     | 
| 
       33 
     | 
    
         
            -
                  #return the lowest distance
         
     | 
| 
       34 
     | 
    
         
            -
                  distances = distances.select { |d| d > 0 }
         
     | 
| 
       35 
     | 
    
         
            -
                  return nil if distances.empty?
         
     | 
| 
       36 
     | 
    
         
            -
                  # return the start of the proper day
         
     | 
| 
       37 
     | 
    
         
            -
                  goal = date + distances.min * IceCube::ONE_DAY
         
     | 
| 
       38 
     | 
    
         
            -
                  self.class.adjust(goal, date)
         
     | 
| 
       39 
     | 
    
         
            -
                end
         
     | 
| 
       40 
     | 
    
         
            -
              
         
     | 
| 
       41 
     | 
    
         
            -
                def to_s
         
     | 
| 
       42 
     | 
    
         
            -
                  'on the ' << self.class.nice_numbers(@days_of_year) << (@days_of_year.size == 1 ? ' day' : ' days') << ' of the year' unless @days_of_year.empty?
         
     | 
| 
      
 3 
     | 
    
         
            +
              module Validations::DayOfYear
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def day_of_year(*days)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  days.each do |day|
         
     | 
| 
      
 7 
     | 
    
         
            +
                    validations_for(:day_of_year) << Validation.new(day)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
                  clobber_base_validations(:month, :day, :wday)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  self
         
     | 
| 
       43 
11 
     | 
    
         
             
                end
         
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
                 
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                class Validation
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  attr_reader :day
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  StringBuilder.register_formatter(:day_of_year) do |entries|
         
     | 
| 
      
 18 
     | 
    
         
            +
                    str = "on the #{StringBuilder.sentence(entries)} "
         
     | 
| 
      
 19 
     | 
    
         
            +
                    str << (entries.size == 1 ? 'day of the year' : 'days of the year')
         
     | 
| 
      
 20 
     | 
    
         
            +
                    str
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def initialize(day)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @day = day
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 28 
     | 
    
         
            +
                    :day
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def build_s(builder)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    builder.piece(:day_of_year) << StringBuilder.nice_number(day)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def build_hash(builder)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    builder.validations_array(:day_of_year) << day
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def build_ical(builder)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    builder['BYYEARDAY'] << day
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def validate(time, schedule)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    days_in_year = TimeUtil.days_in_year(time)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    the_day = day < 0 ? day + days_in_year : day
         
     | 
| 
      
 46 
     | 
    
         
            +
                    # compute the diff
         
     | 
| 
      
 47 
     | 
    
         
            +
                    diff = the_day - time.yday
         
     | 
| 
      
 48 
     | 
    
         
            +
                    diff >= 0 ? diff : diff + days_in_year
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
       47 
51 
     | 
    
         
             
                end
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
       49 
53 
     | 
    
         
             
              end
         
     | 
| 
       50 
54 
     | 
    
         | 
| 
       51 
55 
     | 
    
         
             
            end
         
     |