ice_cube 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ac4ca18d263a59f5e9499349ce536e63d8ec59f
4
- data.tar.gz: 3d2d4ed0d0cf8c0632b870b19e59ac964d64f93a
3
+ metadata.gz: 9b2874169ce6feed9740297977d1dc7a4de82c83
4
+ data.tar.gz: 6b7c7b5c35167429740adb2f64eced33413b3e93
5
5
  SHA512:
6
- metadata.gz: d6715a91023b1c7c0fa7f1bdc59776c0182b3742bd37a6fc28fd14e0bd15ee5e4d0e2c1014c03c43c04cfaaee8b73ed19d5a7557b815f949fe72d044fb9a61bb
7
- data.tar.gz: f2941f23021a8062fc35cc3f3dd1bd2010903645d723aa9bf7ba3b50a4c240ef88757539c6317b4cab967f7d357a5ccf915d84c963659a97a54d47f5c05efc53
6
+ metadata.gz: 7d9ec04b5840a45acd23ad7f41e44491ff6cb66f85348d32504ec3116955513eb2e4ec3c8e6281c958d2f6957ec7f08ff67e126a4853618fa748da84d5705638
7
+ data.tar.gz: e47242b8441a7eb029ebd4c128030eb004110d539aed236ad1b55a8c02f378483fdf925c93ccbf4c794dd990d6a5b80bd3f9a1728c4d5c6e8f748c3a1f33cce8
data/lib/ice_cube.rb CHANGED
@@ -18,6 +18,7 @@ module IceCube
18
18
 
19
19
  autoload :HashParser, 'ice_cube/parsers/hash_parser'
20
20
  autoload :YamlParser, 'ice_cube/parsers/yaml_parser'
21
+ autoload :IcalParser, 'ice_cube/parsers/ical_parser'
21
22
 
22
23
  autoload :CountExceeded, 'ice_cube/errors/count_exceeded'
23
24
  autoload :UntilExceeded, 'ice_cube/errors/until_exceeded'
@@ -34,8 +35,7 @@ module IceCube
34
35
  autoload :YearlyRule, 'ice_cube/rules/yearly_rule'
35
36
 
36
37
  module Validations
37
-
38
- autoload :Lock, 'ice_cube/validations/lock'
38
+ autoload :FixedValue, 'ice_cube/validations/fixed_value'
39
39
  autoload :ScheduleLock, 'ice_cube/validations/schedule_lock'
40
40
 
41
41
  autoload :Count, 'ice_cube/validations/count'
@@ -84,7 +84,7 @@ module IceCube
84
84
  # time formats and is only used when ActiveSupport is available.
85
85
  #
86
86
  def to_s(format=nil)
87
- if format && to_time.public_method(:to_s).arity > 0
87
+ if format && to_time.public_method(:to_s).arity != 0
88
88
  t0, t1 = start_time.to_s(format), end_time.to_s(format)
89
89
  else
90
90
  t0, t1 = start_time.to_s, end_time.to_s
@@ -53,7 +53,9 @@ module IceCube
53
53
  def apply_rrules(schedule, data)
54
54
  return unless data[:rrules]
55
55
  data[:rrules].each do |h|
56
- schedule.rrule(IceCube::Rule.from_hash(h))
56
+ rrule = h.is_a?(IceCube::Rule) ? h : IceCube::Rule.from_hash(h)
57
+
58
+ schedule.rrule(rrule)
57
59
  end
58
60
  end
59
61
 
@@ -61,7 +63,9 @@ module IceCube
61
63
  return unless data[:exrules]
62
64
  warn "IceCube: :exrules is deprecated, and will be removed in a future release. at: #{ caller[0] }"
63
65
  data[:exrules].each do |h|
64
- schedule.exrule(IceCube::Rule.from_hash(h))
66
+ rrule = h.is_a?(IceCube::Rule) ? h : IceCube::Rule.from_hash(h)
67
+
68
+ schedule.exrule(rrule)
65
69
  end
66
70
  end
67
71
 
