ice_cube_chosko 0.1.0 → 0.1.1

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.
@@ -0,0 +1,169 @@
1
+ sv:
2
+ ice_cube:
3
+ pieces_connector: ' / '
4
+ not: 'inte %{target}'
5
+ not_on: 'inte i %{target}'
6
+ date:
7
+ formats:
8
+ default: '%-d %B %Y'
9
+ month_names:
10
+ -
11
+ - januari
12
+ - februari
13
+ - mars
14
+ - april
15
+ - maj
16
+ - juni
17
+ - juli
18
+ - augusti
19
+ - september
20
+ - oktober
21
+ - november
22
+ - december
23
+ day_names:
24
+ - söndag
25
+ - måndag
26
+ - tisdag
27
+ - onsdag
28
+ - torsdag
29
+ - fredag
30
+ - lördag
31
+ times:
32
+ other: '%{count} gånger'
33
+ one: '%{count} gång'
34
+ until: 'tills %{date}'
35
+ days_of_week: '%{segments} %{day}'
36
+ days_of_month:
37
+ other: '%{segments} dagen i månaden'
38
+ one: '%{segments} dagen i månaden'
39
+ days_of_year:
40
+ other: '%{segments} dagen på året'
41
+ one: '%{segments} dagen på året'
42
+ at_hours_of_the_day:
43
+ other: i %{segments} timme på dygnet
44
+ one: i %{segments} timme på dygnet
45
+ on_minutes_of_hour:
46
+ other: i %{segments} minuter av timmen
47
+ one: i %{segments} minuten av timmen
48
+ on_seconds_of_minute:
49
+ other: på %{segments} sekunden av minuten
50
+ one: på %{segments} sekunden av minuten
51
+ each_second:
52
+ one: Varje sekund
53
+ other: Varje %{count} sekund
54
+ each_minute:
55
+ one: Varje minut
56
+ other: Varje %{count} minut
57
+ each_hour:
58
+ one: Varje timme
59
+ other: Varje %{count} timme
60
+ each_day:
61
+ one: Varje dag
62
+ other: Varje %{count} dag
63
+ each_week:
64
+ one: Varje vecka
65
+ other: Varje %{count} vecka
66
+ each_month:
67
+ one: Varje månad
68
+ other: Varje %{count} månad
69
+ each_year:
70
+ one: Varje år
71
+ other: Varje %{count} år
72
+ 'on': i %{sentence}
73
+ in: 'efter %{target}'
74
+ integer:
75
+ negative: '%{ordinal} från slutet'
76
+ literal_ordinals:
77
+ -1: sista
78
+ -2: nästsista
79
+ ordinal: '%{number}%{ordinal}'
80
+ ordinals:
81
+ default: ''
82
+ on_weekends: på helger
83
+ on_weekdays: på vardagar
84
+ days_on:
85
+ - söndagar
86
+ - måndagar
87
+ - tisdagar
88
+ - onsdagar
89
+ - torsdagar
90
+ - fredagar
91
+ - lördagar
92
+ on_days: på %{days}
93
+ array:
94
+ last_word_connector: ' och '
95
+ two_words_connector: ' och '
96
+ words_connector: ', '
97
+ string:
98
+ format:
99
+ day: '%{rest} %{current}'
100
+ day_of_week: '%{rest} %{current}'
101
+ day_of_month: '%{rest} %{current}'
102
+ day_of_year: '%{rest} %{current}'
103
+ hour_of_day: '%{rest} %{current}'
104
+ minute_of_hour: '%{rest} %{current}'
105
+ until: '%{rest} %{current}'
106
+ count: '%{rest} %{current}'
107
+ default: '%{rest} %{current}'
108
+
109
+ date:
110
+ abbr_day_names:
111
+ - Sön
112
+ - Mån
113
+ - Tis
114
+ - Ons
115
+ - Tor
116
+ - Fre
117
+ - Lör
118
+ abbr_month_names:
119
+ -
120
+ - Jan
121
+ - Feb
122
+ - Mar
123
+ - Apr
124
+ - Maj
125
+ - Jun
126
+ - Jul
127
+ - Aug
128
+ - Sep
129
+ - Okt
130
+ - Mov
131
+ - Dec
132
+ day_names:
133
+ - Söndag
134
+ - Måndag
135
+ - Tisdag
136
+ - Onsdag
137
+ - Torsdag
138
+ - Fredag
139
+ - Lördag
140
+ formats:
141
+ default: "%Y-%m-%d"
142
+ long: "%e %B %Y"
143
+ short: "%e %b"
144
+ month_names:
145
+ -
146
+ - Januari
147
+ - Februari
148
+ - Mars
149
+ - April
150
+ - Maj
151
+ - Juni
152
+ - Juli
153
+ - Augusti
154
+ - September
155
+ - Oktober
156
+ - November
157
+ - December
158
+ order:
159
+ - :day
160
+ - :month
161
+ - :year
162
+
163
+ time:
164
+ am: ''
165
+ formats:
166
+ default: "%a, %e %b %Y %H:%M:%S %z"
167
+ long: "%e %B %Y %H:%M"
168
+ short: "%e %b %H:%M"
169
+ pm: ''
data/lib/ice_cube.rb CHANGED
@@ -43,6 +43,7 @@ module IceCube
43
43
 
