rrule 0.3.1 → 0.4.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: cef0ad524f55bb65033204ddb1b134d6e9fb0c6f
4
- data.tar.gz: 881e0c1e363a355b8908fb036568525e8b77dacf
3
+ metadata.gz: 524b148cf5608df2145bb75da8b5bca29867a30a
4
+ data.tar.gz: d258adb876e71ce558ffa5831d2964ddd9263e00
5
5
  SHA512:
6
- metadata.gz: e3a41b83fbd8b90734f1b9c97d493f8e4fa2e6314e300603b6cb0d9949151d60a3bcc34073a03c37e2aeece752ec99e9633f5c5f48b9f9eadb445407f3e5abb3
7
- data.tar.gz: d4a47101d49896dcc2ac9ab2108ae64c1873fc52f1bea394623508f8887d3c23748c728ffaede12a079b662f0da33256f5dd30970845e41348a12444b8b5d394
6
+ metadata.gz: 47e40a473aaf70514150dc11c6779cb3bb31e2d985324416df60515e678bd111fae56a2ff84529e4deab3572c477fa7cdbcfc1437f1e429db25056e1564f026b
7
+ data.tar.gz: 02ff7616862c0307a838f04eb4714fb57f9d261991199c09560ef51dd4d0c6cb223f4d2ff584d2ac94d09337b5dbd00c8a4bb96a6a52bdf1f7a02c34e680acab
@@ -18,6 +18,7 @@ module RRule
18
18
  autoload :ByYearDay, 'rrule/filters/by_year_day'
19
19
  autoload :ByMonthDay, 'rrule/filters/by_month_day'
20
20
 
21
+ autoload :Generator, 'rrule/generators/generator'
21
22
  autoload :AllOccurrences, 'rrule/generators/all_occurrences'
22
23
  autoload :BySetPosition, 'rrule/generators/by_set_position'
23
24
 
@@ -39,10 +39,10 @@ module RRule
39
39
  end
40
40
  end
41
41
  end
42
-
43
- @last_year = year
44
- @last_month = month
45
42
  end
43
+
44
+ @last_year = year
45
+ @last_month = month
46
46
  end
47
47
 
48
48
  def year_length_in_days
@@ -82,7 +82,7 @@ module RRule
82
82
  end
83
83
 
84
84
  def negative_week_number_by_day_of_year
85
- @negative_month_day_by_day_of_year ||= days_in_year.map { |day| day.cweek - Date.new(day.cwyear, 12, 28).cweek - 1 }
85
+ @negative_week_number_by_day_of_year ||= days_in_year.map { |day| day.cweek - Date.new(day.cwyear, 12, 28).cweek - 1 }
86
86
  end
87
87
 
88
88
  def elapsed_days_in_year_by_month
@@ -2,9 +2,9 @@ module RRule
2
2
  class Frequency
3
3
  attr_reader :current_date, :filters, :generator, :timeset
4
4
 
5
- def initialize(context, filters, generator, timeset)
5
+ def initialize(context, filters, generator, timeset, start_date: nil)
6
6
  @context = context
7
- @current_date = context.dtstart
7
+ @current_date = start_date.presence || context.dtstart
8
8
  @filters = filters
9
9
  @generator = generator
10
10
  @timeset = timeset
@@ -50,6 +50,8 @@ module RRule
50
50
  Monthly
51
51
  when 'YEARLY'
52
52
  Yearly
53
+ else
54
+ raise InvalidRRule, "Valid FREQ value is required"
53
55
  end
54
56
  end
55
57
 
@@ -1,9 +1,22 @@
1
1
  module RRule
2
2
  class SimpleWeekly < Frequency
3
3
  def next_occurrences
4
+ correct_current_date_if_needed
4
5
  this_occurrence = current_date
5
6
  @current_date += context.options[:interval].weeks
6
- [this_occurrence]
7
+ generator.process_timeset(this_occurrence, timeset)
8
+ end
9
+
10
+ def correct_current_date_if_needed
11
+ if context.options[:byweekday].present?
12
+ target_wday = context.options[:byweekday].first.index
13
+ else
14
+ target_wday = context.dtstart.wday
15
+ end
16
+
17
+ while @current_date.wday != target_wday
18
+ @current_date = @current_date + 1.day
19
+ end
7
20
  end
8
21
  end
9
22
  end
@@ -1,25 +1,8 @@
1
1
  module RRule
2
- class AllOccurrences
3
- attr_reader :context
4
-
5
- def initialize(context)
6
- @context = context
7
- end
8
-
2
+ class AllOccurrences < Generator
9
3
  def combine_dates_and_times(dayset, timeset)