@@ -0,0 +1,90 @@
1
+ module IceCube
2
+ class IcalParser
3
+ def self.schedule_from_ical(ical_string, options = {})
4
+ data = {}
5
+ ical_string.each_line do |line|
6
+ (property, value) = line.split(':')
7
+ (property, tzid) = property.split(';')
8
+ case property
9
+ when 'DTSTART'
10
+ data[:start_time] = Time.parse(value)
11
+ when 'DTEND'
12
+ data[:end_time] = Time.parse(value)
13
+ when 'EXDATE'
14
+ data[:extimes] ||= []
15
+ data[:extimes] += value.split(',').map{|v| Time.parse(v)}
16
+ when 'DURATION'
17
+ data[:duration] # FIXME
18
+ when 'RRULE'
19
+ data[:rrules] = [rule_from_ical(value)]
20
+ end
21
+ end
22
+ Schedule.from_hash data
23
+ end
24
+
25
+ def self.rule_from_ical(ical)
26
+ params = { validations: { } }
27
+
28
+ ical.split(';').each do |rule|
29
+ (name, value) = rule.split('=')
30
+ value.strip!
31
+ case name
32
+ when 'FREQ'
33
+ params[:freq] = value.downcase
34
+ when 'INTERVAL'
35
+ params[:interval] = value.to_i
36
+ when 'COUNT'
37
+ params[:count] = value.to_i
38
+ when 'UNTIL'
39
+ params[:until] = Time.parse(value).utc
40
+ when 'WKST'
41
+ params[:wkst] = TimeUtil.ical_day_to_symbol(value)
42
+ when 'BYSECOND'
43
+ params[:validations][:second_of_minute] = value.split(',').collect(&:to_i)
44
+ when 'BYMINUTE'
45
+ params[:validations][:minute_of_hour] = value.split(',').collect(&:to_i)
46
+ when 'BYHOUR'
47
+ params[:validations][:hour_of_day] = value.split(',').collect(&:to_i)
48
+ when 'BYDAY'
49
+ dows = {}
50
+ days = []
51
+ value.split(',').each do |expr|
52
+ day = TimeUtil.ical_day_to_symbol(expr.strip[-2..-1])
53
+ if expr.strip.length > 2 # day with occurence
54
+ occ = expr[0..-3].to_i
55
+ dows[day].nil? ? dows[day] = [occ] : dows[day].push(occ)
56
+ days.delete(TimeUtil.sym_to_wday(day))
57
+ else
58
+ days.push TimeUtil.sym_to_wday(day) if dows[day].nil?
59
+ end
60
+ end
61
+ params[:validations][:day_of_week] = dows unless dows.empty?
62
+ params[:validations][:day] = days unless days.empty?
63
+ when 'BYMONTHDAY'
64
+ params[:validations][:day_of_month] = value.split(',').collect(&:to_i)
65
+ when 'BYMONTH'
66
+ params[:validations][:month_of_year] = value.split(',').collect(&:to_i)
67
+ when 'BYYEARDAY'
68
+ params[:validations][:day_of_year] = value.split(',').collect(&:to_i)
69
+ when 'BYSETPOS'
70
+ else
71
+ raise "Invalid or unsupported rrule command: #{name}"
72
+ end
73
+ end
74
+
75
+ params[:interval] ||= 1
76
+
77
+ # WKST only valid for weekly rules
78
+ params.delete(:wkst) unless params[:freq] == 'weekly'
79
+
80
+ rule = Rule.send(*params.values_at(:freq, :interval, :wkst).compact)
81
+ rule.count(params[:count]) if params[:count]
82
+ rule.until(params[:until]) if params[:until]
83
+ params[:validations].each do |key, value|
84
+ value.is_a?(Array) ? rule.send(key, *value) : rule.send(key, value)
85
+ end
86
+
87
+ rule
88
+ end
89
+ end
90
+ end
data/lib/ice_cube/rule.rb CHANGED
@@ -27,6 +27,11 @@ module IceCube
27
27
  raise MethodNotImplemented, "Expected to be overrridden by subclasses"
28
28
  end
29
29
 
30
+ # Convert from ical string and create a rule
31
+ def self.from_ical(ical)
32
+ IceCube::IcalParser.rule_from_ical(ical)
33
+ end
34
+
30
35
  # Yaml implementation
31
36
  def to_yaml(*args)
32
37
  YAML::dump(to_hash, *args)
@@ -167,28 +167,28 @@ module IceCube
167
167
 
168
168
  # The next n occurrences after now
169
169
  def next_occurrences(num, from = nil)