44
44
  autoload :Count, 'ice_cube/validations/count'
45
45
  autoload :Until, 'ice_cube/validations/until'
46
+ autoload :OverrideDuration, 'ice_cube/validations/override_duration'
46
47
 
47
48
  autoload :SecondlyInterval, 'ice_cube/validations/secondly_interval'
48
49
  autoload :MinutelyInterval, 'ice_cube/validations/minutely_interval'
@@ -24,14 +24,18 @@ module IceCube
24
24
  end
25
25
 
26
26
  def self.rule_from_ical(ical)
27
- params = { validations: { } }
27
+ raise ArgumentError, 'empty ical rule' if ical.nil?
28
+
29
+ validations = {}
30
+ params = {validations: validations, interval: 1}
28
31
 
29
32
  ical.split(';').each do |rule|
30
33
  (name, value) = rule.split('=')
34
+ raise ArgumentError, "Invalid iCal rule component" if value.nil?
31
35
  value.strip!
32
36
  case name
33
37
  when 'FREQ'
34
- params[:freq] = value.downcase
38
+ params[:rule_type] = "IceCube::#{value[0]}#{value.downcase[1..-1]}Rule"
35
39
  when 'INTERVAL'
36
40
  params[:interval] = value.to_i
37
41
  when 'COUNT'
@@ -39,13 +43,13 @@ module IceCube
39
43
  when 'UNTIL'
40
44
  params[:until] = Time.parse(value).utc
41
45
  when 'WKST'
42
- params[:wkst] = TimeUtil.ical_day_to_symbol(value)
46
+ params[:week_start] = TimeUtil.ical_day_to_symbol(value)
43
47
  when 'BYSECOND'
44
- params[:validations][:second_of_minute] = value.split(',').collect(&:to_i)
48
+ validations[:second_of_minute] = value.split(',').map(&:to_i)
45
49
  when 'BYMINUTE'
46
- params[:validations][:minute_of_hour] = value.split(',').collect(&:to_i)
50
+ validations[:minute_of_hour] = value.split(',').map(&:to_i)
47
51
  when 'BYHOUR'
48
- params[:validations][:hour_of_day] = value.split(',').collect(&:to_i)
52
+ validations[:hour_of_day] = value.split(',').map(&:to_i)
49
53
  when 'BYDAY'
50
54
  dows = {}
51
55
  days = []
@@ -59,33 +63,21 @@ module IceCube
59
63
  days.push TimeUtil.sym_to_wday(day) if dows[day].nil?
60
64
  end
61
65
  end
62
- params[:validations][:day_of_week] = dows unless dows.empty?
63
- params[:validations][:day] = days unless days.empty?
66
+ validations[:day_of_week] = dows unless dows.empty?
67
+ validations[:day] = days unless days.empty?
64
68
  when 'BYMONTHDAY'
65
- params[:validations][:day_of_month] = value.split(',').collect(&:to_i)
69
+ validations[:day_of_month] = value.split(',').map(&:to_i)
66
70
  when 'BYMONTH'