10
4
  dayset.compact.map { |i| context.first_day_of_year + i }.flat_map do |date|
11
- timeset.map do |time|
12
- Time.use_zone(context.tz) do
13
- Time.zone.local(
14
- date.year,
15
- date.month,
16
- date.day,
17
- time[:hour],
18
- time[:minute],
19
- time[:second]
20
- )
21
- end
22
- end
5
+ process_timeset(date, timeset)
23
6
  end
24
7
  end
25
8
  end
@@ -1,26 +1,15 @@
1
1
  module RRule
2
- class BySetPosition
3
- attr_reader :by_set_positions, :context
2
+ class BySetPosition < Generator
3
+ attr_reader :by_set_positions
4
4
 
5
5
  def initialize(by_set_positions, context)
6
6
  @by_set_positions = by_set_positions
7
- @context = context
7
+ super(context)
8
8
  end
9
9
 
10
10
  def combine_dates_and_times(dayset, timeset)
11
11
  valid_dates(dayset).flat_map do |date|
12
- timeset.map do |time|
13
- Time.use_zone(context.tz) do
14
- Time.zone.local(
15
- date.year,
16
- date.month,
17
- date.day,
18
- time[:hour],
19
- time[:minute],
20
- time[:second]
21
- )
22
- end
23
- end
12
+ process_timeset(date, timeset)
24
13
  end
25
14
  end
26
15
 
@@ -0,0 +1,34 @@
1
+ module RRule
2
+ class Generator
3
+ attr_reader :context
4
+
5
+ def initialize(context)
6
+ @context = context
7
+ end
8
+
9
+ def process_timeset(date, timeset)
10
+ timeset.map do |time|
11
+ hour_sets = (
12
+ Array.wrap(time[:hour]).sort.map do |hour|
13
+ Array.wrap(time[:minute]).sort.map do |minute|
14
+ Array.wrap(time[:second]).sort.map{ |second| [hour, minute, second]}
15
+ end
16
+ end
17
+ ).flatten(2)
18
+
19
+ Time.use_zone(context.tz) do
20
+ hour_sets.map do |hour, minute, second|
21
+ Time.zone.local(
22
+ date.year,
23
+ date.month,
24
+ date.day,
25
+ hour,
26
+ minute,
27
+ second
28
+ )
29
+ end
30
+ end
31
+ end.flatten
32
+ end
33
+ end
34
+ end
@@ -9,6 +9,7 @@ module RRule
9
9
  @tz = tzid
10
10
  @exdate = exdate
11
11
  @options = parse_options(rrule)
12
+ @frequency_type = Frequency.for_options(options)
12
13
  @max_year = max_year || 9999
13
14
  @max_date = DateTime.new(@max_year)
14
15
  end
@@ -20,14 +21,19 @@ module RRule
20
21
  def between(start_date, end_date, limit: nil)
21
22
  floored_start_date = floor_to_seconds(start_date)
22
23
  floored_end_date = floor_to_seconds(end_date)
23
- all_until(end_date: floored_end_date, limit: limit).reject { |instance| instance < floored_start_date }
24
+ all_until(start_date: floored_start_date, end_date: floored_end_date, limit: limit).reject { |instance| instance < floored_start_date }
24
25
  end
25
26
 
26
- def each
27
- return enum_for(:each) unless block_given?
27
+ def each(floor_date: nil)
28
+ floor_date ||= dtstart
29
+ # If we have a COUNT or INTERVAL option, we have to start at dtstart, because those are relative to dtstart
30
+ if count_or_interval_present?
31
+ floor_date = dtstart
32
+ end
28
33
 
34
+ return enum_for(:each, floor_date: floor_date) unless block_given?
29
35
  context = Context.new(options, dtstart, tz)
30
- context.rebuild(dtstart.year, dtstart.month)
36
+ context.rebuild(floor_date.year, floor_date.month)
31
37
 
32
38
  timeset = options[:timeset]
33
39
  count = options[:count]
@@ -59,13 +65,14 @@ module RRule
59
65
  generator = AllOccurrences.new(context)
60
66
  end
61
67
 
62
- frequency = Frequency.for_options(options).new(context, filters, generator, timeset)
68
+ frequency = Frequency.for_options(options).new(context, filters, generator, timeset, start_date: floor_date)
63
69
 
64
70
  loop do
65
71
  return if frequency.current_date.year > max_year
66
72
 
67
73
  frequency.next_occurrences.each do |this_result|
68
74
  next if this_result < dtstart
75
+ next if floor_date.present? && this_result < floor_date
69
76
  return if options[:until] && this_result > options[:until]
70
77
  return if count && (count -= 1) < 0