170
- from ||= TimeUtil.now(@start_time)
170
+ from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
171
171
  enumerate_occurrences(from + 1, nil).take(num)
172
172
  end
173
173
 
174
174
  # The next occurrence after now (overridable)
175
175
  def next_occurrence(from = nil)
176
- from ||= TimeUtil.now(@start_time)
177
- begin
178
- enumerate_occurrences(from + 1, nil).next()
179
- rescue StopIteration
180
- nil
181
- end
176
+ from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
177
+ enumerate_occurrences(from + 1, nil).next
178
+ rescue StopIteration
179
+ nil
182
180
  end
183
181
 
184
182
  # The previous occurrence from a given time
185
183
  def previous_occurrence(from)
184
+ from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
186
185
  return nil if from <= start_time
187
186
  enumerate_occurrences(start_time, from - 1).to_a.last
188
187
  end
189
188
 
190
189
  # The previous n occurrences before a given time
191
190
  def previous_occurrences(num, from)
191
+ from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
192
192
  return [] if from <= start_time
193
193
  a = enumerate_occurrences(start_time, from - 1).to_a
194
194
  a.size > num ? a[-1*num,a.size] : a
@@ -214,12 +214,10 @@ module IceCube
214
214
 
215
215
  # Return a boolean indicating if an occurrence falls between two times
216
216
  def occurs_between?(begin_time, closing_time)
217
- begin
218
- enumerate_occurrences(begin_time, closing_time).next()
219
- true
220
- rescue StopIteration
221
- false
222
- end
217
+ enumerate_occurrences(begin_time, closing_time).next
218
+ true
219
+ rescue StopIteration
220
+ false
223
221
  end
224
222
 
225
223
  # Return a boolean indicating if an occurrence is occurring between two
@@ -235,7 +233,7 @@ module IceCube
235
233
 
236
234
  # Return a boolean indicating if an occurrence falls on a certain date
237
235
  def occurs_on?(date)
238
- date = TimeUtil.ensure_date date
236
+ date = TimeUtil.ensure_date(date)
239
237
  begin_time = TimeUtil.beginning_of_date(date, start_time)
240
238
  closing_time = TimeUtil.end_of_date(date, start_time)
241
239
  occurs_between?(begin_time, closing_time)
@@ -243,6 +241,7 @@ module IceCube
243
241
 
244
242
  # Determine if the schedule is occurring at a given time
245
243
  def occurring_at?(time)
244
+ time = TimeUtil.match_zone(time, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
246
245
  if duration > 0
247
246
  return false if exception_time?(time)
248
247
  occurs_between?(time - duration + 1, time)
@@ -256,7 +255,7 @@ module IceCube
256
255
  # @param [Time] closing_time - the last time to consider
257
256
  # @return [Boolean] whether or not the schedules conflict at all
258
257
  def conflicts_with?(other_schedule, closing_time = nil)
259
- closing_time = TimeUtil.ensure_time closing_time
258
+ closing_time = TimeUtil.ensure_time(closing_time)
260
259
  unless terminating? || other_schedule.terminating? || closing_time
261
260
  raise ArgumentError, "One or both schedules must be terminating to use #conflicts_with?"
262
261
  end
@@ -333,6 +332,11 @@ module IceCube
333
332
  pieces.join("\n")
334
333
  end
335
334
 
335
+ # Load the schedule from ical
336
+ def self.from_ical(ical, options = {})
337
+ IcalParser.schedule_from_ical(ical, options)
338
+ end
339
+
336
340
  # Convert the schedule to yaml
337
341
  def to_yaml(*args)
338
342
  YAML::dump(to_hash, *args)
@@ -404,10 +408,10 @@ module IceCube
404
408
  opening_time = TimeUtil.match_zone(opening_time, start_time)
405
409
  closing_time = TimeUtil.match_zone(closing_time, start_time)
406
410
  opening_time += start_time.subsec - opening_time.subsec rescue 0
407
- reset
408
411
  opening_time = start_time if opening_time < start_time
409
- t1 = full_required? ? start_time : opening_time
410
- e = Enumerator.new do |yielder|
412
+ Enumerator.new do |yielder|
413
+ reset
414
+ t1 = full_required? ? start_time : realign(opening_time)
411
415
  loop do
412
416
  break unless (t0 = next_time(t1, closing_time))
413
417
  break if closing_time && t0 > closing_time
@@ -492,6 +496,28 @@ module IceCube
492
496
  end
493
497
  end
494
498
 
499
+ # If any rule has validations for values within the period, (overriding the
500
+ # interval from start time, e.g. `day[_of_week]`), and the opening time is
501
+ # offset from the interval multiplier such that it might miss the first
502
+ # correct occurrence (e.g. repeat is every N weeks, but selecting from end
503
+ # of week N-1, the first jump would go to end of week N and miss any
504
+ # earlier validations in the week). This realigns the opening time to
505
+ # the start of the interval's correct period (e.g. move to start of week N)
506
+ # TODO: check if this is needed for validations other than `:wday`
507
+ #
508
+ def realign(opening_time)
509
+ time = TimeUtil::TimeWrapper.new(opening_time)
510
+ recurrence_rules.each do |rule|
511
+ wday_validations = rule.other_interval_validations.select { |v| v.type == :wday } or next
512
+ interval = rule.base_interval_validation.validate(opening_time, self).to_i
513
+ offset = wday_validations
514
+ .map { |v| v.validate(opening_time, self).to_i }
515
+ .reduce(0) { |least, i| i > 0 && i <= interval && (i < least || least == 0) ? i : least }
516
+ time.add(rule.base_interval_type, 7 - time.to_time.wday) if offset > 0
517
+ end
518
+ time.to_time
519
+ end
520
+
495
521
  end
496
522
 
497
523
  end
@@ -11,6 +11,11 @@ module IceCube
11
11
  :thursday => 4, :friday => 5, :saturday => 6
12
12
  }
