fnando-recurrence 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -38,4 +38,18 @@
38
38
  == 0.0.7 2009-01-06
39
39
 
40
40
  * 1 major enhancement:
41
- * The events method now accepts :starts and :until dates
41
+ * The events method now accepts :starts and :until dates
42
+
43
+ == 0.0.8 2009-01-07
44
+
45
+ * 1 small enhancement:
46
+ * The iteration is stopped when the :until is reached when limiting events
47
+
48
+ == 0.1.0 2009-07-18
49
+
50
+ * 3 major enhancements:
51
+ * Allow monthly recurrences to occur by weekday:
52
+ Recurrence.new(:every => :month, :on => :first, :weekday => :thursday)
53
+ * Allow several weekdays to be given to weekly recurrence:
54
+ Recurrence.new(:every => :week, :on => [:monday, :wednesday, :friday])
55
+ * Added :daily, :weekly, :monthly and :yearly shortcuts to Recurrence.
data/README.markdown CHANGED
@@ -38,18 +38,26 @@ USAGE:
38
38
  r = Recurrence.new(:every => :day, :interval => 9)
39
39
 
40
40
  # Weekly
41
- r = Recurrence.new(:every => :week, :on => :friday)
42
41
  r = Recurrence.new(:every => :week, :on => 5)
42
+ r = Recurrence.new(:every => :week, :on => :monday)
43
+ r = Recurrence.new(:every => :week, :on => [:monday, :friday])
44
+ r = Recurrence.new(:every => :week, :on => [:monday, :wednesday, :friday])
43
45
  r = Recurrence.new(:every => :week, :on => :friday, :interval => 2)
44
46
 
45
- # Monthly
47
+ # Monthly by month day
46
48
  r = Recurrence.new(:every => :month, :on => 15)
47
49
  r = Recurrence.new(:every => :month, :on => 31)
48
50
  r = Recurrence.new(:every => :month, :on => 7, :interval => 2)
49
51
  r = Recurrence.new(:every => :month, :on => 7, :interval => :monthly)
50
52
  r = Recurrence.new(:every => :month, :on => 7, :interval => :bimonthly)
51
- r = Recurrence.new(:every => :month, :on => 7, :interval => :quarterly)
52
- r = Recurrence.new(:every => :month, :on => 7, :interval => :semesterly)
53
+
54
+ # Monthly by week day
55
+ r = Recurrence.new(:every => :month, :on => :first, :weekday => :sunday)
56
+ r = Recurrence.new(:every => :month, :on => :third, :weekday => :monday)
57
+ r = Recurrence.new(:every => :month, :on => :last, :weekday => :friday)
58
+ r = Recurrence.new(:every => :month, :on => :last, :weekday => :friday, :interval => 2)
59
+ r = Recurrence.new(:every => :month, :on => :last, :weekday => :friday, :interval => :quarterly)
60
+ r = Recurrence.new(:every => :month, :on => :last, :weekday => :friday, :interval => :semesterly)
53
61
 
54
62
  # Yearly
55
63
  r = Recurrence.new(:every => :year, :on => [7, 4]) # => [month, day]
@@ -86,7 +94,12 @@ USAGE:
86
94
  MAINTAINER
87
95
  ----------
88
96
 