71
78
  yield this_result unless exdate.include?(this_result)
@@ -79,7 +86,7 @@ module RRule
79
86
 
80
87
  private
81
88
 
82
- attr_reader :options, :max_year, :max_date
89
+ attr_reader :options, :max_year, :max_date, :frequency_type
83
90
 
84
91
  def floor_to_seconds(date)
85
92
  # This removes all sub-second and floors it to the second level.
@@ -92,8 +99,8 @@ module RRule
92
99
  @enumerator ||= to_enum
93
100
  end
94
101
 
95
- def all_until(end_date: max_date, limit: nil)
96
- limit ? take(limit) : take_while { |date| date <= end_date }
102
+ def all_until(start_date: nil, end_date: max_date, limit: nil)
103
+ limit ? take(limit) : each(floor_date: start_date).take_while { |date| date <= end_date }
97
104
  end
98
105
 
99
106
  def parse_options(rule)
@@ -120,6 +127,12 @@ module RRule
120
127
  i = Integer(value) rescue 0
121
128
  raise InvalidRRule, "INTERVAL must be a positive integer" unless i > 0
122
129
  options[:interval] = i
130
+ when 'BYHOUR'
131
+ options[:byhour] = value.split(',').compact.map(&:to_i)
132
+ when 'BYMINUTE'
133
+ options[:byminute] = value.split(',').compact.map(&:to_i)
134
+ when 'BYSECOND'
135
+ options[:bysecond] = value.split(',').compact.map(&:to_i)
123
136
  when 'BYDAY'
124
137
  options[:byweekday] = value.split(',').map { |day| Weekday.parse(day) }
125
138
  when 'BYSETPOS'
@@ -156,9 +169,13 @@ module RRule
156
169
  options[:byweekday], options[:bynweekday] = options[:byweekday].partition { |wday| wday.ordinal.nil? }
157
170
  end
158
171
 
159
- options[:timeset] = [{ hour: dtstart.hour, minute: dtstart.min, second: dtstart.sec }]
172
+ options[:timeset] = [{ hour: (options[:byhour].presence || dtstart.hour), minute: (options[:byminute].presence || dtstart.min), second: (options[:bysecond].presence || dtstart.sec) }]
160
173
 
161
174
  options
162
175
  end
176
+
177
+ def count_or_interval_present?
178
+ options[:count].present? || (options[:interval].present? && options[:interval] > 1)
179
+ end
163
180
  end
164
181
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'rrule'
3
- s.version = '0.3.1'
3
+ s.version = '0.4.0'
4
4
  s.date = '2018-04-24'
5
5
  s.summary = 'RRule expansion'
6
6
  s.description = 'A gem for expanding dates according to the RRule specification'
@@ -1,19 +1,21 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe RRule::SimpleWeekly do
4
- let(:context) { RRule::Context.new({ interval: interval }, date, nil) }
5
- let(:frequency) { described_class.new(context, nil, nil, nil) }
4
+ let(:context) { RRule::Context.new({ interval: interval }, date, 'America/Los_Angeles') }
5
+ let(:frequency) { described_class.new(context, nil, generator, timeset) }
6
+ let(:generator) { RRule::AllOccurrences.new(context) }
7
+ let(:timeset) { [{ hour: date.hour, minute: date.min, second: date.sec }] }
6
8
 
7
9
  describe '#next_occurrences' do
8
- let(:date) { Date.new(1997, 1, 1) }
10
+ let(:date) { Time.zone.local(1997, 1, 1) }
9
11
 
10
12
  context 'with an interval of 1' do
11
13
  let(:interval) { 1 }
12
14
 
13
15
  it 'returns occurrences every week' do
14
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 1)]
15
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 8)]
16
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 15)]
16
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 1)]
17
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 8)]
18
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 15)]
17
19
  end
18
20
  end
19
21
 
@@ -21,9 +23,9 @@ describe RRule::SimpleWeekly do
21
23
  let(:interval) { 2 }
22
24
 
23
25
  it 'returns occurrences every other week' do
24
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 1)]
25
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 15)]
26
- expect(frequency.next_occurrences).to eql [Date.new(1997, 1, 29)]
26
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 1)]
27
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 15)]
28
+ expect(frequency.next_occurrences).to eql [Time.zone.local(1997, 1, 29)]
27
29
  end
28
30
  end