67
- params[:validations][:month_of_year] = value.split(',').collect(&:to_i)
71
+ validations[:month_of_year] = value.split(',').map(&:to_i)
68
72
  when 'BYYEARDAY'
69
- params[:validations][:day_of_year] = value.split(',').collect(&:to_i)
73
+ validations[:day_of_year] = value.split(',').map(&:to_i)
70
74
  when 'BYSETPOS'
71
75
  else
72
- raise "Invalid or unsupported rrule command: #{name}"
76
+ validations[name] = nil # invalid type
73
77
  end
74
78
  end
75
79
 
76
- params[:interval] ||= 1
77
-
78
- # WKST only valid for weekly rules
79
- params.delete(:wkst) unless params[:freq] == 'weekly'
80
-
81
- rule = Rule.send(*params.values_at(:freq, :interval, :wkst).compact)
82
- rule.count(params[:count]) if params[:count]
83
- rule.until(params[:until]) if params[:until]
84
- params[:validations].each do |key, value|
85
- value.is_a?(Array) ? rule.send(key, *value) : rule.send(key, value)
86
- end
87
-
88
- rule
80
+ Rule.from_hash(params)
89
81
  end
90
82
  end
91
83
  end
data/lib/ice_cube/rule.rb CHANGED
@@ -4,6 +4,11 @@ module IceCube
4
4
 
5
5
  class Rule
6
6
 
7
+ INTERVAL_TYPES = [
8
+ :secondly, :minutely, :hourly,
9
+ :daily, :weekly, :monthly, :yearly
10
+ ]
11
+
7
12
  attr_reader :uses
8
13
 
9
14
  # Is this a terminating schedule?
@@ -11,6 +16,11 @@ module IceCube
11
16
  until_time || occurrence_count
12
17
  end
13
18
 
19
+ # Does this rule override the schedule's duration?
20
+ def overrides_duration?
21
+ !duration.nil?
22
+ end
23
+
14
24
  def ==(rule)
15
25
  if rule.is_a? Rule
16
26
  hash = to_hash
@@ -46,21 +56,6 @@ module IceCube
46
56
  raise MethodNotImplemented, "Expected to be overridden by subclasses"
47
57
  end
48
58
 
49
- # Convert from a hash and create a rule
50
- def self.from_hash(original_hash)
51
- hash = IceCube::FlexibleHash.new original_hash
52
- return nil unless match = hash[:rule_type].match(/\:\:(.+?)Rule/)
53
- rule = IceCube::Rule.send(match[1].downcase.to_sym, hash[:interval] || 1)
54
- rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0)) if match[1] == "Weekly"
55
- rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until]
56
- rule.count(hash[:count]) if hash[:count]
57
- hash[:validations] && hash[:validations].each do |key, value|
58
- key = key.to_sym unless key.is_a?(Symbol)
59
- value.is_a?(Array) ? rule.send(key, *value) : rule.send(key, value)
60
- end
61
- rule
62
- end
63
-
64
59
  # Reset the uses on the rule to 0
65
60
  def reset
66
61
  @uses = 0
@@ -78,6 +73,52 @@ module IceCube
78
73
  !@count.nil?
79
74
  end
80
75
 
76
+ class << self
77
+
78
+ # Convert from a hash and create a rule
79
+ def from_hash(original_hash)
80
+ hash = IceCube::FlexibleHash.new original_hash
81
+
82
+ unless hash[:rule_type] && match = hash[:rule_type].match(/\:\:(.+?)Rule/)
83
+ raise ArgumentError, 'Invalid rule type'
84
+ end
85
+
86
+ interval_type = match[1].downcase.to_sym
87
+
88
+ unless INTERVAL_TYPES.include?(interval_type)
89
+ raise ArgumentError, "Invalid rule frequency type: #{match[1]}"
90
+ end
91
+
92
+ rule = IceCube::Rule.send(interval_type, hash[:interval] || 1)
93
+
94
+ if match[1] == "Weekly"
95
+ rule.interval(hash[:interval] || 1, TimeUtil.wday_to_sym(hash[:week_start] || 0))
96
+ end
97
+
98
+ rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until]
99
+ rule.count(hash[:count]) if hash[:count]
100
+
101
+ hash[:validations] && hash[:validations].each do |name, args|
102
+ apply_validation(rule, name, args)
103
+ end
104
+
105
+ rule
106
+ end
107
+
108
+ private
109
+
110
+ def apply_validation(rule, name, args)
111
+ name = name.to_sym
112
+
113
+ unless ValidatedRule::VALIDATION_ORDER.include?(name)
114
+ raise ArgumentError, "Invalid rule validation type: #{name}"
115
+ end
116
+
117
+ args.is_a?(Array) ? rule.send(name, *args) : rule.send(name, args)
118
+ end
119
+
120
+ end
121
+
81
122
  # Convenience methods for creating Rules
