ice_cube 0.12.1 → 0.13.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.
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