29
31
  end
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ describe RRule::Generator do
4
+ let(:context) do
5
+ RRule::Context.new(
6
+ { interval: 1, wkst: 1 },
7
+ Time.parse('Wed Jan 1 00:00:00 PST 1997'),
8
+ 'America/Los_Angeles'
9
+ )
10
+ end
11
+
12
+ around(:each) do |example|
13
+ Time.use_zone('America/Los_Angeles') do
14
+ example.run
15
+ end
16
+ end
17
+
18
+ before(:each) { context.rebuild(1997, 1) }
19
+
20
+ describe '#process_timeset' do
21
+ describe "single timeset" do
22
+ subject { described_class.new(context).process_timeset(date, timeset)}
23
+
24
+ context 'with a timeset with only 1 set' do
25
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
26
+ let(:timeset) { [{ hour: 12, minute: 30, second: 15 }] }
27
+
28
+ it { is_expected.to match_array [Time.parse('Wed Jan 1 12:30:15 PST 1997')] }
29
+ end
30
+
31
+ context 'with multiple hours in the timeset' do
32
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
33
+ let(:timeset) { [{ hour: [12, 15], minute: 30, second: 15 }] }
34
+
35
+ it { is_expected.to match_array [Time.parse('Wed Jan 1 12:30:15 PST 1997'), Time.parse('Wed Jan 1 15:30:15 PST 1997')] }
36
+ end
37
+
38
+ context 'with multiple minutes in the timeset' do
39
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
40
+ let(:timeset) { [{ hour: [12], minute: [15, 30], second: 15 }] }
41
+
42
+ it { is_expected.to match_array [Time.parse('Wed Jan 1 12:15:15 PST 1997'), Time.parse('Wed Jan 1 12:30:15 PST 1997')] }
43
+ end
44
+
45
+ context 'with multiple seconds in the timeset' do
46
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
47
+ let(:timeset) { [{ hour: [12], minute: [30], second: [15, 59] }] }
48
+
49
+ it { is_expected.to match_array [Time.parse('Wed Jan 1 12:30:15 PST 1997'), Time.parse('Wed Jan 1 12:30:59 PST 1997')] }
50
+ end
51
+
52
+ context 'with multiple hours, minutes, and seconds in the timeset' do
53
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
54
+ let(:timeset) { [{ hour: [12, 20], minute: [30, 55], second: [15, 59] }] }
55
+
56
+ it { is_expected.to eq [
57
+ Time.parse('Wed Jan 1 12:30:15 PST 1997'),
58
+ Time.parse('Wed Jan 1 12:30:59 PST 1997'),
59
+ Time.parse('Wed Jan 1 12:55:15 PST 1997'),
60
+ Time.parse('Wed Jan 1 12:55:59 PST 1997'),
61
+ Time.parse('Wed Jan 1 20:30:15 PST 1997'),
62
+ Time.parse('Wed Jan 1 20:30:59 PST 1997'),
63
+ Time.parse('Wed Jan 1 20:55:15 PST 1997'),
64
+ Time.parse('Wed Jan 1 20:55:59 PST 1997'),
65
+ ] }
66
+ end
67
+
68
+ context 'with multiple hours, minutes, and seconds in the timeset, unsorted' do
69
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
70
+ let(:timeset) { [{ hour: [20, 12], minute: [55, 30], second: [59, 15] }] }
71
+
72
+ it { is_expected.to eq [
73
+ Time.parse('Wed Jan 1 12:30:15 PST 1997'),
74
+ Time.parse('Wed Jan 1 12:30:59 PST 1997'),
75
+ Time.parse('Wed Jan 1 12:55:15 PST 1997'),
76
+ Time.parse('Wed Jan 1 12:55:59 PST 1997'),
77
+ Time.parse('Wed Jan 1 20:30:15 PST 1997'),
78
+ Time.parse('Wed Jan 1 20:30:59 PST 1997'),
79
+ Time.parse('Wed Jan 1 20:55:15 PST 1997'),
80
+ Time.parse('Wed Jan 1 20:55:59 PST 1997'),
81
+ ] }
82
+ end
83
+ end
84
+ end
85
+
86
+ describe "multiple timesets" do
87
+ subject { described_class.new(context).process_timeset(date, timeset)}
88
+
89
+ context 'with multiple timsets' do
90
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
91
+ let(:timeset) { [{ hour: 12, minute: 30, second: 15 }, { hour: 18, minute: 45, second: 20 }] }
92
+
93
+ it { is_expected.to eq [Time.parse('Wed Jan 1 12:30:15 PST 1997'), Time.parse('Wed Jan 1 18:45:20 PST 1997')] }
94
+ end
95
+
96
+ context 'with multiple timsets with multiple hour sets' do
97
+ let(:date) { Time.parse('Wed Jan 1 00:11:22 PST 1997') }
98
+ let(:timeset) { [{ hour: [12, 20], minute: 30, second: [15, 45] }, { hour: 18, minute: [22, 45], second: 20 }] }
99
+
100
+ it { is_expected.to eq [
101
+ Time.parse('Wed Jan 1 12:30:15 PST 1997'),
102
+ Time.parse('Wed Jan 1 12:30:45 PST 1997'),
103
+ Time.parse('Wed Jan 1 20:30:15 PST 1997'),
104
+ Time.parse('Wed Jan 1 20:30:45 PST 1997'),
105
+ Time.parse('Wed Jan 1 18:22:20 PST 1997'),
106
+ Time.parse('Wed Jan 1 18:45:20 PST 1997')
107
+ ] }
108
+ end
109
+ end
110
+ end
@@ -30,6 +30,80 @@ describe RRule::Rule do
30
30
  end