82
123
  class << self
83
124
 
@@ -44,6 +44,16 @@ module IceCube
44
44
  @end_time = start_time + seconds
45
45
  end
46
46
 
47
+ # Return the maximum value between schedule's duration and
48
+ # the durations of all the recurrence and exception rules included
49
+ # in this schedule
50
+ def max_duration
51
+ all_durations = [duration]
52
+ all_durations += @all_recurrence_rules.map { |r| r.duration }.compact
53
+ all_durations += @all_exception_rules.map { |r| r.duration }.compact
54
+ all_durations.max
55
+ end
56
+
47
57
  # Add a recurrence time to the schedule
48
58
  def add_recurrence_time(time)
49
59
  return nil if time.nil?
@@ -166,55 +176,55 @@ module IceCube
166
176
  end
167
177
 
168
178
  # The next n occurrences after now
169
- def next_occurrences(num, from = nil, spans = false)
179
+ def next_occurrences(num, from = nil, options = {})
170
180
  from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
171
- enumerate_occurrences(from + 1, nil, spans).take(num)
181
+ enumerate_occurrences(from + 1, nil, options).take(num)
172
182
  end
173
183
 
174
184
  # The next occurrence after now (overridable)
175
- def next_occurrence(from = nil, spans = false)
185
+ def next_occurrence(from = nil, options = {})
176
186
  from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
177
- enumerate_occurrences(from + 1, nil, spans).next
187
+ enumerate_occurrences(from + 1, nil, options).next
178
188
  rescue StopIteration
179
189
  nil
180
190
  end
181
191
 
182
192
  # The previous occurrence from a given time
183
193
  def previous_occurrence(from)
184
- from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
194
+ from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{from.inspect}"
185
195
  return nil if from <= start_time
186
196
  enumerate_occurrences(start_time, from - 1).to_a.last
187
197
  end
188
198
 
189
199
  # The previous n occurrences before a given time
190
200
  def previous_occurrences(num, from)
191
- from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
201
+ from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{from.inspect}"
192
202
  return [] if from <= start_time
193
203
  a = enumerate_occurrences(start_time, from - 1).to_a
194
204
  a.size > num ? a[-1*num,a.size] : a
195
205
  end
196
206
 
197
207
  # The remaining occurrences (same requirements as all_occurrences)
198
- def remaining_occurrences(from = nil, spans = false)
208
+ def remaining_occurrences(from = nil, options = {})
199
209
  require_terminating_rules
200
210
  from ||= TimeUtil.now(@start_time)
201
- enumerate_occurrences(from, nil, spans).to_a
211
+ enumerate_occurrences(from, nil, options).to_a
202
212
  end
203
213
 
204
214
  # Returns an enumerator for all remaining occurrences
205
- def remaining_occurrences_enumerator(from = nil, spans = false)
215
+ def remaining_occurrences_enumerator(from = nil, options = {})
206
216
  from ||= TimeUtil.now(@start_time)
207
- enumerate_occurrences(from, nil, spans)
217
+ enumerate_occurrences(from, nil, options)
208
218
  end
209
219
 
210
220
  # Occurrences between two times
211
- def occurrences_between(begin_time, closing_time, spans = false)
212
- enumerate_occurrences(begin_time, closing_time, spans).to_a
221
+ def occurrences_between(begin_time, closing_time, options = {})
222
+ enumerate_occurrences(begin_time, closing_time, options).to_a
213
223
  end