13
13
 
14
+ ICAL_DAYS = {
15
+ 'SU' => :sunday, 'MO' => :monday, 'TU' => :tuesday, 'WE' => :wednesday,
16
+ 'TH' => :thursday, 'FR' => :friday, 'SA' => :saturday
17
+ }
18
+
14
19
  MONTHS = {
15
20
  :january => 1, :february => 2, :march => 3, :april => 4, :may => 5,
16
21
  :june => 6, :july => 7, :august => 8, :september => 9, :october => 10,
@@ -24,21 +29,34 @@ module IceCube
24
29
  match_zone(Time.at(Time.now.to_i), reference)
25
30
  end
26
31
 
27
- def self.match_zone(time, reference)
28
- return unless time = ensure_time(time)
29
- if reference.respond_to? :time_zone
30
- time.in_time_zone(reference.time_zone)
32
+ def self.build_in_zone(args, reference)
33
+ if reference.respond_to?(:time_zone)
34
+ reference.time_zone.local(*args)
35
+ elsif reference.utc?
36
+ Time.utc(*args)
37
+ elsif reference.zone
38
+ Time.local(*args)
31
39
  else
32
- if reference.utc?
33
- time.utc
34
- elsif reference.zone
35
- time.getlocal
36
- else
37
- time.getlocal(reference.utc_offset)
38
- end
40
+ Time.new(*args << reference.utc_offset)
39
41
  end
40
42
  end
41
43
 
44
+ def self.match_zone(input_time, reference)
45
+ return unless time = ensure_time(input_time)
46
+ time = if reference.respond_to? :time_zone
47
+ time.in_time_zone(reference.time_zone)
48
+ else
49
+ if reference.utc?
50
+ time.utc
51
+ elsif reference.zone
52
+ time.getlocal
53
+ else
54
+ time.getlocal(reference.utc_offset)
55
+ end
56
+ end
57
+ (Date === input_time) ? beginning_of_date(time, reference) : time
58
+ end
59
+
42
60
  # Ensure that this is either nil, or a time
43
61
  def self.ensure_time(time, date_eod = false)
44
62
  case time
@@ -96,25 +114,13 @@ module IceCube
96
114
  end
97
115
 
98
116
  # Get the beginning of a date
99
- def self.beginning_of_date(date, reference=nil)
100
- args = [date.year, date.month, date.day, 0, 0, 0]
101
- reference ||= Time.local(*args)
102
- if reference.respond_to?(:time_zone) && reference.time_zone
103
- reference.time_zone.local(*args)
104
- else
105
- match_zone(Time.new(*args << reference.utc_offset), reference)
106
- end
117
+ def self.beginning_of_date(date, reference=Time.now)
118
+ build_in_zone([date.year, date.month, date.day, 0, 0, 0], reference)
107
119
  end
108
120
 
109
121
  # Get the end of a date
110
- def self.end_of_date(date, reference=nil)
111
- args = [date.year, date.month, date.day, 23, 59, 59]
112
- reference ||= Time.local(*args)
113
- if reference.respond_to?(:time_zone) && reference.time_zone
114
- reference.time_zone.local(*args)
115
- else
116
- match_zone(Time.new(*args << reference.utc_offset), reference)
117
- end
122
+ def self.end_of_date(date, reference=Time.now)
123
+ build_in_zone([date.year, date.month, date.day, 23, 59, 59], reference)
118
124
  end
119
125
 
120
126
  # Convert a symbol to a numeric month
@@ -146,12 +152,25 @@ module IceCube
146
152
  end
147
153
  end
148
154
 
155
+ # Convert a symbol to an ical day (SU, MO)
156
+ def self.week_start(sym)
157
+ raise ArgumentError, "Invalid day: #{str}" unless DAYS.keys.include?(sym)
158
+ day = sym.to_s.upcase[0..1]
159
+ day
160
+ end
161
+
149
162
  # Convert weekday from base sunday to the schedule's week start.
150
163
  def self.normalize_wday(wday, week_start)
151
164
  (wday - sym_to_wday(week_start)) % 7
152
165
  end
153
166
  deprecated_alias :normalize_weekday, :normalize_wday
154
167
 
168
+ def self.ical_day_to_symbol(str)
169
+ day = ICAL_DAYS[str]
170
+ raise ArgumentError, "Invalid day: #{str}" if day.nil?
171
+ day
172
+ end
173
+
155
174
  # Return the count of the number of times wday appears in the month,
156
175
  # and which of those time falls on
157
176
  def self.which_occurrence_in_month(time, wday)
@@ -30,10 +30,24 @@ module IceCube
30
30
  :interval
31
31
  ]
