rrule 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/lib/rrule/rule.rb +53 -53
- data/lib/rrule.rb +3 -0
- data/rrule.gemspec +1 -1
- data/spec/rule_spec.rb +94 -5
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f4e67330d4bebaf8408d3125386565754c269ef
|
4
|
+
data.tar.gz: e1bfbeadff4cd2cf622360d38357d954bd9f5984
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55b57448971a919141f491bc141d017eb2bd98421ed0f7cf6bf79d51859fb5cf5e6647d1c28b9738e4465258108fa436be006466b25428823a49b6220808216f
|
7
|
+
data.tar.gz: 72792b23c6a0e483a37d16501d1a79a2efc2303e1e0113bf03dc83bcd6775933f0a946bbf7c3f52be211d17a5e87560977874f57b90223af567a2f2a302a8bc0
|
data/README.md
CHANGED
@@ -30,6 +30,14 @@ rrule.between(Time.new(2016, 6, 23), Time.new(2016, 6, 24))
|
|
30
30
|
=> [2016-06-23 16:45:32 -0700]
|
31
31
|
```
|
32
32
|
|
33
|
+
You can limit the number of instances that are returned with the `limit` option:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
rrule = RRule::Rule.new('FREQ=DAILY;COUNT=3')
|
37
|
+
rrule.all(limit: 2)
|
38
|
+
=> [2016-06-23 16:45:32 -0700, 2016-06-24 16:45:32 -0700]
|
39
|
+
```
|
40
|
+
|
33
41
|
By default the DTSTART of the recurrence is the current time, but this can be overriden with the `dtstart` option:
|
34
42
|
|
35
43
|
```ruby
|
data/lib/rrule/rule.rb
CHANGED
@@ -1,44 +1,33 @@
|
|
1
1
|
module RRule
|
2
2
|
class Rule
|
3
|
-
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :dtstart, :tz, :exdate
|
4
6
|
|
5
7
|
def initialize(rrule, dtstart: Time.now, tzid: 'UTC', exdate: [])
|
6
|
-
@
|
7
|
-
# This removes all sub-second and floors it to the second level.
|
8
|
-
# Sub-second level calculations breaks a lot of assumptions in this
|
9
|
-
# library and rounding it may also cause unexpected inequalities.
|
10
|
-
@dtstart = Time.at(dtstart.to_i).in_time_zone(tzid)
|
8
|
+
@dtstart = floor_to_seconds(dtstart).in_time_zone(tzid)
|
11
9
|
@tz = tzid
|
12
10
|
@exdate = exdate
|
11
|
+
@options = parse_options(rrule)
|
13
12
|
end
|
14
13
|
|
15
|
-
def all
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def between(start_date, end_date)
|
20
|
-
# This removes all sub-second and floors it to the second level.
|
21
|
-
# Sub-second level calculations breaks a lot of assumptions in this
|
22
|
-
# library and rounding it may also cause unexpected inequalities.
|
23
|
-
floored_start_date = Time.at(start_date.to_i)
|
24
|
-
floored_end_date = Time.at(end_date.to_i)
|
25
|
-
reject_exdates(all_until(floored_end_date).reject { |instance| instance < floored_start_date })
|
14
|
+
def all(limit: nil)
|
15
|
+
all_until(limit: limit)
|
26
16
|
end
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
18
|
+
def between(start_date, end_date, limit: nil)
|
19
|
+
floored_start_date = floor_to_seconds(start_date)
|
20
|
+
floored_end_date = floor_to_seconds(end_date)
|
21
|
+
all_until(end_date: floored_end_date, limit: limit).reject { |instance| instance < floored_start_date }
|
32
22
|
end
|
33
23
|
|
34
|
-
def
|
35
|
-
|
24
|
+
def each
|
25
|
+
return enum_for(:each) unless block_given?
|
36
26
|
|
37
27
|
context = Context.new(options, dtstart, tz)
|
38
28
|
context.rebuild(dtstart.year, dtstart.month)
|
39
29
|
|
40
30
|
timeset = options[:timeset]
|
41
|
-
total = 0
|
42
31
|
count = options[:count]
|
43
32
|
|
44
33
|
filters = []
|
@@ -81,7 +70,7 @@ module RRule
|
|
81
70
|
end
|
82
71
|
|
83
72
|
loop do
|
84
|
-
return
|
73
|
+
return if frequency.current_date.year > MAX_YEAR
|
85
74
|
|
86
75
|
possible_days_of_year = frequency.possible_days
|
87
76
|
|
@@ -90,42 +79,45 @@ module RRule
|
|
90
79
|
end
|
91
80
|
|
92
81
|
results_with_time = generator.combine_dates_and_times(possible_days_of_year, timeset)
|
93
|
-
results_with_time.
|
94
|
-
if
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
if options[:until]
|
101
|
-
if this_result > options[:until]
|
102
|
-
return result
|
103
|
-
end
|
104
|
-
result.push(this_result)
|
105
|
-
elsif this_result >= dtstart
|
106
|
-
total += 1
|
107
|
-
if options[:count]
|
108
|
-
count -= 1
|
109
|
-
result.push(this_result)
|
110
|
-
return result if count == 0
|
111
|
-
else
|
112
|
-
result.push(this_result)
|
113
|
-
end
|
114
|
-
end
|
82
|
+
results_with_time.each do |this_result|
|
83
|
+
next if this_result < dtstart
|
84
|
+
return if options[:until] && this_result > options[:until]
|
85
|
+
return if count && (count -= 1) < 0
|
86
|
+
yield this_result unless exdate.include?(this_result)
|
115
87
|
end
|
116
88
|
|
117
89
|
frequency.advance
|
118
90
|
end
|
119
91
|
end
|
120
92
|
|
121
|
-
def
|
122
|
-
|
93
|
+
def next
|
94
|
+
enumerator.next
|
123
95
|
end
|
124
96
|
|
125
|
-
|
97
|
+
private
|
98
|
+
|
99
|
+
attr_reader :options
|
100
|
+
|
101
|
+
def floor_to_seconds(date)
|
102
|
+
return date
|
103
|
+
# This removes all sub-second and floors it to the second level.
|
104
|
+
# Sub-second level calculations breaks a lot of assumptions in this
|
105
|
+
# library and rounding it may also cause unexpected inequalities.
|
106
|
+
Time.at(date.to_i)
|
107
|
+
end
|
108
|
+
|
109
|
+
def enumerator
|
110
|
+
@enumerator ||= to_enum
|
111
|
+
end
|
112
|
+
|
113
|
+
def all_until(end_date: MAX_DATE, limit: nil)
|
114
|
+
limit ? take(limit) : take_while { |date| date <= end_date }
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_options(rule)
|
126
118
|
options = { interval: 1, wkst: 1 }
|
127
119
|
|
128
|
-
params =
|
120
|
+
params = rule.split(';')
|
129
121
|
params.each do |param|
|
130
122
|
option, value = param.split('=')
|
131
123
|
|
@@ -133,11 +125,19 @@ module RRule
|
|
133
125
|
when 'FREQ'
|
134
126
|
options[:freq] = value
|
135
127
|
when 'COUNT'
|
136
|
-
|
128
|
+
i = begin
|
129
|
+
Integer(value)
|
130
|
+
rescue ArgumentError
|
131
|
+
raise InvalidRRule, "COUNT must be a non-negative integer"
|
132
|
+
end
|
133
|
+
raise InvalidRRule, "COUNT must be a non-negative integer" if i < 0
|
134
|
+
options[:count] = i
|
137
135
|
when 'UNTIL'
|
138
136
|
options[:until] = Time.parse(value)
|
139
137
|
when 'INTERVAL'
|
140
|
-
|
138
|
+
i = Integer(value) rescue 0
|
139
|
+
raise InvalidRRule, "INTERVAL must be a positive integer" unless i > 0
|
140
|
+
options[:interval] = i
|
141
141
|
when 'BYDAY'
|
142
142
|
options[:byweekday] = value.split(',').map { |day| Weekday.parse(day) }
|
143
143
|
when 'BYSETPOS'
|
data/lib/rrule.rb
CHANGED
data/rrule.gemspec
CHANGED
data/spec/rule_spec.rb
CHANGED
@@ -1,6 +1,35 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe RRule::Rule do
|
4
|
+
describe '#next' do
|
5
|
+
it 'can sequentially return values' do
|
6
|
+
rrule = 'FREQ=DAILY;COUNT=10'
|
7
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
8
|
+
timezone = 'America/New_York'
|
9
|
+
|
10
|
+
rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
|
11
|
+
|
12
|
+
expect(rrule.next).to eql Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
13
|
+
expect(rrule.next).to eql Time.parse('Wed Sep 3 06:00:00 PDT 1997')
|
14
|
+
expect(rrule.next).to eql Time.parse('Thu Sep 4 06:00:00 PDT 1997')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#take' do
|
19
|
+
it 'can return the next N instances' do
|
20
|
+
rrule = 'FREQ=DAILY;COUNT=10'
|
21
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
22
|
+
timezone = 'America/New_York'
|
23
|
+
|
24
|
+
rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
|
25
|
+
expect(rrule.take(3)).to match_array([
|
26
|
+
Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
|
27
|
+
Time.parse('Wed Sep 3 06:00:00 PDT 1997'),
|
28
|
+
Time.parse('Thu Sep 4 06:00:00 PDT 1997')
|
29
|
+
])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
4
33
|
describe '#all' do
|
5
34
|
it 'returns the correct result with an rrule of FREQ=DAILY;COUNT=10' do
|
6
35
|
rrule = 'FREQ=DAILY;COUNT=10'
|
@@ -22,6 +51,21 @@ describe RRule::Rule do
|
|
22
51
|
])
|
23
52
|
end
|
24
53
|
|
54
|
+
it 'returns the correct result with an rrule of FREQ=DAILY;COUNT=10 and a limit' do
|
55
|
+
rrule = 'FREQ=DAILY;COUNT=10'
|
56
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
57
|
+
timezone = 'America/New_York'
|
58
|
+
|
59
|
+
rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
|
60
|
+
expect(rrule.all(limit: 5)).to match_array([
|
61
|
+
Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
|
62
|
+
Time.parse('Wed Sep 3 06:00:00 PDT 1997'),
|
63
|
+
Time.parse('Thu Sep 4 06:00:00 PDT 1997'),
|
64
|
+
Time.parse('Fri Sep 5 06:00:00 PDT 1997'),
|
65
|
+
Time.parse('Sat Sep 6 06:00:00 PDT 1997')
|
66
|
+
])
|
67
|
+
end
|
68
|
+
|
25
69
|
it 'returns the correct result with an rrule of FREQ=DAILY;UNTIL=19971224T000000Z' do
|
26
70
|
rrule = 'FREQ=DAILY;UNTIL=19971224T000000Z'
|
27
71
|
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
@@ -1668,8 +1712,8 @@ describe RRule::Rule do
|
|
1668
1712
|
Time.parse('Mon Aug 29 19:00:00 PDT 2016')
|
1669
1713
|
])
|
1670
1714
|
end
|
1671
|
-
|
1672
1715
|
end
|
1716
|
+
|
1673
1717
|
describe '#between' do
|
1674
1718
|
it 'returns the correct result with an rrule of FREQ=DAILY;INTERVAL=2' do
|
1675
1719
|
rrule = 'FREQ=DAILY;INTERVAL=2'
|
@@ -1707,6 +1751,21 @@ describe RRule::Rule do
|
|
1707
1751
|
])
|
1708
1752
|
end
|
1709
1753
|
|
1754
|
+
it 'returns the correct result with an rrule of FREQ=DAILY;INTERVAL=2 and a limit' do
|
1755
|
+
rrule = 'FREQ=DAILY;INTERVAL=2'
|
1756
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
1757
|
+
timezone = 'America/New_York'
|
1758
|
+
|
1759
|
+
rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
|
1760
|
+
expect(rrule.between(Time.parse('Tue Sep 2 06:00:00 PDT 1997'), Time.parse('Wed Oct 22 06:00:00 PDT 1997'), limit: 5)).to match_array([
|
1761
|
+
Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
|
1762
|
+
Time.parse('Thu Sep 4 06:00:00 PDT 1997'),
|
1763
|
+
Time.parse('Sat Sep 6 06:00:00 PDT 1997'),
|
1764
|
+
Time.parse('Mon Sep 8 06:00:00 PDT 1997'),
|
1765
|
+
Time.parse('Wed Sep 10 06:00:00 PDT 1997')
|
1766
|
+
])
|
1767
|
+
end
|
1768
|
+
|
1710
1769
|
it 'returns the correct result with an rrule of FREQ=WEEKLY;INTERVAL=2;WKST=SU' do
|
1711
1770
|
rrule = 'FREQ=WEEKLY;INTERVAL=2;WKST=SU'
|
1712
1771
|
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
@@ -1958,10 +2017,13 @@ describe RRule::Rule do
|
|
1958
2017
|
dtstart = Time.parse('Thu Jan 28 17:00:00 PST 2016').in_time_zone(timezone)
|
1959
2018
|
|
1960
2019
|
rrule = RRule.parse(rrule, dtstart: dtstart, tzid: timezone)
|
1961
|
-
expect(rrule.between(
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
2020
|
+
expect(rrule.between(
|
2021
|
+
Time.parse('Tue May 24 14:34:59 PDT 2016'),
|
2022
|
+
Time.parse('Sun Jul 24 14:35:09 PDT 2016')
|
2023
|
+
)).to match_array([
|
2024
|
+
Time.parse('Thu Jun 16 17:00:00 PDT 2016'),
|
2025
|
+
Time.parse('Thu Jul 14 17:00:00 PDT 2016')
|
2026
|
+
])
|
1965
2027
|
end
|
1966
2028
|
|
1967
2029
|
it 'returns the correct result with a date start right on the year border' do
|
@@ -1985,4 +2047,31 @@ describe RRule::Rule do
|
|
1985
2047
|
expect(rule.between(start_time, end_time)).to eql([expected_instance])
|
1986
2048
|
end
|
1987
2049
|
end
|
2050
|
+
|
2051
|
+
describe 'validation' do
|
2052
|
+
it 'raises RRule::InvalidRRule if INTERVAL is not a positive integer' do
|
2053
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
2054
|
+
timezone = 'America/New_York'
|
2055
|
+
|
2056
|
+
expect { RRule::Rule.new('FREQ=DAILY;INTERVAL=0', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2057
|
+
expect { RRule::Rule.new('FREQ=DAILY;INTERVAL=-1', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2058
|
+
expect { RRule::Rule.new('FREQ=DAILY;INTERVAL=1.1', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2059
|
+
expect { RRule::Rule.new('FREQ=DAILY;INTERVAL=BOOM', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2060
|
+
end
|
2061
|
+
|
2062
|
+
it 'raises RRule::InvalidRRule if COUNT is not an integer' do
|
2063
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
2064
|
+
timezone = 'America/New_York'
|
2065
|
+
|
2066
|
+
expect { RRule::Rule.new('FREQ=DAILY;COUNT=BOOM', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2067
|
+
expect { RRule::Rule.new('FREQ=DAILY;COUNT=1.5', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2068
|
+
end
|
2069
|
+
|
2070
|
+
it 'raises RRule::InvalidRRule if COUNT is negative' do
|
2071
|
+
dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
|
2072
|
+
timezone = 'America/New_York'
|
2073
|
+
|
2074
|
+
expect { RRule::Rule.new('FREQ=DAILY;COUNT=-1', dtstart: dtstart, tzid: timezone) }.to raise_error(RRule::InvalidRRule)
|
2075
|
+
end
|
2076
|
+
end
|
1988
2077
|
end
|