31
31
  end
32
32
 
33
+ describe 'iterating with a floor_date' do
34
+ describe 'No COUNT or INTERVAL > 1' do
35
+ it 'uses the floor_date provided when iterating' do
36
+ rrule = 'FREQ=DAILY'
37
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
38
+ timezone = 'America/New_York'
39
+
40
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
41
+
42
+ floor_date = Time.parse('Mon Sep 3 06:00:00 PDT 2018')
43
+
44
+ expect(rrule.each(floor_date: floor_date).take(3)).to match_array([
45
+ Time.parse('Tue Sep 3 06:00:00 PDT 2018'),
46
+ Time.parse('Wed Sep 4 06:00:00 PDT 2018'),
47
+ Time.parse('Thu Sep 5 06:00:00 PDT 2018')
48
+ ])
49
+ end
50
+ end
51
+
52
+ describe 'COUNT present' do
53
+ it 'starts at dtstart when iterating' do
54
+ rrule = 'FREQ=DAILY;COUNT=10'
55
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
56
+ timezone = 'America/New_York'
57
+
58
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
59
+
60
+ floor_date = Time.parse('Mon Sep 3 06:00:00 PDT 2018')
61
+
62
+ expect(rrule.each(floor_date: floor_date).take(3)).to match_array([
63
+ Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
64
+ Time.parse('Wed Sep 3 06:00:00 PDT 1997'),
65
+ Time.parse('Thu Sep 4 06:00:00 PDT 1997'),
66
+ ])
67
+ end
68
+ end
69
+
70
+ describe 'INTERVAL present' do
71
+ it 'starts at dtstart when iterating' do
72
+ rrule = 'FREQ=DAILY;INTERVAL=10'
73
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
74
+ timezone = 'America/New_York'
75
+
76
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
77
+
78
+ floor_date = Time.parse('Mon Sep 3 06:00:00 PDT 2018')
79
+
80
+ expect(rrule.each(floor_date: floor_date).take(3)).to match_array([
81
+ Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
82
+ Time.parse('Fri Sep 12 06:00:00 PDT 1997'),
83
+ Time.parse('Mon Sep 22 06:00:00 PDT 1997'),
84
+ ])
85
+ end
86
+ end
87
+
88
+ describe 'INTERVAL AND COUNT present' do
89
+ it 'starts at dtstart when iterating' do
90
+ rrule = 'FREQ=DAILY;INTERVAL=10;COUNT=5'
91
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
92
+ timezone = 'America/New_York'
93
+
94
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
95
+
96
+ floor_date = Time.parse('Mon Sep 3 06:00:00 PDT 2018')
97
+
98
+ expect(rrule.each(floor_date: floor_date).take(3)).to match_array([
99
+ Time.parse('Tue Sep 2 06:00:00 PDT 1997'),
100
+ Time.parse('Fri Sep 12 06:00:00 PDT 1997'),
101
+ Time.parse('Mon Sep 22 06:00:00 PDT 1997'),
102
+ ])
103
+ end
104
+ end
105
+ end
106
+
33
107
  describe '#all' do
34
108
  it 'returns the correct result with an rrule of FREQ=DAILY;COUNT=10' do
35
109
  rrule = 'FREQ=DAILY;COUNT=10'
@@ -1715,6 +1789,25 @@ describe RRule::Rule do
1715
1789
  end
1716
1790
 
1717
1791
  describe '#between' do