89
- * Nando Vieira ([http://simplesideias.com.br](http://simplesideias.com.br))
97
+ * Nando Vieira (<http://simplesideias.com.br/>)
98
+
99
+ CONTRIBUTORS
100
+ ------------
101
+
102
+ * José Valim (<http://josevalim.blogspot.com/>)
90
103
 
91
104
  LICENSE:
92
105
  --------
@@ -110,4 +123,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
110
123
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
111
124
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
112
125
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
113
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
126
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ PKG_FILES = %w(init.rb Rakefile recurrence.gemspec History.txt License.txt READM
5
5
 
6
6
  spec = Gem::Specification.new do |s|
7
7
  s.name = "recurrence"
8
- s.version = "0.0.7"
8
+ s.version = "0.1.0"
9
9
  s.summary = "A simple library to handle recurring events"
10
10
  s.authors = ["Nando Vieira"]
11
11
  s.email = ["fnando.vieira@gmail.com"]
@@ -14,7 +14,6 @@ spec = Gem::Specification.new do |s|
14
14
  s.has_rdoc = false
15
15
  s.files = PKG_FILES
16
16
 
17
- s.add_dependency "rubigen"
18
17
  s.add_dependency "activesupport", ">=2.1.1"
19
18
  end
20
19
 
@@ -74,4 +73,4 @@ end
74
73
  desc "Execute specs"
75
74
  task :spec do
76
75
  system "spec spec/recurrence_spec.rb -c -f s"
77
- end
76
+ end
data/lib/recurrence.rb CHANGED
@@ -1,6 +1,17 @@
1
- require "rubygems"
2
- require "date"
3
- gem "activesupport", ">=2.1.1"
4
- require "activesupport"
5
- require File.dirname(__FILE__) + "/recurrence/base"
6
- require File.dirname(__FILE__) + "/recurrence/event"
1
+ require 'rubygems'
2
+ require 'date'
3
+ require 'activesupport'
4
+
5
+ dirname = File.dirname(__FILE__)
6
+ require dirname + '/recurrence/base'
7
+ require dirname + '/recurrence/shortcuts'
8
+ require dirname + '/recurrence/event'
9
+ require dirname + '/recurrence/event/daily'
10
+ require dirname + '/recurrence/event/weekly'
11
+ require dirname + '/recurrence/event/monthly'
12
+ require dirname + '/recurrence/event/yearly'
13
+
14
+ class Recurrence
15
+ include Base
16
+ extend Shortcuts
17
+ end
@@ -1,145 +1,127 @@
1
1
  class Recurrence
2
- FREQUENCY = %w(day week month year)
3
- DAYS = %w(sunday monday tuesday wednesday thursday friday saturday)
4
- MONTHS = {
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, "september" => 9,
14
- "oct" => 10, "october" => 10,
15
- "nov" => 11, "november" => 11,
16
- "dec" => 12, "december" => 12
17
- }
18
-
19
- INTERVALS = {
20
- :monthly => 1,
21
- :bimonthly => 2,
22
- :quarterly => 3,
23
- :semesterly => 6
24
- }
25
-
26
- attr_reader :event
27
-
28
- def initialize(options)
29
- raise ArgumentError, ':every option is required' unless options.key?(:every)
30
- raise ArgumentError, 'invalid :every option' unless FREQUENCY.include?(options[:every].to_s)
31
-
32
- if options.key?(:interval)
33
- if options[:every].to_sym == :month && options[:interval].is_a?(Symbol) && !INTERVALS.key?(options[:interval])
34
- raise ArgumentError, 'interval symbol is not valid'
35
- elsif options[:interval].to_i == 0
36
- raise ArgumentError, 'interval should be greater than zero'
2
+ module Base
3
+ FREQUENCY = %w(day week month year)
4
+
5
+ attr_reader :event
6
+
7
+ def self.included(base) #:nodoc:
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ # Holds default_until_date configuration.
12
+ #
13
+ module ClassMethods #:nodoc:
14
+ def default_until_date
15
+ @default_until_date ||= Date.new(2037, 12, 31)
16
+ end
17
+
18
+ def default_until_date=(date)
19
+ @default_until_date = if date.is_a?(String)
20
+ Date.parse(date)
21
+ else
22
+ date
23
+ end
37
24
  end
38
25
  end
39
-
40
- @options = initialize_dates(options)
41
- @options[:interval] ||= 1
42
-
43
- case @options[:every].to_sym
44
- when :day then
45
- @event = Recurrence::Event.new(:day, @options)
46
- when :week then
47
- raise ArgumentError, 'invalid day' if !DAYS.include?(@options[:on].to_s) && !(0..6).include?(@options[:on])
48
- @options.merge!(:on => DAYS.index(@options[:on].to_s)) if DAYS.include?(@options[:on].to_s)
49
- @event = Recurrence::Event.new(:week, @options)
50
- when :month then
51
- raise ArgumentError, 'invalid day' unless (1..31).include?(@options[:on])
52
- options.merge!(:interval => INTERVALS[options[:interval]]) if options[:interval].is_a?(Symbol)
53
- @event = Recurrence::Event.new(:month, @options)
54
- when :year then
55
- raise ArgumentError, 'invalid month' if !(1..12).include?(@options[:on].first) && !MONTHS.keys.include?(@options[:on].first)
56
- raise ArgumentError, 'invalid day' unless (1..31).include?(@options[:on].last)
57
- @options.merge!(:on => [MONTHS[@options[:on].first.to_s], @options.last]) unless @options[:on].first.kind_of?(Numeric)
58
- @event = Recurrence::Event.new(:year, @options)
26
+
27
+ def initialize(options)
28
+ raise ArgumentError, ':every option is required' unless options.key?(:every)
29
+ raise ArgumentError, 'invalid :every option' unless FREQUENCY.include?(options[:every].to_s)
30
+
31
+ @options = initialize_dates(options)
32
+ @options[:interval] ||= 1
33
+
34
+ @event = case @options[:every].to_sym
35
+ when :day
36
+ Recurrence::Event::Daily.new(@options)
37
+ when :week
38
+ Recurrence::Event::Weekly.new(@options)
39
+ when :month
40
+ Recurrence::Event::Monthly.new(@options)
41
+ when :year
42
+ Recurrence::Event::Yearly.new(@options)
43
+ end
59
44
  end
60
- end
61
-
62
- def reset!
63
- @event.reset!
64
- @events = nil
65
- end
66
-
67
- def include?(required_date)
68
- required_date = Date.parse(required_date) if required_date.is_a?(String)
69
-
70
- if required_date < @options[:starts] || required_date > @options[:until]
71
- false
72
- else
73
- each do |date|
74
- return true if date == required_date
45
+
46
+ def reset!
47
+ @event.reset!
48
+ @events = nil
49
+ end
50
+
51
+ def include?(required_date)
52
+ required_date = Date.parse(required_date) if required_date.is_a?(String)
53
+
54
+ if required_date < @options[:starts] || required_date > @options[:until]
55
+ false
56
+ else
57
+ each do |date|
58
+ return true if date == required_date
59
+ end
75
60
  end
61
+
62
+ return false
76
63
  end
77
-
78
- return false
79
- end
80
-
81
- def next
82
- @event.next
83
- end
84
-
85
- def next!
86
- @event.next!
87
- end
88
-
89
- def events(options={})
90
- options[:starts] = Date.parse(options[:starts]) if options[:starts].is_a?(String)
91
- options[:until] = Date.parse(options[:until]) if options[:until].is_a?(String)
92
-
93
- reset! if options[:starts] || options[:until]
94
-
95
- @events ||= begin
96
- _events = []
97
-
98
- loop do
99
- date = @event.next!
100
-
101
- break if date.nil?
102
-
103
- if options[:starts] && options[:until] && date >= options[:starts] && date <= options[:until]
104
- _events << date
105
- elsif options[:starts] && options[:until].nil? && date >= options[:starts]
106
- _events << date
107
- elsif options[:until] && options[:starts].nil? && date <= options[:until]
108
- _events << date
109
- elsif options[:starts].nil? && options[:until].nil?
110
- _events << date
64
+
65
+ def next
66
+ @event.next
67
+ end
68
+
69
+ def next!
70
+ @event.next!
71
+ end
72
+
73
+ def events(options={})
74
+ options[:starts] = Date.parse(options[:starts]) if options[:starts].is_a?(String)
75
+ options[:until] = Date.parse(options[:until]) if options[:until].is_a?(String)
76
+
77
+ reset! if options[:starts] || options[:until]
78
+
79
+ @events ||= begin
80
+ _events = []
81
+
82
+ loop do
83
+ date = @event.next!
84
+
85
+ break if date.nil?
86
+
87
+ valid_start = options[:starts].nil? || date >= options[:starts]
88
+ valid_until = options[:until].nil? || date <= options[:until]
89
+ _events << date if valid_start && valid_until
90
+
91
+ break if options[:until] && options[:until] <= date
111
92
  end
93
+
94
+ _events
112
95
  end
113
-
114
- _events
115
96
  end
116
- end
117
-
118
- def events!(options={})
119
- reset!
120
- events(options)
121
- end
122
-
123
- def each!(&block)
124
- reset!
125
- each(&block)
126
- end
127
-
128
- def each(&block)
129
- events.each do |item|
130
- yield item
97
+
98
+ def events!(options={})
99
+ reset!
100
+ events(options)
131
101
  end
132
- end
133
-
134
- private
135
- def initialize_dates(options)
136
- [:starts, :until].each do |name|
137
- options[name] = Date.parse(options[name]) if options[name].is_a?(String)
102
+
103
+ def each!(&block)
104
+ reset!
105
+ each(&block)
106
+ end
107
+
108
+ def each(&block)
109
+ events.each do |item|
110
+ yield item
138
111
  end
139
-
140
- options[:starts] ||= Date.today
141
- options[:until] ||= Date.parse('2037-12-31')
142
-
143
- options
144
112
  end
145
- end
113
+
114
+ private
115
+
116
+ def initialize_dates(options) #:nodoc:
117
+ [:starts, :until].each do |name|
118
+ options[name] = Date.parse(options[name]) if options[name].is_a?(String)
119
+ end
120
+
121
+ options[:starts] ||= Date.today
122
+ options[:until] ||= self.class.default_until_date
123
+
124
+ options
125
+ end
126
+ end
127
+ end
@@ -1,130 +1,78 @@
1
1
  class Recurrence::Event
2
- attr_accessor :date, :start_date, :options, :every
3
-
4
- def initialize(every, options={})
5
- @every = every
6
- @options = options
7
- @current = nil
8
- @date = options[:starts]
2
+ CARDINALS = %w(first second third fourth fifth)
3
+ DAYS = %w(sunday monday tuesday wednesday thursday friday saturday)
4
+
5
+ attr_accessor :start_date
6
+
7
+ def initialize(options={})
8
+ every, options = nil, every if every.is_a?(Hash)
9
+
10
+ @options = options
11
+ @date = options[:starts]
12
+ @finished = false
13
+
14
+ validate
15
+ raise ArgumentError, 'interval should be greater than zero' if @options[:interval] <= 0
16
+
9
17
  prepare!
10
18
  end
11
-
12
- def prepare!
13
- self.next!
14
- @start_date = @date
15
- end
16
-
17
- def next?
18
- !!@next
19
- end
20
-
19
+
21
20
  def next!
22
- # just continue if date object is null or
23
- # hasn't been initialized yet
24
- return nil unless @date || !inited?
25
-
26
- # return the date if is the first interaction after
27
- # initializing object
28
- if inited? && !next?
29
- @next = true
30
- return @date
31
- end
32
-
33
- case @every
34
- when :day
35
- @date = next_day
36
- when :week
37
- @date = next_week
38
- when :month
39
- @date = next_month
40
- when :year
41
- @date = next_year
42
- end
43
-
44
- # if limit date has been reached just set the date
45
- # object to nil
46
- @date = nil unless @date.to_date <= @options[:until].to_date
47
-
21
+ return nil if finished?
22
+ return @date = @start_date if @start_date && @date.nil?
23
+
24
+ @date = next_in_recurrence
25
+
26
+ @finished, @date = true, nil if @date > @options[:until]
48
27
  @date
49
28
  end
50
-
29
+
51
30
  def next
52
- @date
31
+ return nil if finished?
32
+ @date || @start_date
53
33
  end
54
-
34
+
55
35
  def reset!
56
- @date = @start_date
36
+ @date = nil
57
37
  end
58
-
38
+
39
+ def finished?
40
+ @finished
41
+ end
42
+
59
43
  private
60
- def inited?
44
+
45
+ def initialized?
61
46
  !!@start_date
62
47
  end
63
-
64
- def next_day
65
- date = @date
66
- date = date + @options[:interval].days if inited? || @options[:interval] > 1
67
-
68
- date.to_date
69
- end
70
-
71
- def next_week
72
- date = @date
73
-
74
- if inited?
75
- date = date + @options[:interval].weeks
76
- elsif date.wday != @options[:on]
77
- date = date.next until @options[:on] == date.wday && date > @options[:starts]
78
- date = date + (@options[:interval] - 1).weeks
79
- end
80
-
81
- date.to_date
48
+
49
+ def prepare!
50
+ @start_date = next!
51
+ @date = nil
82
52
  end
83
-
84
- def next_month
85
- date = @date
86
-
87
- if inited?
88
- date = advance_to_month(date)
89
- else
90
- day = [options[:on], Time.days_in_month(date.month, date.year)].min
91
- date = Date.new(date.year, date.month, day)
92
- date = advance_to_month(date) if @date.day > day
93
- end
94
53
 
95
- date.to_date
54
+ def validate
55
+ # Inject custom validations
96
56
  end
97
-
98
- def advance_to_month(date)
99
- date = date.beginning_of_month + @options[:interval].months
100
- day = [options[:on], Time.days_in_month(date.month, date.year)].min
101
-
102
- if date.day != day && date.day < day
103
- date = date.to_date.next until date.day == day
104
- end
105
-
106
- date
57
+
58
+ # Common validation for inherited classes.
59
+ #
60
+ def valid_month_day?(day) #:nodoc:
61
+ raise ArgumentError, "invalid day #{day}" unless (1..31).include?(day)
107
62
  end
108
-
109
- def next_year
110
- date = @date
111
-
112
- if inited?
113
- date = advance_to_year(date)
63
+
64
+ # Check if the given key has a valid weekday (0 upto 6) or a valid weekday
65
+ # name (defined in the DAYS constant). If a weekday name (String) is given,
66
+ # convert it to a weekday (Integer).
67
+ #
68
+ def valid_weekday_or_weekday_name?(value)
69
+ if value.kind_of?(Numeric)
70
+ raise ArgumentError, "invalid day #{value}" unless (0..6).include?(value)
71
+ value
114
72
  else
115
- day = [options[:on].last, Time.days_in_month(options[:on].first, date.year)].min
116
- date = Date.new(date.year, options[:on].first, day)
117
- date = advance_to_year(date) if @date.month > date.month || @options[:on].last < @date.day
73
+ raise ArgumentError, "invalid weekday #{value}" unless DAYS.include?(value.to_s)
74
+ DAYS.index(value.to_s)
118
75
  end
119
-
120
- date.to_date
121
- end
122
-
123
- def advance_to_year(date)
124
- date = date.beginning_of_month + @options[:interval].years
125
- day = [Time.days_in_month(@options[:on].first, date.year), @options[:on].last].min
126
- year = date.year
127
-
128
- date = Date.new(year, @options[:on].first, day)
129
76
  end
77
+
130
78
  end
@@ -0,0 +1,10 @@
1
+ class Recurrence::Event::Daily < Recurrence::Event
2
+
3
+ protected
4
+ def next_in_recurrence
5
+ date = @date.to_date
6
+ date += @options[:interval] if initialized?
7
+ date
8
+ end
9
+
10
+ end
@@ -0,0 +1,102 @@
1
+ class Recurrence::Event::Monthly < Recurrence::Event
2
+ INTERVALS = {
3
+ :monthly => 1,
4
+ :bimonthly => 2,
5
+ :quarterly => 3,
6
+ :semesterly => 6
7
+ }
8
+
9
+ protected
10
+
11
+ def validate
12
+ if @options.key?(:weekday)
13
+
14
+ # Allow :on => :last, :weekday => :thursday contruction.
15
+ if @options[:on].to_s == 'last'
16
+ @options[:on] = 5
17
+ elsif @options[:on].kind_of?(Numeric)
18
+ valid_week?(@options[:on])
19
+ else
20
+ valid_cardinal?(@options[:on])
21
+ @options[:on] = CARDINALS.index(@options[:on].to_s) + 1
22
+ end
23
+
24
+ @options[:weekday] = valid_weekday_or_weekday_name?(@options[:weekday])
25
+ else
26
+ valid_month_day?(@options[:on])
27
+ end
28
+
29
+ if @options[:interval].is_a?(Symbol)
30
+ valid_interval?(@options[:interval])
31
+ @options[:interval] = INTERVALS[@options[:interval]]
32
+ end
33
+ end
34
+
35
+ def next_in_recurrence
36
+ return next_month if self.respond_to?(:next_month)
37
+ type = @options.key?(:weekday) ? :weekday : :monthday
38
+
39
+ class_eval <<-METHOD
40
+ def next_month
41
+ if initialized?
42
+ advance_to_month_by_#{type}(@date)
43
+ else
44
+ new_date = advance_to_month_by_#{type}(@date, 0)
45
+ new_date = advance_to_month_by_#{type}(new_date) if @date > new_date
46
+ new_date
47
+ end
48
+ end
49
+ METHOD
50
+
51
+ next_month
52
+ end
53
+
54
+ def advance_to_month_by_monthday(date, interval=@options[:interval])
55
+ # Have a raw month from 0 to 11 interval
56
+ raw_month = date.month + interval - 1
57
+
58
+ next_year = date.year + raw_month / 12
59
+ next_month = (raw_month % 12) + 1 # change back to ruby interval
60
+ next_day = [ @options[:on], Time.days_in_month(next_month, next_year) ].min
61
+
62
+ Date.new(next_year, next_month, next_day)
63
+ end
64
+
65
+ def advance_to_month_by_weekday(date, interval=@options[:interval])
66
+ raw_month = date.month + interval - 1
67
+ next_year = date.year + raw_month / 12
68
+ next_month = (raw_month % 12) + 1 # change back to ruby interval
69
+ date = Date.new(next_year, next_month, 1)
70
+
71
+ weekday, month = @options[:weekday], date.month
72
+
73
+ # Adjust week day
74
+ to_add = weekday - date.wday
75
+ to_add += 7 if to_add < 0
76
+ to_add += (@options[:on] - 1) * 7
77
+ date += to_add
78
+
79
+ # Go to the previous month if we lost it
80
+ if date.month != month
81
+ weeks = (date.day - 1) / 7 + 1
82
+ date -= weeks * 7
83
+ end
84
+
85
+ date
86
+ end
87
+
88
+ private
89
+
90
+ def valid_cardinal?(cardinal) #:nodoc:
91
+ raise ArgumentError, "invalid cardinal #{cardinal}" unless CARDINALS.include?(cardinal.to_s)
92
+ end
93
+
94
+ def valid_interval?(interval) #:nodoc:
95
+ raise ArgumentError, "invalid cardinal #{interval}" unless INTERVALS.key?(interval)
96
+ end
97
+
98
+ def valid_week?(week) #:nodoc:
99
+ raise ArgumentError, "invalid week #{week}" unless (1..5).include?(week)
100
+ end
101
+
102
+ end
@@ -0,0 +1,27 @@
1
+ class Recurrence::Event::Weekly < Recurrence::Event
2
+
3
+ protected
4
+
5
+ def validate
6
+ @options[:on] = Array.wrap(@options[:on]).inject([]) do |days, value|
7
+ days << valid_weekday_or_weekday_name?(value)
8
+ end
9
+
10
+ @options[:on].sort!
11
+ end
12
+
13
+ def next_in_recurrence
14
+ return @date if !initialized? && @options[:on].include?(@date.wday)
15
+
16
+ if next_day = @options[:on].find { |day| day > @date.wday }
17
+ to_add = next_day - @date.wday
18
+ else
19
+ to_add = (7 - @date.wday) # Move to next week
20
+ to_add += (@options[:interval] - 1) * 7 # Add extra intervals
21
+ to_add += @options[:on].first # Go to first required day
22
+ end
23
+
24
+ @date.to_date + to_add
25
+ end
26
+
27
+ end
@@ -0,0 +1,58 @@
1
+ class Recurrence::Event::Yearly < Recurrence::Event
2
+ MONTHS = {
3
+ "jan" => 1, "january" => 1,
4
+ "feb" => 2, "february" => 2,
5
+ "mar" => 3, "march" => 3,
6
+ "apr" => 4, "april" => 4,
7
+ "may" => 5,
8
+ "jun" => 6, "june" => 6,
9
+ "jul" => 7, "july" => 7,
10
+ "aug" => 8, "august" => 8,
11
+ "sep" => 9, "september" => 9,
12
+ "oct" => 10, "october" => 10,
13
+ "nov" => 11, "november" => 11,
14
+ "dec" => 12, "december" => 12
15
+ }
16
+
17
+ protected
18
+
19
+ def validate
20
+ valid_month_day?(@options[:on].last)
21
+
22
+ if @options[:on].first.kind_of?(Numeric)
23
+ valid_month?(@options[:on].first)
24
+ else
25
+ valid_month_name?(@options[:on].first)
26
+ @options[:on] = [ MONTHS[@options[:on].first.to_s], @options.last ]
27
+ end
28
+ end
29
+
30
+ def next_in_recurrence
31
+ if initialized?
32
+ advance_to_year(@date)
33
+ else
34
+ new_date = advance_to_year(@date, 0)
35
+ new_date = advance_to_year(new_date) if @date > new_date
36
+ new_date
37
+ end
38
+ end
39
+
40
+ def advance_to_year(date, interval=@options[:interval])
41
+ next_year = date.year + interval
42
+ next_month = @options[:on].first
43
+ next_day = [ @options[:on].last, Time.days_in_month(next_month, next_year) ].min
44
+
45
+ Date.new(next_year, next_month, next_day)
46
+ end
47
+
48
+ private
49
+
50
+ def valid_month?(month) #:nodoc:
51
+ raise ArgumentError, "invalid month #{month}" unless (1..12).include?(month)
52
+ end
53
+
54
+ def valid_month_name?(month) #:nodoc:
55
+ raise ArgumentError, "invalid month #{month}" unless MONTHS.keys.include?(month.to_s)
56
+ end
57
+
58
+ end
@@ -0,0 +1,23 @@
1
+ class Recurrence
2
+ module Shortcuts
3
+ def daily(options={})
4
+ options[:every] = :day
5
+ new(options)
6
+ end
7
+
8
+ def weekly(options)
9
+ options[:every] = :week
10
+ new(options)
11
+ end
12
+
13
+ def monthly(options)
14
+ options[:every] = :month
15
+ new(options)
16
+ end
17
+
18
+ def yearly(options)
19
+ options[:every] = :year
20
+ new(options)
21
+ end
22
+ end
23
+ end
data/recurrence.gemspec CHANGED
@@ -2,26 +2,31 @@
2
2
  # RUN : 'rake gem:update_gemspec'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.authors = ["Nando Vieira"]
5
+ s.add_dependency "activesupport", ">= 2.1.1"
6
6
  s.require_paths = ["lib"]
7
- s.required_rubygems_version = ">= 0"
8
- s.has_rdoc = false
9
7
  s.files = ["init.rb",
10
8
  "Rakefile",
11
9
  "recurrence.gemspec",
12
10
  "History.txt",
13
11
  "License.txt",
14
12
  "README.markdown",
13
+ "lib/recurrence.rb",
15
14
  "lib/recurrence",
16
15
  "lib/recurrence/base.rb",
17
16
  "lib/recurrence/event.rb",
18
- "lib/recurrence.rb"]
17
+ "lib/recurrence/shortcuts.rb",
18
+ "lib/recurrence/event",
19
+ "lib/recurrence/event/monthly.rb",
20
+ "lib/recurrence/event/daily.rb",
21
+ "lib/recurrence/event/weekly.rb",
22
+ "lib/recurrence/event/yearly.rb"]
23
+ s.authors = ["Nando Vieira"]
24
+ s.name = "recurrence"
25
+ s.required_rubygems_version = ">= 0"
26
+ s.has_rdoc = false
19
27
  s.email = ["fnando.vieira@gmail.com"]
20
- s.version = "0.0.7"
21
28
  s.homepage = "http://github.com/fnando/recurrence"
22
- s.name = "recurrence"
23
- s.summary = "A simple library to handle recurring events"
24
- s.add_dependency "rubigen", ">= 0"
25
- s.add_dependency "activesupport", ">= 2.1.1"
26
29
  s.bindir = "bin"
30
+ s.version = "0.1.0"
31
+ s.summary = "A simple library to handle recurring events"
27
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fnando-recurrence
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
@@ -9,20 +9,12 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-22 00:00:00 -08:00
12
+ date: 2009-05-16 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: rubigen
17
- version_requirement:
18
- version_requirements: !ruby/object:Gem::Requirement
19
- requirements:
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: "0"
23
- version:
24
15
  - !ruby/object:Gem::Dependency
25
16
  name: activesupport
17
+ type: :runtime
26
18
  version_requirement:
27
19
  version_requirements: !ruby/object:Gem::Requirement
28
20
  requirements:
@@ -46,10 +38,16 @@ files:
46
38
  - History.txt
47
39
  - License.txt
48
40
  - README.markdown
41
+ - lib/recurrence.rb
49
42
  - lib/recurrence
50
43
  - lib/recurrence/base.rb
51
44
  - lib/recurrence/event.rb
52
- - lib/recurrence.rb
45
+ - lib/recurrence/shortcuts.rb
46
+ - lib/recurrence/event
47
+ - lib/recurrence/event/monthly.rb
48
+ - lib/recurrence/event/daily.rb
49
+ - lib/recurrence/event/weekly.rb
50
+ - lib/recurrence/event/yearly.rb
53
51
  has_rdoc: false
54
52
  homepage: http://github.com/fnando/recurrence
55
53
  post_install_message: