recurrence 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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
+