32
32
 
33
+ attr_reader :validations
34
+
33
35
  def initialize(interval = 1, *)
34
36
  @validations = Hash.new
35
37
  end
36
38
 
39
+ def base_interval_validation
40
+ @validations[:interval].first
41
+ end
42
+
43
+ def other_interval_validations
44
+ Array(@validations[base_interval_validation.type])
45
+ end
46
+
47
+ def base_interval_type
48
+ base_interval_validation.type
49
+ end
50
+
37
51
  # Compute the next time after (or including) the specified time in respect
38
52
  # to the given schedule
39
53
  def next_time(time, schedule, closing_time)
@@ -106,6 +120,7 @@ module IceCube
106
120
  end
107
121
 
108
122
  private
123
+
109
124
  def normalized_interval(interval)
110
125
  int = interval.to_i
111
126
  raise ArgumentError, "'#{interval}' is not a valid input for interval. Please pass an integer." unless int > 0
@@ -127,15 +142,11 @@ module IceCube
127
142
 
128
143
  def validation_accepts_or_updates_time?(validations_for_type)
129
144
  res = validated_results(validations_for_type)
130
- # If there is any nil, then we're set - otherwise choose the lowest
131
- if res.any? { |r| r.nil? || r == 0 }
132
- true
133
- else
134
- return nil if res.all? { |r| r === true } # allow quick escaping
135
- res.reject! { |r| r.nil? || r == 0 || r === true }
136
- shift_time_by_validation(res, validations_for_type)
137
- false
138
- end
145
+ return true if res.any? { |r| r.nil? || r == 0 }
146
+ return nil if res.all? { |r| r == true }
147
+ res.reject! { |r| r == true }
148
+ shift_time_by_validation(res, validations_for_type.first)
149
+ false
139
150
  end
140
151
 
141
152
  def validated_results(validations_for_type)
@@ -144,9 +155,8 @@ module IceCube
144
155
  end
145
156
  end
146
157
 
147
- def shift_time_by_validation(res, vals)
158
+ def shift_time_by_validation(res, validation)
148
159
  return unless (interval = res.min)
149
- validation = vals.first
150
160
  wrapper = TimeUtil::TimeWrapper.new(@time, validation.dst_adjust?)
151
161
  wrapper.add(validation.type, interval)