1792
+ it 'returns the correct result with an rrule of FREQ=WEEKLY;BYSECOND=59;BYMINUTE=59;BYHOUR=23;WKST=SU' do
1793
+ rrule = 'FREQ=WEEKLY;BYSECOND=59;BYMINUTE=59;BYHOUR=23;WKST=SU'
1794
+ dtstart = DateTime.parse('2018-02-04 04:00:00 +1000')
1795
+ timezone = 'Brisbane'
1796
+
1797
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
1798
+ expect(rrule.between(Time.parse('Sun, 08 Apr 2018 00:00:00 +0000'), Time.parse('Fri, 08 Jun 2018 23:59:59 +0000'))).to match_array([
1799
+ Time.parse('Sun, 08 Apr 2018 23:59:59 +1000'),
1800
+ Time.parse('Sun, 15 Apr 2018 23:59:59 +1000'),
1801
+ Time.parse('Sun, 22 Apr 2018 23:59:59 +1000'),
1802
+ Time.parse('Sun, 29 Apr 2018 23:59:59 +1000'),
1803
+ Time.parse('Sun, 06 May 2018 23:59:59 +1000'),
1804
+ Time.parse('Sun, 13 May 2018 23:59:59 +1000'),
1805
+ Time.parse('Sun, 20 May 2018 23:59:59 +1000'),
1806
+ Time.parse('Sun, 27 May 2018 23:59:59 +1000'),
1807
+ Time.parse('Sun, 03 Jun 2018 23:59:59 +1000'),
1808
+ ])
1809
+ end
1810
+
1718
1811
  it 'returns the correct result with an rrule of FREQ=DAILY;INTERVAL=2' do
1719
1812
  rrule = 'FREQ=DAILY;INTERVAL=2'
1720
1813
  dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
@@ -1979,7 +2072,7 @@ describe RRule::Rule do
1979
2072
  ])
1980
2073
  end
1981
2074
 
1982
- it 'returns the correct result with an rrule of FREQ=DAILY;COUNT=7 when the range extends beyond the end of the recurrence' do
2075
+ it 'returns the correct result with an rrule of FREQ=DAILY;COUNT=7 when the range extends beyond the end of the recurrence (run out of COUNT before the range ends)' do
1983
2076
  rrule ='FREQ=DAILY;COUNT=7'
1984
2077
  dtstart = Time.parse('Thu Feb 6 16:00:00 PST 2014')
1985
2078
  timezone = 'America/Los_Angeles'
