cyclical 0.1.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/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +85 -0
- data/Rakefile +1 -0
- data/cyclical.gemspec +24 -0
- data/lib/cyclical/filters/monthdays_filter.rb +38 -0
- data/lib/cyclical/filters/months_filter.rb +55 -0
- data/lib/cyclical/filters/weekdays_filter.rb +90 -0
- data/lib/cyclical/filters/yeardays_filter.rb +38 -0
- data/lib/cyclical/occurrence.rb +121 -0
- data/lib/cyclical/rule.rb +235 -0
- data/lib/cyclical/rules/daily_rule.rb +49 -0
- data/lib/cyclical/rules/monthly_rule.rb +56 -0
- data/lib/cyclical/rules/weekly_rule.rb +58 -0
- data/lib/cyclical/rules/yearly_rule.rb +66 -0
- data/lib/cyclical/schedule.rb +125 -0
- data/lib/cyclical/suboccurrence.rb +48 -0
- data/lib/cyclical/version.rb +3 -0
- data/lib/cyclical.rb +12 -0
- data/spec/filters/monthdays_filter_spec.rb +36 -0
- data/spec/filters/months_filter_spec.rb +19 -0
- data/spec/filters/weekdays_filter_spec.rb +108 -0
- data/spec/filters/yeardays_filter_spec.rb +36 -0
- data/spec/occurrence_spec.rb +207 -0
- data/spec/rule_dsl_spec.rb +105 -0
- data/spec/rules/daily_rule_spec.rb +140 -0
- data/spec/rules/monthly_rule_spec.rb +218 -0
- data/spec/rules/weekly_rule_spec.rb +150 -0
- data/spec/rules/yearly_rule_spec.rb +232 -0
- data/spec/schedule_spec.rb +650 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/suboccurrence_spec.rb +51 -0
- metadata +126 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2011 Viktor Charypar
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Cyclical
|
2
|
+
|
3
|
+
Recurring events library for ruby calendar applications.
|
4
|
+
|
5
|
+
## About
|
6
|
+
|
7
|
+
Cyclical lets you list recurring events with complex recurrence rules like "every 4 years, the first Tuesday after a Monday in November" in a simple way. The API is inspired by [ice_cube](https://github.com/seejohnrun/ice_cube) and uses method chaining for natural rule specification.
|
8
|
+
|
9
|
+
You can find out if a given time matches the schedule, list event occurrences or add event duration and list suboccurrences in a given interval, which is handy when you need to trim event occurences to the interval (like rendering a day in a week view of a calendar with events crossing midnight).
|
10
|
+
|
11
|
+
Cyclical was originally extracted from a browser based calendar application and is written in ruby. There is a [JavaScript implementation of Cyclical](https://github.com/charypar/cyclical-js) supporting the same features, intended as a front-end counterpart. You can pass data between the implementations using the built-in JSON serialization.
|
12
|
+
|
13
|
+
### Missing features and TODO
|
14
|
+
|
15
|
+
* Rule exception dates
|
16
|
+
* Hourly and secondly rules
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
gem 'cyclical'
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
$ bundle
|
27
|
+
|
28
|
+
Or install it yourself as:
|
29
|
+
|
30
|
+
$ gem install cyclical
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
The central thing in Cyclical is the ```Schedule```. Let's take the example of U.S. Presidential Election day from RFC 5545:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
include Cyclical
|
38
|
+
|
39
|
+
date = Time.local(1997, 8, 2, 9, 0, 0)
|
40
|
+
schedule = Schedule.new date, Rule.yearly(4).month(11).weekday(:tue).monthdays(2, 3, 4, 5, 6, 7, 8)
|
41
|
+
|
42
|
+
election_dates = schedule.first(3);
|
43
|
+
```
|
44
|
+
|
45
|
+
### Creating schedules
|
46
|
+
|
47
|
+
Each schedule has a base ```date``` and a recurrence rule. The four supported rules are:
|
48
|
+
|
49
|
+
* daily
|
50
|
+
* weekly
|
51
|
+
* monthly
|
52
|
+
* yearly
|
53
|
+
|
54
|
+
with corresponding factory methods on ```Rule```. The factory methods take a single argument - the repetition interval.
|
55
|
+
|
56
|
+
The basic recurrence rule matches the original date, i.e. for a yearly rule, the occurences will always happen on the same date. To specify a more complex pattern, you can use filters.
|
57
|
+
|
58
|
+
Filters replace the single value (day, month) with a set of values that match. For example, instead of only matching the day of month in of the base date, with the ```monthdays``` filter, you can match multiple month days.
|
59
|
+
|
60
|
+
Available filters are:
|
61
|
+
|
62
|
+
* weekday(s)
|
63
|
+
* monthday(s)
|
64
|
+
* yearday(s)
|
65
|
+
* month(s)
|
66
|
+
|
67
|
+
Each filter methord takes variable arguments containing integers or string (incl. shortcuts) for a given date component.
|
68
|
+
|
69
|
+
You can limit the schedule either by a number of events (using the ```count``` method) or an end date (using the ```stop``` method).
|
70
|
+
|
71
|
+
### Querying occurrences and suboccurrences
|
72
|
+
|
73
|
+
TODO. See ```lib/cyclical/schedule.rb```
|
74
|
+
|
75
|
+
### Serialization and deserialization
|
76
|
+
|
77
|
+
TODO. See ```lib/cyclical/schedule.rb```
|
78
|
+
|
79
|
+
### More examples
|
80
|
+
|
81
|
+
TODO. See RFC 5545 examples in ```spec/schedule_spec.rb```
|
82
|
+
|
83
|
+
## License
|
84
|
+
|
85
|
+
Cyclical is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/cyclical.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cyclical/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "cyclical"
|
8
|
+
gem.version = Cyclical::VERSION
|
9
|
+
gem.authors = ["Viktor Charypar"]
|
10
|
+
gem.email = ["charypar@gmail.com"]
|
11
|
+
gem.description = %q{Cyclical lets you list recurring events with complex recurrence rules like "every 4 years, the first Tuesday after a Monday in November" in a simple way.}
|
12
|
+
gem.summary = %q{Recurring events library for calendar applications.}
|
13
|
+
gem.homepage = "http://github.com/charypar/cyclical"
|
14
|
+
gem.license = 'MIT'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_runtime_dependency 'active_support'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cyclical
|
2
|
+
class MonthdaysFilter
|
3
|
+
|
4
|
+
attr_reader :monthdays
|
5
|
+
|
6
|
+
def initialize(*monthdays)
|
7
|
+
raise ArgumentError, "Specify at least one day of the month" if monthdays.empty?
|
8
|
+
|
9
|
+
@monthdays = monthdays.sort
|
10
|
+
end
|
11
|
+
|
12
|
+
def match?(date)
|
13
|
+
last = date.end_of_month.day
|
14
|
+
(@monthdays.include?(date.day) || @monthdays.include?(date.day - last - 1))
|
15
|
+
end
|
16
|
+
|
17
|
+
def step
|
18
|
+
1.day
|
19
|
+
end
|
20
|
+
|
21
|
+
# FIXME - this can probably be calculated
|
22
|
+
def next(date)
|
23
|
+
until match?(date)
|
24
|
+
date += 1.day
|
25
|
+
end
|
26
|
+
|
27
|
+
date
|
28
|
+
end
|
29
|
+
|
30
|
+
def previous(date)
|
31
|
+
until match?(date)
|
32
|
+
date -= 1.day
|
33
|
+
end
|
34
|
+
|
35
|
+
date
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Cyclical
|
2
|
+
class MonthsFilter
|
3
|
+
|
4
|
+
MONTH_NAMES = {
|
5
|
+
:jan => 1, :january => 1,
|
6
|
+
:feb => 2, :february => 2,
|
7
|
+
:mar => 3, :march => 3,
|
8
|
+
:apr => 4, :april => 4,
|
9
|
+
:may => 5,
|
10
|
+
:jun => 6, :june => 6,
|
11
|
+
:jul => 7, :july => 7,
|
12
|
+
:aug => 8, :august => 8,
|
13
|
+
:sep => 9, :sept => 9, :september => 9,
|
14
|
+
:oct => 10, :october => 10,
|
15
|
+
:nov => 11, :november => 11,
|
16
|
+
:dec => 12, :december => 12
|
17
|
+
}
|
18
|
+
|
19
|
+
attr_reader :months
|
20
|
+
|
21
|
+
def initialize(*months)
|
22
|
+
raise ArgumentError, "Specify at least one month" if months.empty?
|
23
|
+
|
24
|
+
@months = months.map { |m| m.is_a?(Integer) ? m : MONTH_NAMES[m.to_sym] }.sort
|
25
|
+
end
|
26
|
+
|
27
|
+
def match?(date)
|
28
|
+
@months.include?(date.mon)
|
29
|
+
end
|
30
|
+
|
31
|
+
def step
|
32
|
+
1.month
|
33
|
+
end
|
34
|
+
|
35
|
+
def next(date)
|
36
|
+
return date if match?(date)
|
37
|
+
|
38
|
+
if month = @months.find { |m| m > date.month }
|
39
|
+
date.beginning_of_year + (month - 1).months + date.hour.hours + date.min.minutes + date.sec.seconds
|
40
|
+
else
|
41
|
+
date.beginning_of_year + 1.year + (@months.first - 1).months + date.hour.hours + date.min.minutes + date.sec.seconds
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def previous(date)
|
46
|
+
return date if match?(date)
|
47
|
+
|
48
|
+
if month = @months.reverse.find { |m| m < date.month }
|
49
|
+
date.beginning_of_year + month.months - 1.day + date.hour.hours + date.min.minutes + date.sec.seconds
|
50
|
+
else
|
51
|
+
date.beginning_of_year - 1.year + @months.last.months - 1.day + date.hour.hours + date.min.minutes + date.sec.seconds
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Cyclical
|
2
|
+
class WeekdaysFilter
|
3
|
+
|
4
|
+
WEEKDAYS = {
|
5
|
+
:su => 0, :sun => 0, :sunday => 0,
|
6
|
+
:mo => 1, :mon => 1, :monday => 1,
|
7
|
+
:tu => 2, :tue => 2, :tuesday => 2,
|
8
|
+
:we => 3, :wed => 3, :wednesday => 3,
|
9
|
+
:th => 4, :thu => 4, :thursday => 4,
|
10
|
+
:fr => 5, :fri => 5, :friday => 5,
|
11
|
+
:sa => 6, :sat => 6, :saturday => 6
|
12
|
+
}
|
13
|
+
|
14
|
+
WEEKDAY_NAMES = [:su, :mo, :tu, :we, :th, :fr, :sa]
|
15
|
+
|
16
|
+
attr_reader :weekdays, :ordered_weekdays, :rule
|
17
|
+
|
18
|
+
def initialize(*weekdays)
|
19
|
+
@rule = weekdays.shift if weekdays.first.is_a?(Rule)
|
20
|
+
|
21
|
+
raise ArgumentError, "Specify at least one weekday" if weekdays.empty?
|
22
|
+
@ordered_weekdays = {}
|
23
|
+
|
24
|
+
if weekdays.last.respond_to?(:has_key?)
|
25
|
+
raise ArgumentError, "No recurrence rule given for ordered weekdays filter" if @rule.nil?
|
26
|
+
|
27
|
+
weekdays.last.each do |day, orders|
|
28
|
+
day = day.is_a?(Integer) ? day : WEEKDAYS[day]
|
29
|
+
orders = [orders] unless orders.respond_to?(:each)
|
30
|
+
|
31
|
+
@ordered_weekdays[WEEKDAY_NAMES[day]] = orders.sort
|
32
|
+
end
|
33
|
+
weekdays = weekdays[0..-2]
|
34
|
+
end
|
35
|
+
|
36
|
+
@weekdays = weekdays.map { |w| w.is_a?(Integer) ? w : WEEKDAYS[w.to_sym] }.sort
|
37
|
+
end
|
38
|
+
|
39
|
+
def match?(date)
|
40
|
+
return true if weekdays.include?(date.wday)
|
41
|
+
|
42
|
+
day = WEEKDAY_NAMES[date.wday]
|
43
|
+
return false if ordered_weekdays[day].nil?
|
44
|
+
|
45
|
+
first, occ, max = order_in_interval(date)
|
46
|
+
|
47
|
+
return (ordered_weekdays[day].include?(occ) || ordered_weekdays[day].include?(occ - max - 1))
|
48
|
+
end
|
49
|
+
|
50
|
+
def step
|
51
|
+
1.day
|
52
|
+
end
|
53
|
+
|
54
|
+
# FIXME - this can probably be calculated
|
55
|
+
def next(date)
|
56
|
+
until match?(date)
|
57
|
+
date += 1.day
|
58
|
+
end
|
59
|
+
|
60
|
+
date
|
61
|
+
end
|
62
|
+
|
63
|
+
def previous(date)
|
64
|
+
until match?(date)
|
65
|
+
date -= 1.day
|
66
|
+
end
|
67
|
+
|
68
|
+
date
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def order_in_interval(date)
|
74
|
+
case @rule
|
75
|
+
when YearlyRule
|
76
|
+
first = (7 + date.wday - date.beginning_of_year.wday) % 7 + 1
|
77
|
+
occ = (date.yday - first) / 7 + 1
|
78
|
+
max = (date.end_of_year.yday - first) / 7 + 1
|
79
|
+
when MonthlyRule
|
80
|
+
first = (7 + date.wday - date.beginning_of_month.wday) % 7 + 1
|
81
|
+
occ = (date.day - first) / 7 + 1
|
82
|
+
max = (date.end_of_month.day - first) / 7 + 1
|
83
|
+
else
|
84
|
+
raise RuntimeError, "Ordered weekdays filter only supports monthy and yearly rules. (#{@rule.class} given)"
|
85
|
+
end
|
86
|
+
|
87
|
+
[first, occ, max]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cyclical
|
2
|
+
class YeardaysFilter
|
3
|
+
|
4
|
+
attr_reader :yeardays
|
5
|
+
|
6
|
+
def initialize(*yeardays)
|
7
|
+
raise ArgumentError, "Specify at least one day of the month" if yeardays.empty?
|
8
|
+
|
9
|
+
@yeardays = yeardays.sort
|
10
|
+
end
|
11
|
+
|
12
|
+
def match?(date)
|
13
|
+
last = date.end_of_year.yday
|
14
|
+
(@yeardays.include?(date.yday) || @yeardays.include?(date.yday - last - 1))
|
15
|
+
end
|
16
|
+
|
17
|
+
def step
|
18
|
+
1.day
|
19
|
+
end
|
20
|
+
|
21
|
+
# FIXME - traverse the days directly
|
22
|
+
def next(date)
|
23
|
+
until match?(date)
|
24
|
+
date += 1.day
|
25
|
+
end
|
26
|
+
|
27
|
+
date
|
28
|
+
end
|
29
|
+
|
30
|
+
def previous(date)
|
31
|
+
until match?(date)
|
32
|
+
date -= 1.day
|
33
|
+
end
|
34
|
+
|
35
|
+
date
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'cyclical/suboccurrence'
|
2
|
+
|
3
|
+
module Cyclical
|
4
|
+
# Holds an occurence of a recurrence rule, can compute next and previous and list occurrences
|
5
|
+
class Occurrence
|
6
|
+
|
7
|
+
attr_reader :rule, :start_time
|
8
|
+
attr_accessor :duration
|
9
|
+
|
10
|
+
def initialize(rule, start_time)
|
11
|
+
@rule = rule
|
12
|
+
@start_time = @rule.match?(start_time, start_time) ? start_time : @rule.next(start_time, start_time)
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_occurrence(after)
|
16
|
+
next_occurrences(1, after).first
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_occurrences(n, after)
|
20
|
+
return [] if @rule.stop && after > @rule.stop
|
21
|
+
time = (after <= @start_time ? @start_time : after)
|
22
|
+
time = @rule.next(time, @start_time) unless @rule.match?(time, @start_time)
|
23
|
+
|
24
|
+
list_occurrences(time) { (n -= 1) >= 0 }
|
25
|
+
end
|
26
|
+
|
27
|
+
def previous_occurrence(before)
|
28
|
+
previous_occurrences(1, before).first
|
29
|
+
end
|
30
|
+
|
31
|
+
def previous_occurrences(n, before)
|
32
|
+
return [] if before <= @start_time
|
33
|
+
time = (@rule.stop.nil? || before < @rule.stop ? before : @rule.stop)
|
34
|
+
time = @rule.previous(time, @start_time) # go back even if before matches the rule (half-open time intervals, remember?)
|
35
|
+
|
36
|
+
list_occurrences(time, :back) { (n -= 1) >= 0 }.reverse
|
37
|
+
end
|
38
|
+
|
39
|
+
def occurrences_between(t1, t2)
|
40
|
+
raise ArgumentError, "Empty time interval" unless t2 > t1
|
41
|
+
return [] if t2 <= @start_time || @rule.stop && t1 >= @rule.stop
|
42
|
+
|
43
|
+
time = (t1 <= @start_time ? @start_time : t1)
|
44
|
+
time = @rule.next(time, @start_time) unless @rule.match?(time, @start_time)
|
45
|
+
|
46
|
+
list_occurrences(time) { |t| t < t2 }
|
47
|
+
end
|
48
|
+
|
49
|
+
def suboccurrences_between(t1, t2)
|
50
|
+
occurrences = occurrences_between(t1 - duration, t2)
|
51
|
+
occurrences.map { |occ| Suboccurrence.find(:occurrence => (occ)..(occ + duration), :interval => t1..t2) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def all
|
55
|
+
if @rule.stop
|
56
|
+
list_occurrences(@start_time) { |t| t < @rule.stop }
|
57
|
+
else
|
58
|
+
n = @rule.count
|
59
|
+
list_occurrences(@start_time) { (n -= 1) >= 0 }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_hash
|
64
|
+
@rule.to_hash
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# yields valid occurrences, return false from the block to stop
|
70
|
+
def list_occurrences(from, direction = :forward, &block)
|
71
|
+
raise ArgumentError, "From #{from} not matching the rule #{@rule} and start time #{@start_time}" unless @rule.match?(from, @start_time)
|
72
|
+
|
73
|
+
results = []
|
74
|
+
|
75
|
+
n, current = init_loop(from, direction)
|
76
|
+
loop do
|
77
|
+
# Rails.logger.debug("Listing occurrences of #{@rule}, going #{direction.to_s}, current: #{current}")
|
78
|
+
# break on schedule span limits
|
79
|
+
return results unless (current >= @start_time) && (@rule.stop.nil? || current < @rule.stop) && (@rule.count.nil? || (n -= 1) >= 0)
|
80
|
+
|
81
|
+
# break on block condition
|
82
|
+
return results unless yield current
|
83
|
+
|
84
|
+
results << current
|
85
|
+
|
86
|
+
# step
|
87
|
+
if direction == :forward
|
88
|
+
current = @rule.next(current, @start_time)
|
89
|
+
else
|
90
|
+
current = @rule.previous(current, @start_time)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def init_loop(from, direction)
|
96
|
+
return 0, from unless @rule.count # without count limit, life is easy
|
97
|
+
|
98
|
+
# with it, it's... well...
|
99
|
+
if direction == :forward
|
100
|
+
n = 0
|
101
|
+
current = @start_time
|
102
|
+
while current < from
|
103
|
+
n += 1
|
104
|
+
current = @rule.next(current, @start_time)
|
105
|
+
end
|
106
|
+
|
107
|
+
# return the n remaining events
|
108
|
+
return (@rule.count - n), current
|
109
|
+
else
|
110
|
+
n = 0
|
111
|
+
current = @start_time
|
112
|
+
while current < from && (n += 1) < @rule.count
|
113
|
+
current = @rule.next(current, @start_time)
|
114
|
+
end
|
115
|
+
|
116
|
+
# return all events (downloop - yaay, I invented a word - will stop on start time)
|
117
|
+
return @rule.count, current
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|