smart_month 1.0.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.
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