recurrence 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,55 @@
1
+ == 0.0.1 2008-09-21
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
5
+
6
+ == 0.0.2 2008-09-21
7
+
8
+ * 1 minor enhancement:
9
+ * Added `items` and `items!` methods; returns an array with all events
10
+
11
+ == 0.0.3 2008-09-22
12
+
13
+ * 1 major enhancement:
14
+ * The recurrence now considers the starting date and its configurations
15
+ * Added lots of specs
16
+
17
+ == 0.0.4 2008-09-30
18
+
19
+ * 1 major enhancement:
20
+ * Renamed items method to events
21
+ * Added each! method
22
+ * Added lots of specs
23
+
24
+ == 0.0.5 2008-09-30
25
+
26
+ * 1 major enhancement:
27
+ * Monthly interval now accepts symbols: monthly, bimonthly, quarterly,
28
+ semesterly
29
+
30
+ == 0.0.6 2009-01-06
31
+
32
+ * 1 major enhancement:
33
+ * Code refactoring
34
+ * Added next and next! methods
35
+ * Added more specs
36
+ * Yearly interval now accepts symbols: jan-dec and january-december
37
+
38
+ == 0.0.7 2009-01-06
39
+
40
+ * 1 major enhancement:
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/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Nando Vieira
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,126 @@
1
+ Recurrence
2
+ ==========
3
+
4
+ * [http://github.com/fnando/recurrence](http://github.com/fnando/recurrence)
5
+
6
+ DESCRIPTION:
7
+ ------------
8
+
9
+ A simple library to handle recurring events.
10
+
11
+
12
+ INSTALLATION:
13
+ -------------
14
+
15
+ Recurrence can be installed as Rails plugin or gem. To install it as gem, just
16
+ run the command
17
+
18
+ sudo gem install fnando-recurrence --source=http://gems.github.com
19
+
20
+ Sometimes Github just won't build the gem. You can then do the following
21
+
22
+ git clone git://github.com/fnando/recurrence.git
23
+ cd recurrence
24
+ rake gem:install
25
+
26
+ If you prefer it as a plugin, just run the command
27
+
28
+ script/plugin install git://github.com/fnando/recurrence.git
29
+
30
+ USAGE:
31
+ ------
32
+
33
+ require 'rubygems'
34
+ require 'recurrence'
35
+
36
+ # Daily
37
+ r = Recurrence.new(:every => :day)
38
+ r = Recurrence.new(:every => :day, :interval => 9)
39
+
40
+ # Weekly
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])
45
+ r = Recurrence.new(:every => :week, :on => :friday, :interval => 2)
46
+
47
+ # Monthly by month day
48
+ r = Recurrence.new(:every => :month, :on => 15)
49
+ r = Recurrence.new(:every => :month, :on => 31)
50
+ r = Recurrence.new(:every => :month, :on => 7, :interval => 2)
51
+ r = Recurrence.new(:every => :month, :on => 7, :interval => :monthly)
52
+ r = Recurrence.new(:every => :month, :on => 7, :interval => :bimonthly)
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)
61
+
62
+ # Yearly
63
+ r = Recurrence.new(:every => :year, :on => [7, 4]) # => [month, day]
64
+ r = Recurrence.new(:every => :year, :on => [10, 31], :interval => 3)
65
+ r = Recurrence.new(:every => :year, :on => [:jan, 31])
66
+ r = Recurrence.new(:every => :year, :on => [:january, 31])
67
+
68
+ # Limit recurrence
69
+ # :starts defaults to Date.today
70
+ # :until defaults to 2037-12-31
71
+ r = Recurrence.new(:every => :day, :starts => Date.today)
72
+ r = Recurrence.new(:every => :day, :until => '2010-01-31')
73
+ r = Recurrence.new(:every => :day, :starts => Date.today, :until => '2010-01-31')
74
+
75
+ # Getting an array with all events
76
+ r.events.each {|date| puts date.to_s } # => Memoized array
77
+ r.events!.each {|date| puts date.to_s } # => reset items cache and re-execute it
78
+ r.events(:starts => '2009-01-01').each {|date| puts date.to_s }
79
+ r.events(:until => '2009-01-10').each {|date| puts date.to_s }
80
+ r.events(:starts => '2009-01-05', :until => '2009-01-10').each {|date| puts date.to_s }
81
+
82
+ # Iterating events
83
+ r.each { |date| puts date.to_s } # => Use items method
84
+ r.each! { |date| puts date.to_s } # => Use items! method
85
+
86
+ # Check if a date is included
87
+ r.include?(Date.today) # => true or false
88
+ r.include?('2008-09-21')
89
+
90
+ # Get next available date
91
+ r.next # => Keep the original date object
92
+ r.next! # => Change the internal date object to the next available date
93
+
94
+ MAINTAINER
95
+ ----------
96
+
97
+ * Nando Vieira (<http://simplesideias.com.br/>)
98
+
99
+ CONTRIBUTORS
100
+ ------------
101
+
102
+ * José Valim (<http://josevalim.blogspot.com/>)
103
+
104
+ LICENSE:
105
+ --------
106
+
107
+ (The MIT License)
108
+
109
+ Permission is hereby granted, free of charge, to any person obtaining
110
+ a copy of this software and associated documentation files (the
111
+ 'Software'), to deal in the Software without restriction, including
112
+ without limitation the rights to use, copy, modify, merge, publish,
113
+ distribute, sublicense, and/or sell copies of the Software, and to
114
+ permit persons to whom the Software is furnished to do so, subject to
115
+ the following conditions:
116
+
117
+ The above copyright notice and this permission notice shall be
118
+ included in all copies or substantial portions of the Software.
119
+
120
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
121
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
122
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
123
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
124
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
125
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
126
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rake'
2
+
3
+ PKG_FILES = %w(init.rb Rakefile recurrence.gemspec History.txt License.txt README.markdown) +
4
+ Dir["lib/**/*"]
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = "recurrence"
8
+ s.version = "0.1.0"
9
+ s.summary = "A simple library to handle recurring events"
10
+ s.authors = ["Nando Vieira"]
11
+ s.email = ["fnando.vieira@gmail.com"]
12
+ s.homepage = "http://github.com/fnando/recurrence"
13
+ s.description = ""
14
+ s.has_rdoc = false
15
+ s.files = PKG_FILES
16
+
17
+ s.add_dependency "activesupport", ">=2.1.1"
18
+ end
19
+
20
+ namespace :gem do
21
+ # Thanks to the Merb project for this code.
22
+ desc "Update Github Gemspec"
23
+ task :update_gemspec do
24
+ skip_fields = %w(new_platform original_platform specification_version loaded required_ruby_version rubygems_version platform)
25
+
26
+ result = "# WARNING : RAKE AUTO-GENERATED FILE. DO NOT MANUALLY EDIT!\n"
27
+ result << "# RUN : 'rake gem:update_gemspec'\n\n"
28
+ result << "Gem::Specification.new do |s|\n"
29
+
30
+ spec.instance_variables.each do |ivar|
31
+ value = spec.instance_variable_get(ivar)
32
+ name = ivar.split("@").last
33
+ next if name == "date"
34
+
35
+ next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
36
+ if name == "dependencies"
37
+ value.each do |d|
38
+ dep, *ver = d.to_s.split(" ")
39
+ result << " s.add_dependency #{dep.inspect}, #{ver.join(" ").inspect.gsub(/[()]/, "").gsub(", runtime", "")}\n"
40
+ end
41
+ else
42
+ case value
43
+ when Array
44
+ value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
45
+ when FalseClass
46
+ when TrueClass
47
+ when Fixnum
48
+ when String
49
+ value = value.inspect
50
+ else
51
+ value = value.to_s.inspect
52
+ end
53
+ result << " s.#{name} = #{value}\n"
54
+ end
55
+ end
56
+
57
+ result << "end"
58
+ File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
59
+ end
60
+
61
+ desc "Build gem"
62
+ task :build => [:update_gemspec] do
63
+ system "rm *.gem"
64
+ system "gem build #{spec.instance_variable_get('@name')}.gemspec"
65
+ end
66
+
67
+ desc "Install gem"
68
+ task :install => [:update_gemspec, :build] do
69
+ system "sudo gem install #{spec.instance_variable_get('@name')}"
70
+ end
71
+ end
72
+
73
+ desc "Execute specs"
74
+ task :spec do
75
+ system "spec spec/recurrence_spec.rb -c -f s"
76
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "recurrence"
data/lib/recurrence.rb ADDED
@@ -0,0 +1,17 @@
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
@@ -0,0 +1,127 @@
1
+ class Recurrence
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
24
+ end
25
+ end
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
44
+ end
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
60
+ end
61
+
62
+ return false
63
+ end
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
92
+ end
93
+
94
+ _events
95
+ end
96
+ end
97
+
98
+ def events!(options={})
99
+ reset!
100
+ events(options)
101
+ end
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
111
+ end
112
+ 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
@@ -0,0 +1,78 @@
1
+ class Recurrence::Event
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
+
17
+ prepare!
18
+ end
19
+
20
+ def next!
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]
27
+ @date
28
+ end
29
+
30
+ def next
31
+ return nil if finished?
32
+ @date || @start_date
33
+ end
34
+
35
+ def reset!
36
+ @date = nil
37
+ end
38
+
39
+ def finished?
40
+ @finished
41
+ end
42
+
43
+ private
44
+
45
+ def initialized?
46
+ !!@start_date
47
+ end
48
+
49
+ def prepare!
50
+ @start_date = next!
51
+ @date = nil
52
+ end
53
+
54
+ def validate
55
+ # Inject custom validations
56
+ end
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)
62
+ end
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
72
+ else
73
+ raise ArgumentError, "invalid weekday #{value}" unless DAYS.include?(value.to_s)
74
+ DAYS.index(value.to_s)
75
+ end
76
+ end
77
+
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
@@ -0,0 +1,32 @@
1
+ # WARNING : RAKE AUTO-GENERATED FILE. DO NOT MANUALLY EDIT!
2
+ # RUN : 'rake gem:update_gemspec'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.homepage = "http://github.com/fnando/recurrence"
6
+ s.bindir = "bin"
7
+ s.name = "recurrence"
8
+ s.summary = "A simple library to handle recurring events"
9
+ s.add_dependency "activesupport", ">= 2.1.1"
10
+ s.require_paths = ["lib"]
11
+ s.files = ["init.rb",
12
+ "Rakefile",
13
+ "recurrence.gemspec",
14
+ "History.txt",
15
+ "License.txt",
16
+ "README.markdown",
17
+ "lib/recurrence",
18
+ "lib/recurrence/base.rb",
19
+ "lib/recurrence/event",
20
+ "lib/recurrence/event/daily.rb",
21
+ "lib/recurrence/event/monthly.rb",
22
+ "lib/recurrence/event/weekly.rb",
23
+ "lib/recurrence/event/yearly.rb",
24
+ "lib/recurrence/event.rb",
25
+ "lib/recurrence/shortcuts.rb",
26
+ "lib/recurrence.rb"]
27
+ s.authors = ["Nando Vieira"]
28
+ s.required_rubygems_version = ">= 0"
29
+ s.version = "0.1.0"
30
+ s.has_rdoc = true
31
+ s.email = ["fnando.vieira@gmail.com"]
32
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recurrence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nando Vieira
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-18 00:00:00 -02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.1.1
24
+ version:
25
+ description:
26
+ email:
27
+ - fnando.vieira@gmail.com
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files: []
33
+
34
+ files:
35
+ - init.rb
36
+ - Rakefile
37
+ - recurrence.gemspec
38
+ - History.txt
39
+ - License.txt
40
+ - README.markdown
41
+ - lib/recurrence/base.rb
42
+ - lib/recurrence/event/daily.rb
43
+ - lib/recurrence/event/monthly.rb
44
+ - lib/recurrence/event/weekly.rb
45
+ - lib/recurrence/event/yearly.rb
46
+ - lib/recurrence/event.rb
47
+ - lib/recurrence/shortcuts.rb
48
+ - lib/recurrence.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/fnando/recurrence
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.3.5
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A simple library to handle recurring events
77
+ test_files: []
78
+