@@ -2002,7 +2095,7 @@ describe RRule::Rule do
2002
2095
  expect(rrule.all).to match_array([
2003
2096
  Time.parse('Tue Sep 2 06:00:00 PST 1997'),
2004
2097
  Time.parse('Wed Sep 3 06:00:00 PST 1997'),
2005
- Time.parse('Thu Sep 4 06:00:00 PST 1997"'),
2098
+ Time.parse('Thu Sep 4 06:00:00 PST 1997'),
2006
2099
  Time.parse('Sat Sep 6 06:00:00 PST 1997'),
2007
2100
  Time.parse('Sun Sep 7 06:00:00 PST 1997'),
2008
2101
  Time.parse('Tue Sep 9 06:00:00 PST 1997'),
@@ -2046,9 +2139,159 @@ describe RRule::Rule do
2046
2139
  end_time = Time.parse('Wed Aug 31 21:59:59 PDT 2016')
2047
2140
  expect(rule.between(start_time, end_time)).to eql([expected_instance])
2048
2141
  end
2142
+
2143
+
2144
+ describe 'iterating with a floor_date' do
2145
+ describe 'No COUNT or INTERVAL > 1' do
2146
+ it 'still limits to the given range' do
2147
+ rrule = 'FREQ=DAILY'
2148
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
2149
+ timezone = 'America/New_York'
2150
+
2151
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2152
+
2153
+ start_time = Time.parse('Mon Sep 3 06:00:00 PDT 2018')
2154
+ end_time = Time.parse('Thu Sep 10 06:00:00 PDT 2018')
2155
+
2156
+ expect(rrule.between(start_time, end_time).take(3)).to match_array([
2157
+ Time.parse('Tue Sep 3 06:00:00 PDT 2018'),
2158
+ Time.parse('Wed Sep 4 06:00:00 PDT 2018'),
2159
+ Time.parse('Thu Sep 5 06:00:00 PDT 2018')
2160
+ ])
2161
+ end
2162
+ end
2163
+
2164
+ describe 'COUNT present' do
2165
+ it 'still limits to the given range' do
2166
+ rrule = 'FREQ=DAILY;COUNT=10'
2167
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
2168
+ timezone = 'America/New_York'
2169
+
2170
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2171
+
2172
+ start_time = Time.parse('Wed Sep 3 06:00:00 PDT 1997')
2173
+ end_time = Time.parse('Thu Sep 10 06:00:00 PDT 2018')
2174
+
2175
+ expect(rrule.between(start_time, end_time).take(3)).to match_array([
2176
+ Time.parse('Tue Sep 3 06:00:00 PDT 1997'),
2177
+ Time.parse('Wed Sep 4 06:00:00 PDT 1997'),
2178
+ Time.parse('Thu Sep 5 06:00:00 PDT 1997'),
2179
+ ])
2180
+ end
2181
+ end
2182
+
2183
+ describe 'INTERVAL present' do
2184
+ it 'still limits to the given range' do
2185
+ rrule = 'FREQ=DAILY;INTERVAL=10'
2186
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
2187
+ timezone = 'America/New_York'
2188
+
2189
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2190
+
2191
+ start_time = Time.parse('Wed Sep 3 06:00:00 PDT 1997')
2192
+ end_time = Time.parse('Thu Sep 30 06:00:00 PDT 2018')
2193
+
2194
+ expect(rrule.between(start_time, end_time).take(3)).to match_array([
2195
+ Time.parse('Tue Sep 12 06:00:00 PDT 1997'),
2196
+ Time.parse('Fri Sep 22 06:00:00 PDT 1997'),
2197
+ Time.parse('Mon Oct 02 06:00:00 PDT 1997'),
2198
+ ])
2199
+ end
2200
+ end
2201
+
2202
+ describe 'INTERVAL AND COUNT present' do
2203
+ it 'still limits to the given range' do
2204
+ rrule = 'FREQ=DAILY;INTERVAL=10;COUNT=5'
2205
+ dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
2206
+ timezone = 'America/New_York'
2207
+
2208
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2209
+
2210
+ start_time = Time.parse('Wed Sep 3 06:00:00 PDT 1997')
2211
+ end_time = Time.parse('Thu Sep 30 06:00:00 PDT 2018')
2212
+
2213
+ expect(rrule.between(start_time, end_time).take(3)).to match_array([
2214
+ Time.parse('Tue Sep 12 06:00:00 PDT 1997'),
2215
+ Time.parse('Fri Sep 22 06:00:00 PDT 1997'),
2216
+ Time.parse('Mon Oct 02 06:00:00 PDT 1997'),
2217
+ ])
2218
+ end
2219
+ end
2220
+ end
2221
+ end
2222
+
2223
+ it 'returns the correct result with an rrule of FREQ=WEEKLY;BYMONTH=1,3;COUNT=4;BYHOUR=2' do
2224
+ rrule = 'FREQ=WEEKLY;BYMONTH=1,3;COUNT=4;BYHOUR=2'
2225
+ dtstart = Time.parse('Tue Sep 2 09:00:00 PDT 1997')
2226
+ timezone = 'America/Los_Angeles'
2227
+
2228
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2229
+
2230
+ expect(rrule.all).to match_array([
2231
+ Time.parse('Tue Jan 6 02:00:00 PST 1998'),
2232
+ Time.parse('Tue Jan 13 02:00:00 PST 1998'),
2233
+ Time.parse('Tue Jan 20 02:00:00 PST 1998'),
2234
+ Time.parse('Tue Jan 27 02:00:00 PST 1998')
2235
+ ])
2236
+ end
2237
+
2238
+ it 'returns the correct result with an rrule of FREQ=WEEKLY;BYMONTH=1,3;COUNT=4;BYHOUR=2;BYMINUTE=44' do
2239
+ rrule = 'FREQ=WEEKLY;BYMONTH=1,3;COUNT=4;BYHOUR=2;BYMINUTE=44'
2240
+ dtstart = Time.parse('Tue Sep 2 09:00:00 PDT 1997')
2241
+ timezone = 'America/Los_Angeles'
2242
+
2243
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2244
+ expect(rrule.all).to match_array([
2245
+ Time.parse('Tue Jan 6 02:44:00 PST 1998'),
2246
+ Time.parse('Tue Jan 13 02:44:00 PST 1998'),
2247
+ Time.parse('Tue Jan 20 02:44:00 PST 1998'),
2248
+ Time.parse('Tue Jan 27 02:44:00 PST 1998')
2249
+ ])
2250
+ end
2251
+
2252
+ it 'returns the correct result with an rrule of FREQ=WEEKLY;BYMONTH=1,3;COUNT=24;BYHOUR=2,4,6;BYMINUTE=33,22' do
2253
+ rrule = 'FREQ=WEEKLY;BYMONTH=1,3;COUNT=24;BYHOUR=2,4,6;BYMINUTE=33,22'
2254
+ dtstart = Time.parse('Tue Sep 2 09:23:42 PDT 1997')
2255
+ timezone = 'America/Los_Angeles'
2256
+
2257
+ rrule = RRule::Rule.new(rrule, dtstart: dtstart, tzid: timezone)
2258
+ expect(rrule.all).to match_array([
2259
+ Time.parse('Tue Jan 6 02:22:42 PST 1998'),
2260
+ Time.parse('Tue Jan 6 02:33:42 PST 1998'),
2261
+ Time.parse('Tue Jan 6 04:22:42 PST 1998'),
2262
+ Time.parse('Tue Jan 6 04:33:42 PST 1998'),
2263
+ Time.parse('Tue Jan 6 06:22:42 PST 1998'),
2264
+ Time.parse('Tue Jan 6 06:33:42 PST 1998'),
2265
+ Time.parse('Tue Jan 13 02:22:42 PST 1998'),
2266
+ Time.parse('Tue Jan 13 02:33:42 PST 1998'),
2267
+ Time.parse('Tue Jan 13 04:22:42 PST 1998'),
2268
+ Time.parse('Tue Jan 13 04:33:42 PST 1998'),
2269
+ Time.parse('Tue Jan 13 06:22:42 PST 1998'),
2270
+ Time.parse('Tue Jan 13 06:33:42 PST 1998'),
2271
+ Time.parse('Tue Jan 20 02:22:42 PST 1998'),
2272
+ Time.parse('Tue Jan 20 02:33:42 PST 1998'),
2273
+ Time.parse('Tue Jan 20 04:22:42 PST 1998'),
2274
+ Time.parse('Tue Jan 20 04:33:42 PST 1998'),
2275
+ Time.parse('Tue Jan 20 06:22:42 PST 1998'),
2276
+ Time.parse('Tue Jan 20 06:33:42 PST 1998'),
2277
+ Time.parse('Tue Jan 27 02:22:42 PST 1998'),
2278
+ Time.parse('Tue Jan 27 02:33:42 PST 1998'),
2279
+ Time.parse('Tue Jan 27 04:22:42 PST 1998'),
2280
+ Time.parse('Tue Jan 27 04:33:42 PST 1998'),
2281
+ Time.parse('Tue Jan 27 06:22:42 PST 1998'),
2282
+ Time.parse('Tue Jan 27 06:33:42 PST 1998')
2283
+ ])
2049
2284
  end
2050
2285
 
2051
2286
  describe 'validation' do
2287
+ it 'raises RRule::InvalidRRule if FREQ is not provided' do
2288
+ expect { RRule::Rule.new('') }.to raise_error(RRule::InvalidRRule)
2289
+ expect { RRule::Rule.new('FREQ=') }.to raise_error(RRule::InvalidRRule)
2290
+ expect { RRule::Rule.new('FREQ=FOO') }.to raise_error(RRule::InvalidRRule)
2291
+ expect { RRule::Rule.new('COUNT=1') }.to raise_error(RRule::InvalidRRule)
2292
+ expect { RRule::Rule.new('FREQ=FOO;COUNT=1') }.to raise_error(RRule::InvalidRRule)
2293
+ end
2294
+
2052
2295
  it 'raises RRule::InvalidRRule if INTERVAL is not a positive integer' do
2053
2296
  dtstart = Time.parse('Tue Sep 2 06:00:00 PDT 1997')
2054
2297
  timezone = 'America/New_York'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rrule
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Mitchell
@@ -67,6 +67,7 @@ files:
67
67
  - lib/rrule/frequencies/yearly.rb
68
68
  - lib/rrule/generators/all_occurrences.rb
69
69
  - lib/rrule/generators/by_set_position.rb
70
+ - lib/rrule/generators/generator.rb
70
71
  - lib/rrule/rule.rb
71
72
  - lib/rrule/weekday.rb
72
73
  - rrule.gemspec
@@ -85,6 +86,7 @@ files:
85
86
  - spec/frequencies/yearly_spec.rb
86
87
  - spec/generators/all_occurrences_spec.rb
87
88
  - spec/generators/by_set_position_spec.rb
89
+ - spec/generators/generator_spec.rb
88
90
  - spec/rule_spec.rb
89
91
  - spec/spec_helper.rb
90
92
  - spec/weekday_spec.rb
@@ -107,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
109
  version: '0'
108
110
  requirements: []
109
111
  rubyforge_project:
110
- rubygems_version: 2.5.2
112
+ rubygems_version: 2.6.13
111
113
  signing_key:
112
114
  specification_version: 4
113
115
  summary: RRule expansion
@@ -125,6 +127,7 @@ test_files:
125
127
  - spec/frequencies/yearly_spec.rb
126
128
  - spec/generators/all_occurrences_spec.rb
127
129
  - spec/generators/by_set_position_spec.rb
130
+ - spec/generators/generator_spec.rb
128
131
  - spec/rule_spec.rb
129
132
  - spec/spec_helper.rb
130
133
  - spec/weekday_spec.rb