smart_month 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README +33 -0
  3. data/Rakefile +51 -0
  4. data/VERSION +1 -0
  5. data/lib/month.rb +26 -0
  6. data/lib/smart_month.rb +11 -0
  7. data/lib/smart_month/calculations.rb +87 -0
  8. data/lib/smart_month/collection.rb +51 -0
  9. data/lib/smart_month/extensions/date.rb +19 -0
  10. data/lib/smart_month/magic.rb +71 -0
  11. data/lib/smart_month/math.rb +41 -0
  12. data/lib/smart_month/rulesets.rb +151 -0
  13. data/lib/smart_month/util.rb +37 -0
  14. data/pkg/smart_month-1.0.0.gem +0 -0
  15. data/rdoc/classes/Date.html +209 -0
  16. data/rdoc/classes/Month.html +189 -0
  17. data/rdoc/classes/SmartMonth.html +136 -0
  18. data/rdoc/classes/SmartMonth/Calculations.html +383 -0
  19. data/rdoc/classes/SmartMonth/Collection.html +306 -0
  20. data/rdoc/classes/SmartMonth/Magic.html +214 -0
  21. data/rdoc/classes/SmartMonth/Magic/MonthFactory.html +178 -0
  22. data/rdoc/classes/SmartMonth/Math.html +311 -0
  23. data/rdoc/classes/SmartMonth/Rulesets.html +419 -0
  24. data/rdoc/classes/SmartMonth/Util.html +302 -0
  25. data/rdoc/classes/Time.html +152 -0
  26. data/rdoc/created.rid +1 -0
  27. data/rdoc/files/README.html +170 -0
  28. data/rdoc/files/lib/month_rb.html +113 -0
  29. data/rdoc/files/lib/smart_month/calculations_rb.html +101 -0
  30. data/rdoc/files/lib/smart_month/collection_rb.html +101 -0
  31. data/rdoc/files/lib/smart_month/extensions/date_rb.html +108 -0
  32. data/rdoc/files/lib/smart_month/magic_rb.html +101 -0
  33. data/rdoc/files/lib/smart_month/math_rb.html +101 -0
  34. data/rdoc/files/lib/smart_month/rulesets_rb.html +108 -0
  35. data/rdoc/files/lib/smart_month/util_rb.html +101 -0
  36. data/rdoc/files/lib/smart_month_rb.html +108 -0
  37. data/rdoc/fr_class_index.html +37 -0
  38. data/rdoc/fr_file_index.html +36 -0
  39. data/rdoc/fr_method_index.html +74 -0
  40. data/rdoc/index.html +24 -0
  41. data/rdoc/rdoc-style.css +208 -0
  42. data/smart_month.gemspec +179 -0
  43. data/test/spec/date/accessor_spec.rb +91 -0
  44. data/test/spec/date/add_month_spec.rb +24 -0
  45. data/test/spec/date/add_spec.rb +23 -0
  46. data/test/spec/date/boat_spec.rb +20 -0
  47. data/test/spec/date/civil_spec.rb +28 -0
  48. data/test/spec/date/commercial_spec.rb +29 -0
  49. data/test/spec/date/constants_spec.rb +41 -0
  50. data/test/spec/date/conversions_spec.rb +153 -0
  51. data/test/spec/date/downto_spec.rb +18 -0
  52. data/test/spec/date/eql_spec.rb +10 -0
  53. data/test/spec/date/gregorian_spec.rb +28 -0
  54. data/test/spec/date/hash_spec.rb +14 -0
  55. data/test/spec/date/infinity_spec.rb +77 -0
  56. data/test/spec/date/julian_spec.rb +52 -0
  57. data/test/spec/date/minus_month_spec.rb +23 -0
  58. data/test/spec/date/minus_spec.rb +30 -0
  59. data/test/spec/date/new_spec.rb +9 -0
  60. data/test/spec/date/neww_spec.rb +9 -0
  61. data/test/spec/date/ordinal_spec.rb +29 -0
  62. data/test/spec/date/parse_spec.rb +149 -0
  63. data/test/spec/date/relationship_spec.rb +20 -0
  64. data/test/spec/date/shared/civil.rb +66 -0
  65. data/test/spec/date/shared/commercial.rb +39 -0
  66. data/test/spec/date/shared/parse.rb +54 -0
  67. data/test/spec/date/shared/parse_eu.rb +30 -0
  68. data/test/spec/date/shared/parse_us.rb +29 -0
  69. data/test/spec/date/step_spec.rb +56 -0
  70. data/test/spec/date/strftime_spec.rb +205 -0
  71. data/test/spec/date/strptime_spec.rb +143 -0
  72. data/test/spec/date/upto_spec.rb +18 -0
  73. data/test/spec/parsedate/parsedate.rb +95 -0
  74. data/test/spec/time/httpdate_spec.rb +21 -0
  75. data/test/spec/time/iso8601_spec.rb +7 -0
  76. data/test/spec/time/rfc2822_spec.rb +7 -0
  77. data/test/spec/time/rfc822_spec.rb +7 -0
  78. data/test/spec/time/shared/rfc2822.rb +65 -0
  79. data/test/spec/time/shared/xmlschema.rb +53 -0
  80. data/test/spec/time/xmlschema_spec.rb +7 -0
  81. data/test/spec_helper.rb +56 -0
  82. data/test/test_helper.rb +4 -0
  83. data/test/unit/calculations_test.rb +82 -0
  84. data/test/unit/collection_test.rb +42 -0
  85. data/test/unit/magic_test.rb +37 -0
  86. data/test/unit/math_test.rb +30 -0
  87. data/test/unit/rulesets_test.rb +94 -0
  88. data/test/unit/samples/test_ruleset.yml +6 -0
  89. data/test/unit/util_test.rb +29 -0
  90. metadata +196 -0
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 (Derek Perez | www.derekperez.com)
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 ADDED
@@ -0,0 +1,33 @@
1
+ NOTE: Please read the wiki for usage examples and to see how you can help our project!
2
+
3
+ SmartMonth is a "bodysnatcher" plugin that takes boring Date month fixnums and replaces them with a rich toolkit of functionality. You can use SmartMonth to:
4
+
5
+ - Determine the first tuesday of any month in any year.
6
+ - Determine all of the fridays of any month in any year.
7
+ - Iterate through all the days of a month.
8
+ - Determine how many days of the month there are.
9
+ - Determine the first and last days of the month.
10
+ - And other fun date/month related things!
11
+
12
+ This is designed to be an extension of not only Time.now.month, but adds a new Month class to the ruby object model.
13
+
14
+ Keep in mind, this is 1.1, and may break things that rely on Time.now.month being a Fixnum. I've tried to fix this by making sure #to_i works as expected, but it may be an issue, handle with care.
15
+
16
+ Examples:
17
+
18
+ Time.now.month.every_tuesday #=> [array of tuesdays for the current month]
19
+ Month.april.first_wednesday #=> Date object corresponding to the first wednesday of april.
20
+ Month.may.every_monday_and_friday #=> {:monday=>[...],:friday=>[...]}
21
+ Month.june(2012).last_monday #=> Date object corresponding to the last monday in june 2012.
22
+
23
+ # iterator:
24
+ Month.june(2004).each do |day|
25
+ day.to_day #=> name of day (Saturday, Sunday, etc.)
26
+ day.to_i #=> value between 1 and the last day of the month corresponding.
27
+ end
28
+
29
+ Check the included documentation (rdoc) or the documentation site http://www.derekperez.com/projects/smartmonth
30
+ for more info on usage!
31
+
32
+ Copyright (c) 2009 (Derek Perez | www.derekperez.com), released under the MIT license
33
+
@@ -0,0 +1,51 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gemspec|
8
+ gemspec.name = "smart_month"
9
+ gemspec.summary = 'SmartMonth is a "bodysnatcher" plugin that takes boring Date month fixnums and replaces them with a rich toolkit of
10
+ functionality.'
11
+ gemspec.description = "You can use SmartMonth to: \n" +
12
+ "- Determine the first tuesday of any month in any year. \n" +
13
+ "- Determine all of the fridays of any month in any year. \n" +
14
+ "- Iterate through all the days of a month. \n" +
15
+ "- Determine how many days of the month there are. \n" +
16
+ "- Determine the first and last days of the month. \n" +
17
+ "- And other fun date/month related things!"
18
+ gemspec.email = "derek@derekperez.com"
19
+ gemspec.homepage = "http://github.com/perezd/smart_month"
20
+ gemspec.authors = ["Derek Perez", "Jonathan Silverman"]
21
+ end
22
+ Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler not available. Install it with: gem install jeweler"
25
+ end
26
+
27
+
28
+ desc 'Default: run unit tests.'
29
+ task :default => :test
30
+
31
+ desc 'Runs unit test suite.'
32
+ Rake::TestTask.new do |test|
33
+ test.libs << './test'
34
+ test.test_files = FileList['test/unit/*test.rb']
35
+ test.verbose = true
36
+ end
37
+
38
+ desc 'Runs Ruby Standard Library tests for date and time.'
39
+ task :test_ruby do
40
+ puts `mspec test/spec`
41
+ end
42
+
43
+ desc 'Generate documentation for the smart_month plugin.'
44
+ Rake::RDocTask.new(:rdoc) do |rdoc|
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = 'SmartMonth'
47
+ rdoc.options << '--line-numbers' << '--inline-source'
48
+ rdoc.rdoc_files.include('README')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
51
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) + '/smart_month'
2
+
3
+ require 'calculations'
4
+ require 'collection'
5
+ require 'math'
6
+ require 'magic'
7
+ require 'util'
8
+ require 'rulesets'
9
+
10
+ class Month
11
+ # gather the modules
12
+ include SmartMonth::Calculations
13
+ include SmartMonth::Collection
14
+ include SmartMonth::Math
15
+ include SmartMonth::Util
16
+ include SmartMonth::Magic
17
+ # abstraction that returns an array of months.
18
+ NAMES = Date::MONTHNAMES
19
+ # abstraction that returns an array of day names.
20
+ DAYS = Date::DAYNAMES
21
+ # constructor, takes 2 optional arguments, if you don't provide them, it will default to
22
+ # the current month and year.
23
+ def initialize(month = Time.now.mon, year = Time.now.year)
24
+ @date = Date.new(year,( month.is_a?(Integer) ? month : Month::NAMES.index(month.to_s.capitalize) ) ,1)
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ require 'date'
2
+ require File.expand_path(File.dirname(__FILE__)) + '/month'
3
+ require File.expand_path(File.dirname(__FILE__)) + '/smart_month/extensions/date'
4
+
5
+ # inject smart_month system into the Time class.
6
+ class ::Time
7
+ def month
8
+ return Month.new(self.mon,self.year)
9
+ end
10
+ end
11
+
@@ -0,0 +1,87 @@
1
+ module SmartMonth
2
+ # Responsible for all core month/week calculations.
3
+ module Calculations
4
+ # returns the first date of the day requested.
5
+ # if no day is defined, it returns the very first day of the month.
6
+ def first(day = nil)
7
+ (day.nil?) ? @date : nth_weekday(day,1)
8
+ end
9
+
10
+ # returns the second date of the day requested.
11
+ def second(day)
12
+ nth_weekday(day,2)
13
+ end
14
+
15
+ # returns the third date of the day requested.
16
+ def third(day)
17
+ nth_weekday(day,3)
18
+ end
19
+
20
+ # returns the fourth (or potentially last), date of the day requested.
21
+ def fourth(day)
22
+ nth_weekday(day,4) || self.last(day)
23
+ end
24
+
25
+ # returns last date of the day requested.
26
+ # if no day is defined, it returns the very last day of the month.
27
+ def last(day = nil)
28
+ (day.nil?) ? Date.new(@date.year,@date.month,-1) : every(day).last
29
+ end
30
+
31
+ # returns an array of dates of the day requested.
32
+ # if an array of dates are passed in, a hash of day names containing
33
+ # the corresponding date are returned.
34
+ def every!(*days)
35
+ days = [days].flatten
36
+ if days.size == 1
37
+ dates = []
38
+ (1..5).each do |week|
39
+ dates << nth_weekday(days.first,week)
40
+ end
41
+ dates.compact!
42
+ else
43
+ dates = {}
44
+ days.each do |day|
45
+ day = day.to_sym
46
+ dates[day] = []
47
+ (1..5).each do |week|
48
+ dates[day] << nth_weekday(day,week)
49
+ end
50
+ dates[day].compact!
51
+ end
52
+ end
53
+ dates
54
+ end
55
+
56
+ # this returns the same data as #every! but removes the hash
57
+ # organization of the data.
58
+ def every(*days)
59
+ days = every!(days)
60
+ (days.is_a?(Array)) ? days : days.values.flatten
61
+ end
62
+
63
+ # returns the total number of days in month.
64
+ def size
65
+ self.last.day.to_i
66
+ end
67
+ alias_method :length, :size
68
+
69
+ private
70
+
71
+ # this is the heart of the calculations module.
72
+ def nth_weekday(day,limit = 0)
73
+ raise RuntimeError, "#{day} is not a valid day of the week" unless Month::DAYS.collect{|d| d.downcase}.include?(day.to_s)
74
+ limit -= 1 # make more sense to humans
75
+ requested_weekday = Month::DAYS.collect{|d| d.downcase}.index(day.to_s)
76
+ iter_date = Date.new(@date.year,@date.month,1)
77
+ # find the first day that works
78
+ until iter_date.wday == requested_weekday
79
+ iter_date +=1
80
+ end
81
+ # then add a multipler based on the limit
82
+ iter_date = iter_date + (limit*7)
83
+ # if the date we've iterated to doesn't exist in this month, send out a nil.
84
+ return iter_date unless iter_date.month != @date.month
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,51 @@
1
+ module SmartMonth
2
+ # Responsible for all collection-based functionality.
3
+ module Collection
4
+
5
+ # allows for increment by copy
6
+ def next
7
+ if @date.month + 1 > 12
8
+ Month.new(1,@date.year+1)
9
+ else
10
+ Month.new(@date.month+1,@date.year)
11
+ end
12
+ end
13
+
14
+ # allows for in place incrementing by 1
15
+ def next!
16
+ if @date.month + 1 > 12
17
+ @date = Date.new(@date.year+1,@date.month,1)
18
+ else
19
+ @date = Date.new(@date.year,@date.month+1,1)
20
+ end
21
+ end
22
+
23
+ # allows for deincrement by copy
24
+ def previous
25
+ if @date.month - 1 == 0
26
+ Month.new(12,@date.year-1)
27
+ else
28
+ Month.new(@date.month-1,@date.year)
29
+ end
30
+ end
31
+ alias_method :prev, :previous
32
+
33
+ # allows for in place deincrementing by 1
34
+ def previous!
35
+ if @date.month - 1 == 0
36
+ @date = Date.new(@date.year-1,12,1)
37
+ else
38
+ @date = Date.new(@date.year,@date.month-1,1)
39
+ end
40
+ end
41
+ alias_method :prev!, :previous!
42
+
43
+ # allows us to iterate internally on the days of a given month.
44
+ def each(first = 1, last = self.last.day, &block)
45
+ (1..self.last.day).each do |e|
46
+ block.call(Date.new(@date.year,@date.month,e))
47
+ end
48
+ return self
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # mixin some new methods to the Date class.
2
+ class Date
3
+
4
+ # this is to make date objects more console-friendly
5
+ def inspect
6
+ strftime("%a, %d %b %Y")
7
+ end
8
+
9
+ # allows a date to retrun the current day its set to as an integer
10
+ def to_i
11
+ self.day.to_i
12
+ end
13
+
14
+ # allows a date to return the day of the week it currently is as a string
15
+ def to_day
16
+ Month::DAYS[self.wday]
17
+ end
18
+
19
+ end
@@ -0,0 +1,71 @@
1
+ module SmartMonth
2
+ # Magic is responsible for the rails-like dynamic "magic finders".
3
+ module Magic
4
+
5
+ # extends the Month class with the Month factory
6
+ def self.included(base)
7
+ base.extend MonthFactory
8
+ end
9
+
10
+ # This module allows your code to ignore the class instantiation, simply do Month.june!
11
+ module MonthFactory
12
+ # singleton month factory
13
+ def method_missing(meth,*args)
14
+ return new(meth.to_s.capitalize, (args.first || Time.now.year)) if Month::NAMES.include? meth.to_s.capitalize
15
+ end
16
+
17
+ # this allows you to essentially treat the month as a hash or array object to construct new months.
18
+ def [](month)
19
+ new(month)
20
+ end
21
+ end
22
+
23
+ # nice catchall to allow rails-esque APIs if you choose to.
24
+ def method_missing(meth)
25
+ raw = meth.to_s.split("_")
26
+
27
+ # execute lookup using week as positioning control
28
+ if week_position?(raw[0]) && raw[1] == "and"
29
+ results = []
30
+ # parse the request
31
+ arg = raw.slice!(raw.index(raw.last))
32
+ funcs = parse_arguments(raw)
33
+ # execute
34
+ funcs.each do |func|
35
+ results << self.send(func,arg) if week_position?(func)
36
+ end
37
+ return results
38
+
39
+ # execute an every pattern on n-days
40
+ elsif raw[0] == "every"
41
+ # parse the request
42
+ func = raw.slice!(0)
43
+ args = parse_arguments(raw)
44
+ # execute
45
+ return self.every(args)
46
+
47
+ # revert to basic singular lookup or raise an exception
48
+ elsif self.respond_to?(raw[0])
49
+ func = raw.slice!(0)
50
+ return self.send(func,raw) if week_position?(func)
51
+ else
52
+ raise NoMethodError
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ # a quicky helper for breaking up text to args.
59
+ def parse_arguments(args)
60
+ args.select { |a| a != 'and' }
61
+ end
62
+
63
+ # this is used a lot for validity and sanitizing.
64
+ def week_position?(compare)
65
+ [compare].flatten.each do |comp|
66
+ %w(first second third fourth last).include?(comp)
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ module SmartMonth
2
+ # Responsible for all math related functionality.
3
+ module Math
4
+
5
+ # adds a number to the month value and returns a Fixnum.
6
+ def +(int)
7
+ self.to_i + int
8
+ end
9
+
10
+ # subtracts a number to the month value and returns a Fixnum.
11
+ def -(int)
12
+ self.to_i - int
13
+ end
14
+
15
+ # multiplies a number to the month value and returns a Fixnum.
16
+ def *(int)
17
+ self.to_i * int
18
+ end
19
+
20
+ # divides a number to the month value and returns a Fixnum.
21
+ def /(int)
22
+ self.to_i / int
23
+ end
24
+
25
+ # exponents a number to the month value and returns a Fixnum.
26
+ def **(int)
27
+ self.to_i ** int
28
+ end
29
+
30
+ # modulos a number to the month value and returns a Fixnum.
31
+ def %(int)
32
+ self.to_i % int
33
+ end
34
+
35
+ # compares the current month to another month.
36
+ def ==(month)
37
+ self.inspect! == month.inspect!
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,151 @@
1
+ require 'yaml'
2
+
3
+ module SmartMonth
4
+ class Rulesets
5
+
6
+ ## LOADING RULES FROM YAML STREAM OR YAML FILE
7
+
8
+ # this allows importing of rules into the system as
9
+ # a raw yaml stream
10
+ def self.load_yaml(yaml)
11
+ @@rulesets = YAML::load(yaml)
12
+ self.new.send(:activate_rules!)
13
+ end
14
+
15
+ # this allows import of rules into the system as
16
+ # a yaml file on disk
17
+ def self.load_file(path)
18
+ File.open(path) { |yaml| self.load_yaml(yaml) }
19
+ end
20
+
21
+ ## MANUAL RULE MANAGEMENT
22
+
23
+ # manually add a rule to the system. The rule argument should be a string,
24
+ # an optional year argument can be passed if using a "natural frequency"
25
+ def self.add_rule(name, rule, year = nil)
26
+ @@rulesets = {} unless defined? @@rulesets
27
+ if rule.is_a?(String)
28
+ # define rule
29
+ rule_hash = { name => {'when' => rule } }
30
+ rule_hash[name]['year'] = year unless year.nil?
31
+ # merge and inject
32
+ @@rulesets.merge!(rule_hash)
33
+ self.new.send(:inject_rule,name,@@rulesets[name])
34
+ end
35
+ end
36
+
37
+ # updating and adding rules should do the same thing
38
+ class << self
39
+ alias_method :update_rule, :add_rule
40
+ end
41
+
42
+ # manually remove an existing rule from the system.
43
+ def self.remove_rule(name)
44
+ begin
45
+ name = name.gsub(' ','_').downcase
46
+ @@rulesets.delete_if { |k,v| k.downcase == name }
47
+ Date.class_eval { eval("undef is_#{name}?") }
48
+ Month.class_eval { eval("undef #{name}")}
49
+ rescue
50
+ return false
51
+ end
52
+ return true
53
+ end
54
+
55
+ protected
56
+
57
+ # iterates through all the rules currently defined in the class and injects them
58
+ # if they are not currently implemented.
59
+ def activate_rules!
60
+ @@rulesets.each_key do |rule|
61
+ inject_rule(rule, @@rulesets[rule])
62
+ end
63
+ end
64
+
65
+ # this method parses and defines the methods in our pre-existing ruby classes
66
+ def inject_rule(name,rule)
67
+ meth_name = name.gsub(' ','_').downcase
68
+ type, data = parse(rule)
69
+ # don't define the rulset methods if they might override existing methods!
70
+ define_methods(type,meth_name,data) unless Month.respond_to?(meth_name) || Date.respond_to?(meth_name)
71
+ end
72
+
73
+ ## PARSER STRATEGIES
74
+
75
+ # factory method for determing which parsing strategy to use based on the rule.
76
+ def parse(rule)
77
+ (Month::NAMES.include?(rule['when'].split[0].capitalize)) ? parse_string_as_date(rule) : parse_string_as_frequency(rule)
78
+ end
79
+
80
+
81
+ # this is the parsing process for basic "date as string" rules
82
+ # (eg: 'December 12th, 2005' or 'March 15th')
83
+ def parse_string_as_date(rule)
84
+ # first attempt to parse the string and load structure
85
+ month,day,year = rule['when'].split
86
+ year = Time.now.year.to_i if year.nil?
87
+ data = {:year => year.to_i, :month => Month.new(month).to_i, :day => day.to_i}
88
+ return [:date,data]
89
+ end
90
+
91
+ # this is the parsing process for basic "date as frequency" rules
92
+ # (eg: 'every tuesday and saturday' or 'every monday in march and april')
93
+ def parse_string_as_frequency(rule)
94
+ data,context = rule['when'].split('in')
95
+ data = data.rstrip.gsub(' ','_').downcase unless data.nil?
96
+ unless context.nil?
97
+ context = context.gsub(/(\,|and)/,'').split
98
+ context.collect {|ctx| ctx.downcase }
99
+ end
100
+ return [:freq, data, context, rule['year'].to_i]
101
+ end
102
+
103
+ ## METHOD DEFINITION
104
+
105
+ ## The folowing methods are used to alter the existing objects in memory
106
+ # through the use of meta-programming.
107
+
108
+ private
109
+
110
+ # do not define the alias unless the root is defined properly
111
+ def define_methods(type,meth_name,data)
112
+ define_alias(meth_name,data) if define_root(type,meth_name,data)
113
+ end
114
+
115
+ # define root definition for lookup directly into the Month class
116
+ def define_root(type,meth_name,data)
117
+ return false unless [:date,:freq].include?(type)
118
+ begin
119
+ # algorithm template
120
+ context = "([#{data[:month]}].flatten.include?(self.month))"
121
+ fail = 'nil'
122
+ # determine lookup based on requested strategy
123
+ case type
124
+ when :date: qry = "Date.new(#{data[:year]},#{data[:month]},#{data[:day]})"
125
+ when :freq: qry = 'true'
126
+ else qry = 'nil'
127
+ end
128
+ # evaluate the custom rule into the function
129
+ Month.send(:define_method,meth_name) {
130
+ eval "#{context} ? #{qry} : #{fail}"
131
+ }
132
+ rescue
133
+ return false
134
+ end
135
+ return true
136
+ end
137
+
138
+ # alias root definition as boolean directly into the Date class
139
+ def define_alias(meth_name,data)
140
+ begin
141
+ Date.send(:define_method, "is_#{meth_name}?") {
142
+ [Month[data[:month]].send(meth_name)].flatten.include?(self)
143
+ }
144
+ rescue
145
+ return false
146
+ end
147
+ return true
148
+ end
149
+
150
+ end
151
+ end