152
162
  wrapper.clear_below(validation.type)
@@ -16,9 +16,7 @@ module IceCube
16
16
  self
17
17
  end
18
18
 
19
- class Validation
20
-
21
- include Validations::Lock
19
+ class Validation < Validations::FixedValue
22
20
 
23
21
  attr_reader :day
24
22
  alias :value :day
@@ -13,9 +13,7 @@ module IceCube
13
13
  self
14
14
  end
15
15
 
16
- class Validation
17
-
18
- include Validations::Lock
16
+ class Validation < Validations::FixedValue
19
17
 
20
18
  attr_reader :day
21
19
  alias :value :day
@@ -0,0 +1,95 @@
1
+ module IceCube
2
+
3
+ # This abstract validation class is used by the various "fixed-time" (e.g.
4
+ # day, day_of_month, hour_of_day) Validation and ScheduleLock::Validation
5
+ # modules. It is not a standalone rule validation module like the others.
6
+ #
7
+ # Given the including Validation's defined +type+ field, it will lock to the
8
+ # specified +value+ or else the corresponding time unit from the schedule's
9
+ # start_time
10
+ #
11
+ class Validations::FixedValue
12
+
13
+ INTERVALS = {:min => 60, :sec => 60, :hour => 24, :month => 12, :wday => 7}
14
+
15
+ def validate(time, schedule)
16
+ case type
17
+ when :day then validate_day_lock(time, schedule)
18
+ when :hour then validate_hour_lock(time, schedule)
19
+ else validate_interval_lock(time, schedule)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # Validate if the current time unit matches the same unit from the schedule
26
+ # start time, returning the difference to the interval
27
+ #
28
+ def validate_interval_lock(time, schedule)
29
+ t0 = starting_unit(schedule.start_time)
30
+ t1 = time.send(type)
31
+ t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0
32
+ end
33
+
34
+ # Lock the hour if explicitly set by hour_of_day, but allow for the nearest
35
+ # hour during DST start to keep the correct interval.
36
+ #
37
+ def validate_hour_lock(time, schedule)
38
+ h0 = starting_unit(schedule.start_time)
39
+ h1 = time.hour
40
+ if h0 >= h1
41
+ h0 - h1
42
+ else
43
+ if dst_offset = TimeUtil.dst_change(time)
44
+ h0 - h1 + dst_offset
45
+ else
46
+ 24 - h1 + h0
47
+ end
48
+ end
49
+ end
50
+
51
+ # For monthly rules that have no specified day value, the validation relies
52
+ # on the schedule start time and jumps to include every month even if it
53
+ # has fewer days than the schedule's start day.
54
+ #
55
+ # Negative day values (from month end) also include all months.
56
+ #
57
+ # Positive day values are taken literally so months with fewer days will
58
+ # be skipped.
59
+ #
60
+ def validate_day_lock(time, schedule)
61
+ days_in_month = TimeUtil.days_in_month(time)
62
+ date = Date.new(time.year, time.month, time.day)
63
+
64
+ if value && value < 0
65
+ start = TimeUtil.day_of_month(value, date)
66
+ month_overflow = days_in_month - TimeUtil.days_in_next_month(time)
67
+ elsif value && value > 0
68
+ start = value
69
+ month_overflow = 0
70
+ else
71
+ start = TimeUtil.day_of_month(schedule.start_time.day, date)
72
+ month_overflow = 0
73
+ end
74
+
75
+ sleeps = start - date.day
76
+
77
+ if value && value > 0
78
+ until_next_month = days_in_month + sleeps
79
+ else
80
+ until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date)
81
+ until_next_month += sleeps - month_overflow
82
+ end
83
+
84
+ sleeps >= 0 ? sleeps : until_next_month
85
+ end
86
+
87
+ def starting_unit(start_time)
88
+ start = value || start_time.send(type)
89
+ start = start % INTERVALS[type] if start < 0
90
+ start
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -14,9 +14,7 @@ module IceCube
14
14
  self
15
15
  end
16
16
 
17
- class Validation
18
-
19
- include Validations::Lock
17
+ class Validation < Validations::FixedValue
20
18
 
21
19
  attr_reader :hour
22
20
  alias :value :hour
@@ -13,9 +13,7 @@ module IceCube
13
13
  self
14
14
  end
15
15
 