214
224
 
215
225
  # Return a boolean indicating if an occurrence falls between two times
216
- def occurs_between?(begin_time, closing_time, spans = false)
217
- enumerate_occurrences(begin_time, closing_time, spans).next
226
+ def occurs_between?(begin_time, closing_time, options = {})
227
+ enumerate_occurrences(begin_time, closing_time, options).next
218
228
  true
219
229
  rescue StopIteration
220
230
  false
@@ -226,7 +236,7 @@ module IceCube
226
236
  # occurrences at the end of the range since none of their duration
227
237
  # intersects the range.
228
238
  def occurring_between?(opening_time, closing_time)
229
- occurs_between?(opening_time, closing_time, true)
239
+ occurs_between?(opening_time, closing_time, :spans => true)
230
240
  end
231
241
 
232
242
  # Return a boolean indicating if an occurrence falls on a certain date
@@ -240,9 +250,14 @@ module IceCube
240
250
  # Determine if the schedule is occurring at a given time
241
251
  def occurring_at?(time)
242
252
  time = TimeUtil.match_zone(time, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
243
- if duration > 0
253
+ if max_duration > 0
244
254
  return false if exception_time?(time)
245
- occurs_between?(time - duration + 1, time)
255
+ begin
256
+ enumerate_occurrences(time, time, :spans => true).next
257
+ true
258
+ rescue StopIteration
259
+ false
260
+ end
246
261
  else
247
262
  occurs_at?(time)
248
263
  end
@@ -405,15 +420,15 @@ module IceCube
405
420
  # Find all of the occurrences for the schedule between opening_time
406
421
  # and closing_time
407
422
  # Iteration is unrolled in pairs to skip duplicate times in end of DST
408
- def enumerate_occurrences(opening_time, closing_time = nil, spans = false, &block)
423
+ def enumerate_occurrences(opening_time, closing_time = nil, options = {}, &block)
409
424
  opening_time = TimeUtil.match_zone(opening_time, start_time)
410
425
  closing_time = TimeUtil.match_zone(closing_time, start_time)
411
426
  opening_time += start_time.subsec - opening_time.subsec rescue 0
412
427
  opening_time = start_time if opening_time < start_time
413
- spans = false if duration == 0
428
+ spans = options[:spans] == true && max_duration != 0
414
429
  Enumerator.new do |yielder|
415
430
  reset
416
- t1 = full_required? ? start_time : realign((spans ? opening_time - duration : opening_time))
431
+ t1 = full_required? ? start_time : realign(opening_time) - (spans ? max_duration : 0)
417
432
  loop do
418
433
  break unless (t0 = next_time(t1, closing_time))
419
434
  break if closing_time && t0 > closing_time
@@ -437,17 +452,30 @@ module IceCube
437
452
  # Get the next time after (or including) a specific time
438
453
  def next_time(time, closing_time)
439
454
  loop do
440
- min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |min_time, rule|
455
+ min_time_with_duration = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |min_time_with_duration, rule|
456
+ min_time = (min_time_with_duration && min_time_with_duration[0]) || nil
441
457
  begin
442
458
  new_time = rule.next_time(time, self, min_time || closing_time)
443
- [min_time, new_time].compact.min
459
+ new_time_with_duration = [new_time, rule.duration]
460
+
461
+ if new_time.nil?
462
+ min_time_with_duration
463
+ elsif min_time.nil?
464
+ new_time_with_duration
465
+ elsif new_time < min_time
466
+ new_time_with_duration
467
+ else
468
+ min_time_with_duration
469
+ end
444
470
  rescue StopIteration
445
- min_time
471
+ (min_time && [min_time, rule.duration]) || nil
446
472
  end
447
473
  end
474
+ min_time = (min_time_with_duration && min_time_with_duration[0]) || nil
475
+ rule_duration = (min_time_with_duration && min_time_with_duration[1]) || nil
448
476
  break nil unless min_time
449
477
  next (time = min_time + 1) if exception_time?(min_time)
450
- break Occurrence.new(min_time, min_time + duration)
478
+ break Occurrence.new(min_time, min_time + (rule_duration || duration))
451
479
  end
452
480
  end
453
481