16
- class Validation
17
-
18
- include Validations::Lock
16
+ class Validation < Validations::FixedValue
19
17
 
20
18
  attr_reader :minute
21
19
  alias :value :minute
@@ -14,9 +14,7 @@ module IceCube
14
14
  self
15
15
  end
16
16
 
17
- class Validation
18
-
19
- include Validations::Lock
17
+ class Validation < Validations::FixedValue
20
18
 
21
19
  attr_reader :month
22
20
  alias :value :month
@@ -12,9 +12,7 @@ module IceCube
12
12
  end
13
13
  end
14
14
 
15
- class Validation
16
-
17
- include Validations::Lock
15
+ class Validation < Validations::FixedValue
18
16
 
19
17
  attr_reader :type, :value
20
18
 
@@ -13,9 +13,7 @@ module IceCube
13
13
  self
14
14
  end
15
15
 
16
- class Validation
17
-
18
- include Validations::Lock
16
+ class Validation < Validations::FixedValue
19
17
 
20
18
  attr_reader :second
21
19
  alias :value :second
@@ -39,7 +39,7 @@ module IceCube
39
39
  d1 = Date.new(t1.year, t1.month, t1.day)
40
40
  days = (d1 - TimeUtil.normalize_wday(d1.wday, week_start)) -
41
41
  (d0 - TimeUtil.normalize_wday(d0.wday, week_start))
42
- offset = ((days / 7) % interval).nonzero?
42
+ offset = ((days.to_i / 7) % interval).nonzero?
43
43
  (interval - offset) * 7 if offset
44
44
  end
45
45
 
@@ -1,5 +1,5 @@
1
1
  module IceCube
2
2
 
3
- VERSION = '0.12.1'
3
+ VERSION = '0.13.0'
4
4
 
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,8 @@ end
7
7
 
8
8
  require File.dirname(__FILE__) + '/../lib/ice_cube'
9
9
 
10
+ IceCube.compatibility = 12
11
+
10
12
  DAY = Time.utc(2010, 3, 1)
11
13
  WEDNESDAY = Time.utc(2010, 6, 23, 5, 0, 0)
12
14
 
@@ -17,6 +19,9 @@ WORLD_TIME_ZONES = [
17
19
  ]
18
20
 
19
21
  RSpec.configure do |config|
22
+ Dir[File.dirname(__FILE__) + '/support/**/*'].each { |f| require f }
23
+
24
+ config.include WarningHelpers
20
25
 
21
26
  config.around :each, :if_active_support_time => true do |example|
22
27
  example.run if defined? ActiveSupport
@@ -50,4 +55,10 @@ RSpec.configure do |config|
50
55
  end
51
56
  end
52
57
 
58
+ config.around :each, expect_warnings: true do |example|
59
+ capture_warnings do
60
+ example.run
61
+ end
62
+ end
63
+
53
64
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ice_cube
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Crepezzi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-04 00:00:00.000000000 Z
11
+ date: 2015-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -83,6 +83,7 @@ files:
83
83
  - lib/ice_cube/flexible_hash.rb
84
84
  - lib/ice_cube/occurrence.rb
85
85
  - lib/ice_cube/parsers/hash_parser.rb
86
+ - lib/ice_cube/parsers/ical_parser.rb
86
87
  - lib/ice_cube/parsers/yaml_parser.rb
87
88
  - lib/ice_cube/rule.rb
88
89
  - lib/ice_cube/rules/daily_rule.rb
@@ -102,6 +103,7 @@ files:
102
103
  - lib/ice_cube/validations/day_of_month.rb
103
104
  - lib/ice_cube/validations/day_of_week.rb
104
105
  - lib/ice_cube/validations/day_of_year.rb
106
+ - lib/ice_cube/validations/fixed_value.rb
105
107
  - lib/ice_cube/validations/hour_of_day.rb
106
108
  - lib/ice_cube/validations/hourly_interval.rb
107
109
  - lib/ice_cube/validations/lock.rb
@@ -137,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
139
  version: '0'
138
140
  requirements: []
139
141
  rubyforge_project: ice-cube
140
- rubygems_version: 2.2.2
142
+ rubygems_version: 2.4.6
141
143
  signing_key:
142
144
  specification_version: 4
143
145
  summary: Ruby Date Recurrence Library