nickel 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,5 @@
1
- # Ruby Nickel Library
2
- # Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
3
- # MIT License [http://www.opensource.org/licenses/mit-license.php]
1
+ require_relative 'occurrence'
2
+ require_relative 'construct'
4
3
 
5
4
  module Nickel
6
5
 
@@ -30,7 +29,7 @@ module Nickel
30
29
  occurrences_from_wrappers_only
31
30
  end
32
31
  end
33
-
32
+
34
33
  private
35
34
  def initialize_index_to_type_map
36
35
  # The @index_to_type_map hash looks like this: {0 => :date, 1 => :timespan, ...}
@@ -38,32 +37,32 @@ module Nickel
38
37
  @index_to_type_map = {}
39
38
  @constructs.each_with_index do |c,i|
40
39
  @index_to_type_map[i] = case c.class.name
41
- when "Nickel::DateConstruct" then :date
42
- when "Nickel::TimeConstruct" then :time
43
- when "Nickel::DateSpanConstruct" then :datespan
44
- when "Nickel::TimeSpanConstruct" then :timespan
45
- when "Nickel::RecurrenceConstruct" then :recurrence
46
- when "Nickel::WrapperConstruct" then :wrapper
47
- end
40
+ when "Nickel::DateConstruct" then :date
41
+ when "Nickel::TimeConstruct" then :time
42
+ when "Nickel::DateSpanConstruct" then :datespan
43
+ when "Nickel::TimeSpanConstruct" then :timespan
44
+ when "Nickel::RecurrenceConstruct" then :recurrence
45
+ when "Nickel::WrapperConstruct" then :wrapper
46
+ end
48
47
  end
49
48
  end
50
-
49
+
51
50
  def initialize_user_input_style
52
51
  # Initializes @user_input_style.
53
- # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE".
54
- # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE".
55
- case (@index_to_type_map[0] == :wrapper ? @index_to_type_map[1] : @index_to_type_map[0])
56
- when :date then @user_input_style = :datetime
57
- when :time then @user_input_style = :timedate
58
- when :datespan then @user_input_style = :datetime
59
- when :timespan then @user_input_style = :timedate
60
- when :recurrence then @user_input_style = :datetime
61
- else
62
- # We only have wrappers. It doesn't matter which user style we choose.
63
- @user_input_style = :datetime
64
- end
52
+ # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE".
53
+ # Determine user input style, i.e. "DATE TIME DATE TIME" OR "TIME DATE TIME DATE".
54
+ case (@index_to_type_map[0] == :wrapper ? @index_to_type_map[1] : @index_to_type_map[0])
55
+ when :date then @user_input_style = :datetime
56
+ when :time then @user_input_style = :timedate
57
+ when :datespan then @user_input_style = :datetime
58
+ when :timespan then @user_input_style = :timedate
59
+ when :recurrence then @user_input_style = :datetime
60
+ else
61
+ # We only have wrappers. It doesn't matter which user style we choose.
62
+ @user_input_style = :datetime
63
+ end
65
64
  end
66
-
65
+
67
66
  def initialize_arrays_of_construct_indices
68
67
  @dci,@tci,@dsci,@tsci,@rci,@wci = [],[],[],[],[],[]
69
68
  @index_to_type_map.each do |i, type|
@@ -77,18 +76,18 @@ module Nickel
77
76
  end
78
77
  end
79
78
  end
80
-
79
+
81
80
  def initialize_sorted_time_map
82
81
  # Sorted time map has date/datespan/recurrence construct indices as keys, and
83
82
  # an array of time/timespan indices as values.
84
- @sorted_time_map = {}
85
-
83
+ @sorted_time_map = {}
84
+
86
85
  # Get all indices of date/datespan/recurrence constructs in the order they occurred.
87
- date_indices = (@dci + @dsci + @rci).sort
88
-
86
+ date_indices = (@dci + @dsci + @rci).sort
87
+
89
88
  # What is inhert_on about? If a user enters something like "wed and fri at 4pm" they
90
89
  # really want 4pm to be associated with wed and fri. For all dates that we don't find
91
- # associated times, we append the dates index to the inherit_on array. Then, once we
90
+ # associated times, we append the dates index to the inherit_on array. Then, once we
92
91
  # find a date associated with times, we copy the sorted_time_map at that index to the
93
92
  # other indices in the inherit_on array.
94
93
  #
@@ -97,43 +96,43 @@ module Nickel
97
96
  # inherit_from will hold the last date index with associated times, and subsequent dates that
98
97
  # do not have associated times will inherit from this index.
99
98
  @user_input_style == :datetime ? inherit_on = [] : inherit_from = nil
100
-
99
+
101
100
  # Iterate date_indices and populate @sorted_time_map
102
101
  date_indices.each do |i|
103
102
  # Do not change i.
104
- j = i
103
+ j = i
105
104
 
106
105
  # Now find all time and time span construct indices between this index and a boundary.
107
- # Boundaries are any other index in date_indices, -1 (passed the first construct),
106
+ # Boundaries are any other index in date_indices, -1 (passed the first construct),
108
107
  # and @constructs.size (passed the last construct).
109
108
  map_to_indices = []
110
109
  while (j = move_time_map_index(j)) && j != -1 && j != @constructs.size && !date_indices.include?(j) # boundaries
111
110
  (index_references_time(j) || index_references_timespan(j)) && map_to_indices << j
112
111
  end
113
-
112
+
114
113
  # NOTE: time/timespan indices are sorted by the order which they appeared, e.g.
115
114
  # their construct index number.
116
- @sorted_time_map[i] = map_to_indices.sort
117
- if @user_input_style == :datetime
115
+ @sorted_time_map[i] = map_to_indices.sort
116
+ if @user_input_style == :datetime
118
117
  inherit_on = handle_datetime_time_map_inheritance(inherit_on, i)
119
118
  else
120
119
  inherit_from = handle_timedate_time_map_inheritance(inherit_from, i)
121
120
  end
122
121
  end
123
122
  end
124
-
123
+
125
124
  def move_time_map_index(index)
126
- # The time map index must be moved based on user input style. For instance,
127
- # if a user enters dates then times, we must attach times to preceding dates,
125
+ # The time map index must be moved based on user input style. For instance,
126
+ # if a user enters dates then times, we must attach times to preceding dates,
128
127
  # meaning move forward.
129
128
  if @user_input_style == :datetime then index + 1
130
129
  elsif @user_input_style == :timedate then index - 1
131
130
  else raise "ConstructInterpreter#move_time_map_index says: @user_input_style is not valid"
132
131
  end
133
132
  end
134
-
133
+
135
134
  def handle_datetime_time_map_inheritance(inherit_on, date_index)
136
- if @sorted_time_map[date_index].empty?
135
+ if @sorted_time_map[date_index].empty?
137
136
  # There are no times for this date, mark to be inherited
138
137
  inherit_on << date_index
139
138
  else
@@ -154,7 +153,7 @@ module Nickel
154
153
  end
155
154
  inherit_from
156
155
  end
157
-
156
+
158
157
  def index_references_time(index)
159
158
  @index_to_type_map[index] == :time
160
159
  end
@@ -162,18 +161,18 @@ module Nickel
162
161
  def index_references_timespan(index)
163
162
  @index_to_type_map[index] == :timespan
164
163
  end
165
-
164
+
166
165
  # Returns either @time or @start_time, depending on whether tindex references a Time or TimeSpan construct
167
166
  def start_time_from_tindex(tindex)
168
167
  if index_references_time(tindex)
169
168
  return @constructs[tindex].time
170
169
  elsif index_references_timespan(tindex)
171
170
  return @constructs[tindex].start_time
172
- else
171
+ else
173
172
  raise "ConstructInterpreter#start_time_from_tindex says: tindex does not reference a time or time span"
174
173
  end
175
174
  end
176
-
175
+
177
176
  # If guess is false, either start time or end time (but not both) must already be firm.
178
177
  # The time that is not firm will be modified according to the firm time and set to firm.
179
178
  def finalize_timespan_constructs(guess = false)
@@ -190,26 +189,26 @@ module Nickel
190
189
  end
191
190
  end
192
191
  end
193
-
192
+
194
193
  # One of this methods functions will be to assign proper am/pm values to time
195
194
  # and timespan constructs if they were not specified.
196
195
  def finalize_constructs
197
-
198
- # First assign am/pm values to timespan constructs independent of
196
+
197
+ # First assign am/pm values to timespan constructs independent of
199
198
  # other times in timemap.
200
199
  finalize_timespan_constructs
201
-
202
- # Next we need to burn through the time map, find any start times
200
+
201
+ # Next we need to burn through the time map, find any start times
203
202
  # that are not firm, and set them based on previous firm times.
204
203
  # Note that @sorted_time_map has the format {date_index => [array, of, time, indices]}
205
204
  @sorted_time_map.each_value do |time_indices|
206
205
  # The time_indices array holds TimeConstruct and TimeSpanConstruct indices.
207
206
  # The time_array will hold an array of ZTime objects to modify (potentially)
208
- time_array = []
207
+ time_array = []
209
208
  time_indices.each {|tindex| time_array << start_time_from_tindex(tindex)}
210
209
  ZTime.am_pm_modifier(*time_array)
211
210
  end
212
-
211
+
213
212
  # Finally, we need to modify the timespans based on the the time info from am_pm_modifier.
214
213
  # We also need to guess at any timespans that didn't get any help from am_pm_modifier.
215
214
  # i.e. we originally guessed at timespans independently of other time info in time map;
@@ -217,13 +216,13 @@ module Nickel
217
216
  # end times in our time spans. If we didn't pick them up before.
218
217
  finalize_timespan_constructs(true)
219
218
  end
220
-
219
+
221
220
  # The @sorted_time_map hash has keys of date/datespans/recurrence indices (in this case date),
222
- # and an array of time and time span indices as values. This checks to make sure that array of
221
+ # and an array of time and time span indices as values. This checks to make sure that array of
223
222
  # times is not empty, and if it is there are no times associated with this date construct.
224
223
  # Huh? That does not explain this method... at all.
225
224
  def create_occurrence_for_each_time_in_time_map(occ_base, dindex, &block)
226
- if !@sorted_time_map[dindex].empty?
225
+ if !@sorted_time_map[dindex].empty?
227
226
  @sorted_time_map[dindex].each do |tindex| # tindex may be time index or time span index
228
227
  occ = occ_base.dup
229
228
  occ.start_time = start_time_from_tindex(tindex)
@@ -252,7 +251,7 @@ module Nickel
252
251
  create_occurrence_for_each_time_in_time_map(occ_base, dindex) {|occ| @occurrences << occ}
253
252
  end
254
253
  end
255
-
254
+
256
255
  def found_one_date_span
257
256
  @dci.size == 0 && @dsci.size == 1 && @rci.size == 0
258
257
  end
@@ -264,34 +263,34 @@ module Nickel
264
263
  :interval => 1)
265
264
  create_occurrence_for_each_time_in_time_map(occ_base, @dsci[0]) {|occ| @occurrences << occ}
266
265
  end
267
-
266
+
268
267
  def found_recurrences_and_optional_date_span
269
268
  @dsci.size <= 1 && @rci.size >= 1 # dates are optional
270
269
  end
271
270
 
272
271
  def occurrences_from_recurrences_and_optional_date_span
273
- if @dsci.size == 1
272
+ if @dsci.size == 1
274
273
  # If a date span exists, it functions as wrapper.
275
274
  occ_base_opts = {:start_date => @constructs[@dsci[0]].start_date, :end_date => @constructs[@dsci[0]].end_date}
276
- else
275
+ else
277
276
  # Perhaps there are type 0 or type 1 wrappers to provide start/end dates.
278
277
  occ_base_opts = occ_base_opts_from_wrappers
279
278
  end
280
-
279
+
281
280
  @rci.each do |rcindex|
282
281
  # Construct#interpret returns an array of hashes, each hash represents a single occurrence.
283
- @constructs[rcindex].interpret.each do |rec_occ_base_opts|
282
+ @constructs[rcindex].interpret.each do |rec_occ_base_opts|
284
283
  # RecurrenceConstruct#interpret returns base_opts for each occurrence,
285
284
  # but they must be merged with start/end dates, if supplied.
286
- occ_base = Occurrence.new(rec_occ_base_opts.merge(occ_base_opts))
285
+ occ_base = Occurrence.new(rec_occ_base_opts.merge(occ_base_opts))
287
286
  # Attach times:
288
287
  create_occurrence_for_each_time_in_time_map(occ_base, rcindex) {|occ| @occurrences << occ}
289
288
  end
290
289
  end
291
290
  end
292
-
291
+
293
292
  def found_wrappers_only
294
- # This should really be "found length wrappers only", because @dci.size must be zero,
293
+ # This should really be "found length wrappers only", because @dci.size must be zero,
295
294
  # and start/end wrappers require a date.
296
295
  @dsci.size == 0 && @rci.size == 0 && @wci.size > 0 && @dci.size == 0
297
296
  end
@@ -303,22 +302,22 @@ module Nickel
303
302
 
304
303
  def occ_base_opts_from_wrappers
305
304
  base_opts = {}
306
- # Must do type 0 and 1 wrappers first, imagine something like
305
+ # Must do type 0 and 1 wrappers first, imagine something like
307
306
  # "every friday starting next friday for 6 months".
308
307
  @wci.each do |wi|
309
308
  # Make sure the construct after the wrapper is a date.
310
- if @constructs[wi].wrapper_type == 0 && @dci.include?(wi + 1)
309
+ if @constructs[wi].wrapper_type == 0 && @dci.include?(wi + 1)
311
310
  base_opts[:start_date] = @constructs[wi + 1].date
312
311
  elsif @constructs[wi].wrapper_type == 1 && @dci.include?(wi + 1)
313
312
  base_opts[:end_date] = @constructs[wi + 1].date
314
313
  end
315
314
  end
316
-
315
+
317
316
  # Now pick up wrapper types 2,3,4
318
317
  @wci.each do |wi|
319
318
  if @constructs[wi].wrapper_type >= 2
320
319
  if base_opts[:start_date].nil? && base_opts[:end_date].nil? # span must start today
321
- base_opts[:start_date] = @curdate.dup
320
+ base_opts[:start_date] = @curdate.dup
322
321
  base_opts[:end_date] = case @constructs[wi].wrapper_type
323
322
  when 2 then @curdate.add_days(@constructs[wi].wrapper_length)
324
323
  when 3 then @curdate.add_weeks(@constructs[wi].wrapper_length)
data/lib/nickel/nlp.rb CHANGED
@@ -1,6 +1,9 @@
1
- # Ruby Nickel Library
2
- # Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
3
- # MIT License [http://www.opensource.org/licenses/mit-license.php]
1
+ require_relative 'zdate'
2
+ require_relative 'ztime'
3
+ require_relative 'nlp_query'
4
+ require_relative 'occurrence'
5
+ require_relative 'construct_finder'
6
+ require_relative 'construct_interpreter'
4
7
 
5
8
  module Nickel
6
9
 
@@ -8,56 +11,89 @@ module Nickel
8
11
 
9
12
  attr_reader :query, :input_date, :input_time, :nlp_query
10
13
  attr_reader :construct_finder, :construct_interpreter
11
- attr_reader :occurrences, :output
12
-
13
- # Never, EVER change the default behavior to false; <-- then why did I put it here?
14
- @use_date_correction = true
15
- class << self; attr_accessor :use_date_correction; end
14
+ attr_reader :occurrences, :message
16
15
 
17
- def initialize(query, date_time = Time.now)
16
+ def initialize(query, date_time = Time.now)
18
17
  raise InvalidDateTimeError unless [DateTime, Time].include?(date_time.class)
19
18
  str_time = date_time.strftime("%Y%m%dT%H%M%S")
20
19
  validate_input query, str_time
21
20
  @query = query.dup
22
21
  @input_date = ZDate.new str_time[0..7] # up to T, note format is already verified
23
22
  @input_time = ZTime.new str_time[9..14] # after T
24
- #setup_logger
25
23
  end
26
24
 
27
25
  def parse
28
26
  @nlp_query = NLPQuery.new(@query).standardize # standardizes the query
29
27
  @construct_finder = ConstructFinder.new(@nlp_query, @input_date, @input_time)
30
28
  @construct_finder.run
31
- @nlp_query.extract_message(@construct_finder.constructs)
29
+
30
+ extract_message
31
+ correct_case
32
+
32
33
  @construct_interpreter = ConstructInterpreter.new(@construct_finder.constructs, @input_date) # input_date only needed for wrappers
33
34
  @construct_interpreter.run
34
- @occurrences = Occurrence.finalizer(@construct_interpreter.occurrences, @input_date) # finds start and end dates
35
- @occurrences.sort! {|x,y| if x.start_date > y.start_date then 1 elsif x.start_date < y.start_date then -1 else 0 end} # sorts occurrences by start date
36
- @output = @occurrences # legacy
35
+
36
+ @occurrences = @construct_interpreter.occurrences.each {|occ| occ.finalize(@input_date) } # finds start and end dates
37
+ @occurrences.sort! {|x,y| if x.start_date > y.start_date then 1 elsif x.start_date < y.start_date then -1 else 0 end} # sorts occurrences by start dat
37
38
  @occurrences
38
39
  end
39
-
40
- def setup_logger
41
- @logger = Logger.new(STDOUT)
42
- def @logger.blue(a)
43
- self.warn "\e[44m #{a.inspect} \e[0m"
44
- end
45
- end
46
-
40
+
47
41
  def inspect
48
42
  "message: \"#{message}\", occurrences: #{occurrences.inspect}"
49
43
  end
50
44
 
51
- # Pass :inspect or :pretty_inspect as the inspect_method
52
- def debug_str(inspect_method = :inspect)
53
- "Current Date: #{self.input_date.readable}\nCurrent Time: #{self.input_time.readable}\n\nQuery: #{self.query}\nStandardized Query: #{self.nlp_query}\nQuery changed in: #{self.nlp_query.changed_in.inspect}\n\nConstructs Found: #{s = "\n"; self.construct_finder.constructs.each{|x| s << x.send(inspect_method) + "\n"}; s}\n\n@construct_interpreter: #{self.construct_interpreter.send(inspect_method)}"
45
+ private
46
+
47
+ def extract_message
48
+ # message could be all components put back together (which would be @nlp_query), so start with that
49
+ message_array = @nlp_query.split
50
+ constructs = @construct_finder.constructs
51
+
52
+ # now iterate through constructs, blow away any words between positions comp_start and comp_end
53
+ constructs.each do |c|
54
+ # create a range between comp_start and comp_end, iterate through it and wipe out words between them
55
+ (c.comp_start..c.comp_end).each {|x| message_array[x] = nil}
56
+ # also wipe out words before comp start if it is something like in, at, on, or the
57
+ if c.comp_start - 1 >= 0 && message_array[c.comp_start - 1] =~ /\b(from|in|at|on|the|are|is|for)\b/
58
+ message_array[c.comp_start - 1] = nil
59
+ if $1 == "the" && c.comp_start - 2 >= 0 && message_array[c.comp_start - 2] =~ /\b(for|on)\b/ # for the next three days; on the 27th;
60
+ message_array[c.comp_start - 2] = nil
61
+ if $1 == "on" && c.comp_start - 3 >= 0 && message_array[c.comp_start - 3] =~ /\b(is|are)\b/ # is on the 28th; are on the 21st and 22nd;
62
+ message_array[c.comp_start - 3] = nil
63
+ end
64
+ elsif $1 == "on" && c.comp_start - 2 >= 0 && message_array[c.comp_start - 2] =~ /\b(is|are)\b/ # is on tuesday; are on tuesday and wed;
65
+ message_array[c.comp_start - 2] = nil
66
+ end
67
+ end
68
+ end
69
+
70
+ # reloop and wipe out words after end of constructs, if they are followed by another construct
71
+ # note we already wiped out terms ahead of the constructs, so be sure to check for nil values, these indicate that a construct is followed by the nil
72
+ constructs.each_with_index do |c, i|
73
+ if message_array[c.comp_end+1] && message_array[c.comp_end + 1] == "and" # do something tomorrow and on friday
74
+ if message_array[c.comp_end + 2].nil? || (constructs[i+1] && constructs[i+1].comp_start == c.comp_end + 2)
75
+ message_array[c.comp_end + 1] = nil
76
+ elsif message_array[c.comp_end + 2] == "also" && message_array[c.comp_end + 3].nil? || (constructs[i+1] && constructs[i+1].comp_start == c.comp_end + 3) # do something tomorrow and also on friday
77
+ message_array[c.comp_end + 1] = nil
78
+ message_array[c.comp_end + 2] = nil
79
+ end
80
+ end
81
+ end
82
+ @message = message_array.compact.join(" ") # remove nils and join the words with spaces
54
83
  end
55
-
56
- def message
57
- @nlp_query.message
84
+
85
+ # returns any words in the query that appeared as input to their original case
86
+ def correct_case
87
+ orig = @query.split
88
+ latest = @message.split
89
+ orig.each_with_index do |original_word,j|
90
+ if i = latest.index(original_word.downcase)
91
+ latest[i] = original_word
92
+ end
93
+ end
94
+ @message = latest.join(" ")
58
95
  end
59
-
60
- private
96
+
61
97
  def validate_input query, date_time
62
98
  raise "Empty NLP query" unless query.length > 0
63
99
  raise "NLP says: date_time is not in the correct format" unless date_time =~ /^\d{8}T\d{6}$/
@@ -68,6 +104,6 @@ module Nickel
68
104
  def message
69
105
  "You must pass in a ruby DateTime or Time class object"
70
106
  end
71
- end
107
+ end
72
108
  end
73
109
 
@@ -1,25 +1,28 @@
1
- # Ruby Nickel Library
2
- # Copyright (c) 2008-2011 Lou Zell, lzell11@gmail.com, http://hazelmade.com
3
- # MIT License [http://www.opensource.org/licenses/mit-license.php]
1
+ require_relative 'zdate'
2
+ require_relative 'ztime'
3
+ require_relative 'nlp_query_constants'
4
4
 
5
5
  module Nickel
6
6
 
7
- class NLPQuery < String
7
+ class NLPQuery
8
8
  include NLPQueryConstants
9
-
10
- # Note there is no initialize here, it is inherited from string class.
11
- attr_reader :after_formatting, :changed_in, :message
12
-
9
+
10
+ def initialize(query_str)
11
+ @query_str = query_str.dup
12
+ end
13
+
14
+ attr_reader :after_formatting, :changed_in
15
+
13
16
  def standardize
14
- @query = self.dup # needed for case correcting after extract_message has been called
17
+ @query = query_str.dup # needed for case correcting after extract_message has been called
15
18
  query_formatting # easy text manipulation, no regex involved here
16
19
  query_pre_processing # puts query in the form that construct_finder understands, lots of manipulation here
17
- self
20
+ query_str.dup
18
21
  end
19
22
 
20
23
  def query_formatting
21
- gsub!(/\n/,'')
22
- downcase!
24
+ query_str.gsub!(/\n/,'')
25
+ query_str.downcase!
23
26
  remove_unused_punctuation
24
27
  replace_backslashes
25
28
  run_spell_check
@@ -31,34 +34,34 @@ module Nickel
31
34
  replace_hyphens
32
35
  insert_repeats_before_words_indicating_recurrence_lame
33
36
  insert_space_at_end_of_string_lame
34
- @after_formatting = self.dup # save current state
37
+ @after_formatting = query_str.dup # save current state
35
38
  end
36
-
37
- # Usage:
39
+
40
+ # Usage:
38
41
  # self.nsub!(/foo/, 'bar')
39
42
  #
40
43
  # nsub! is like gsub! except it logs the calling method in @changed_in.
41
- # There is another difference: When using blocks, matched strings are
44
+ # There is another difference: When using blocks, matched strings are
42
45
  # available as block params, e.g.: # nsub!(/(match1)(match2)/) {|m1,m2|}
43
46
  #
44
47
  # I wrote this because I was having problems overriding gsub and passing
45
48
  # a block from the new gsub to super.
46
49
  def nsub!(*args)
47
- if m = self.match(args[0]) # m will now hold the FIRST set of backreferenced matches
50
+ if m = query_str.match(args[0]) # m will now hold the FIRST set of backreferenced matches
48
51
  # there is at least one match
49
52
  @changed_in ||= []
50
- @changed_in << calling_method
53
+ @changed_in << caller[1][/(\w+)\W*$/, 1]
51
54
  if block_given?
52
- # gsub!(args[0]) {yield(*m.to_a[1..-1])} # There is a bug here: If gsub matches more than once,
55
+ # query_str.gsub!(args[0]) {yield(*m.to_a[1..-1])} # There is a bug here: If gsub matches more than once,
53
56
  # then the first set of referenced matches will be passed to the block
54
57
  ret_str = m.pre_match + m[0].sub(args[0]) {yield(*m.to_a[1..-1])} # this will take care of the first set of matches
55
58
  while (m_old = m.dup) && (m = m.post_match.match(args[0]))
56
59
  ret_str << m.pre_match + m[0].sub(args[0]) {yield(*m.to_a[1..-1])}
57
60
  end
58
61
  ret_str << m_old.post_match
59
- self.sub!(/.*/,ret_str)
62
+ query_str.sub!(/.*/,ret_str)
60
63
  else
61
- gsub!(args[0],args[1])
64
+ query_str.gsub!(args[0],args[1])
62
65
  end
63
66
  end
64
67
  end
@@ -112,7 +115,7 @@ module Nickel
112
115
  nsub!(/\s*at\s+night/,'pm')
113
116
  nsub!(/(after\s*)?noon(ish)?/,'12:00pm')
114
117
  nsub!(/\bmi(dn|nd)ight\b/,'12:00am')
115
- nsub!(/final/,'last')
118
+ nsub!(/final/,'last')
116
119
  nsub!(/recur(s|r?ing)?/,'repeats')
117
120
  nsub!(/\beach\b/,'every')
118
121
  nsub!(/running\s+(until|through)/,'through')
@@ -186,7 +189,7 @@ module Nickel
186
189
  nsub!(/\bninety\s*-?\s*fifth\b/,'95th')
187
190
  nsub!(/\bninety\s*-?\s*four\b/,'94')
188
191
  nsub!(/\bninety\s*-?\s*fourth\b/,'94th')
189
- nsub!(/\bninety\s*-?\s*three\b/,'93')
192
+ nsub!(/\bninety\s*-?\s*three\b/,'93')
190
193
  nsub!(/\bninety\s*-?\s*third\b/,'93rd')
191
194
  nsub!(/\bninety\s*-?\s*two\b/,'92')
192
195
  nsub!(/\bninety\s*-?\s*second\b/,'92nd')
@@ -198,7 +201,7 @@ module Nickel
198
201
  nsub!(/\beighty\s*-?\s*ninth\b/,'89th')
199
202
  nsub!(/\beighty\s*-?\s*eight\b/,'88')
200
203
  nsub!(/\beighty\s*-?\s*eighth\b/,'88th')
201
- nsub!(/\beighty\s*-?\s*seven\b/,'87')
204
+ nsub!(/\beighty\s*-?\s*seven\b/,'87')
202
205
  nsub!(/\beighty\s*-?\s*seventh\b/,'87th')
203
206
  nsub!(/\beighty\s*-?\s*six\b/,'86')
204
207
  nsub!(/\beighty\s*-?\s*sixth\b/,'86th')
@@ -373,7 +376,7 @@ module Nickel
373
376
  nsub!(/\bone\b/,'1')
374
377
  nsub!(/\bfirst\b/,'1st')
375
378
  nsub!(/\bzero\b/,'0')
376
- nsub!(/\bzeroth\b/,'0th')
379
+ nsub!(/\bzeroth\b/,'0th')
377
380
  end
378
381
 
379
382
  def standardize_am_pm
@@ -388,243 +391,27 @@ module Nickel
388
391
  end
389
392
 
390
393
  def insert_repeats_before_words_indicating_recurrence_lame
391
- comps = self.split
394
+ comps = query_str.split
392
395
  (daily_index = comps.index("daily")) && comps[daily_index - 1] != "repeats" && comps[daily_index] = "repeats daily"
393
396
  (weekly_index = comps.index("weekly")) && comps[weekly_index - 1] != "repeats" && comps[weekly_index] = "repeats weekly"
394
397
  (monthly_index = comps.index("monthly")) && comps[monthly_index - 1] != "repeats" && comps[monthly_index] = "repeats monthly"
395
- if (rejoin = comps.join(' ')) != self
398
+ if (rejoin = comps.join(' ')) != query_str
396
399
  nsub!(/.+/,rejoin)
397
400
  end
398
401
  end
399
402
 
400
403
  def insert_space_at_end_of_string_lame
401
404
  # nsub!(/(.+)/,'\1 ') # I don't really want to be notified about this
402
- gsub!(/(.+)/,'\1 ')
405
+ query_str.gsub!(/(.+)/,'\1 ')
403
406
  end
404
407
 
405
- # These are possible because: NLPQuery.new("hi there").split[0].class ==> Nickel::NLPQuery!!
406
- # valid hour, 24hour, and minute could use some cleaning
407
- def valid_dd?
408
- self =~ %r{^(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?$}
409
- end
410
- def valid_hour?
411
- validity = false
412
- if (self.length == 1) && (self =~ /^(1|2|3|4|5|6|7|8|9)/)
413
- validity = true
414
- end
415
- if self.length == 2
416
- if self =~ /^0/
417
- if self =~ /(1|2|3|4|5|6|7|8|9)$/
418
- validity = true
419
- end
420
- end
421
- if self =~ /^1/
422
- if self =~ /(0|1|2)$/
423
- validity = true
424
- end
425
- end
426
- end
427
- return validity
428
- end # END valid_hour?
429
- def valid_24_hour?
430
- validity = false
431
- if (self.length == 1) && (self =~ /^(0|1|2|3|4|5|6|7|8|9)/)
432
- validity = true
433
- end
434
- if self.length == 2
435
- if self =~ /^(0|1)/
436
- if self =~ /(0|1|2|3|4|5|6|7|8|9)$/
437
- validity = true
438
- end
439
- end
440
- if self =~ /^2/
441
- if self =~ /(0|1|2|3)$/
442
- validity = true
443
- end
444
- end
445
- end
446
- return validity
447
- end # END valid_hour?
448
- def valid_minute?
449
- validity = false
450
- if self.length <= 2
451
- if self =~ /^(0|1|2|3|4|5)/
452
- if self =~ /(0|1|2|3|4|5|6|7|8|9)$/
453
- validity = true
454
- end
455
- end
456
- end
457
- return validity
458
- end # END valid_minute?
459
- def digits_only?
460
- self =~ /^\d+$/ #no characters other than digits
461
- end
462
-
463
- # Interpret Time is an important one, set some goals:
464
- # match all of the following
465
- # a.) 5, 12, 530, 1230, 2000
466
- # b.) 5pm, 12pm, 530am, 1230am,
467
- # c.) 5:30, 12:30, 20:00
468
- # d.) 5:3, 12:3, 20:3 ... that's not needed but we supported it in version 1, this would be 5:30 and 12:30
469
- # e.) 5:30am, 12:30am
470
- # 20:00am, 20:00pm ... ZTime will flag these as invalid, so it is ok if we match them here
471
- def interpret_time
472
- a_b = /^(\d{1,4})(am|pm)?$/ # handles cases (a) and (b)
473
- c_d_e = /^(\d{1,2}):(\d{1,2})(am|pm)?$/ # handles cases (c), (d), and (e)
474
- if mdata = match(a_b)
475
- am_pm = mdata[2]
476
- case mdata[1].length # this may look a bit confusing, but all we are doing is interpreting
477
- when 1 then hstr = "0" + mdata[1] # what the user meant based on the number of digits they provided
478
- when 2 then hstr = mdata[1] # e.g. "11" means 11:00
479
- when 3 then hstr = "0" + mdata[1][0..0]; mstr = mdata[1][1..2] # e.g. "530" means 5:30
480
- when 4 then hstr = mdata[1][0..1]; mstr = mdata[1][2..3] # e.g. "1215" means 12:15
481
- end
482
- elsif mdata = match(c_d_e)
483
- am_pm = mdata[3]
484
- hstr = mdata[1]
485
- mstr = mdata[2]
486
- hstr.length == 1 && hstr.insert(0,"0")
487
- mstr.length == 1 && mstr << "0"
488
- else
489
- return nil
490
- end
491
- # in this case we do not care if time fails validation, if it does, it just means we haven't found a valid time, return nil
492
- begin ZTime.new("#{hstr}#{mstr}", am_pm) rescue return nil end
493
- end
494
-
495
- # Interpret Date is equally as important, our goals:
496
- # First off, convention of the NLP is to not allow month names to the construct finder (unless it is implying date span), so we will not be interpreting
497
- # anything such as january 2nd, 2008. Instead all dates will be represented in this form month/day/year. However it may not
498
- # be as nice as that. We need to match things like '5', if someone just typed in "the 5th." Because of this, there will be
499
- # overlap between interpret_date and interpret_time in matching; interpret_date should ALWAYS be found after interpret_time in
500
- # the construct finder. If the construct finder happens upon a digit on it's own, e.g. "5", it will not run interpret_time
501
- # because there is no "at" preceeding it. Therefore it will fall through to the finder with interpret_date and we will assume
502
- # the user meant the 5th. If interpret_date is before interpret_time, then .... wait... does the order actually matter? Even if
503
- # this is before interpret_time, it shouldn't get hit because the time should be picked up at the "at" construct. This may be a bunch
504
- # of useless rambling.
505
- #
506
- # 2/08 <------ This is not A date
507
- # 2/2008 <------ Neither is this, but I can see people using these as wrappers, must support this in next version
508
- # 11/08 <------ same
509
- # 11/2008 <------ same
510
- # 2/1/08, 2/12/08, 2/1/2008, 2/12/2008
511
- # 11/1/08, 11/12/08, 11/1/2008, 11/12/2008
512
- # 2/1 feb first
513
- # 2/12 feb twelfth
514
- # 11/1 nov first
515
- # 11/12 nov twelfth
516
- # 11 the 11th
517
- # 2 the 2nd
518
- #
519
- #
520
- # Match all of the following:
521
- # a.) 1 10
522
- # b.) 1/1 1/12 10/1 10/12
523
- # c.) 1/1/08 1/12/08 1/1/2008 1/12/2008 10/1/08 10/12/08 10/12/2008 10/12/2008
524
- # d.) 1st 10th
525
- def interpret_date(current_date)
526
- day_str, month_str, year_str = nil, nil, nil
527
- ambiguous = {:month => false, :year => false} # assume false, we use this flag if we aren't certain about the year
528
-
529
- #appropriate matches
530
- a_d = /^(\d{1,2})(rd|st|nd|th)?$/ # handles cases a and d
531
- b = /^(\d{1,2})\/(\d{1,2})$/ # handles case b
532
- c = /^(\d{1,2})\/(\d{1,2})\/(\d{2}|\d{4})$/ # handles case c
533
-
534
- if mdata = match(a_d)
535
- ambiguous[:month] = true
536
- day_str = mdata[1].to_s2
537
- elsif mdata = match(b)
538
- ambiguous[:year] = true
539
- month_str = mdata[1].to_s2
540
- day_str = mdata[2].to_s2
541
- elsif mdata = match(c)
542
- month_str = mdata[1].to_s2
543
- day_str = mdata[2].to_s2
544
- year_str = mdata[3].sub(/^(\d\d)$/,'20\1') # if there were only two digits, prepend 20 (e.g. "08" should be "2008")
545
- else
546
- return nil
547
- end
548
-
549
- inst_str = (year_str || current_date.year_str) + (month_str || current_date.month_str) + (day_str || current_date.day_str)
550
- # in this case we do not care if date fails validation, if it does, it just means we haven't found a valid date, return nil
551
- date = ZDate.new(inst_str) rescue nil
552
- if date && NLP::use_date_correction
553
- if ambiguous[:year]
554
- # say the date is 11/1 and someone enters 2/1, they probably mean next year, I pick 4 months as a threshold but that is totally arbitrary
555
- current_date.diff_in_months(date) < -4 and date = date.add_years(1)
556
- elsif ambiguous[:month]
557
- current_date.day > date.day and date = date.add_months(1)
558
- end
559
- end
560
- date
561
- end
562
-
563
-
564
- def extract_message(constructs)
565
- @logger = Logger.new(STDOUT)
566
- def @logger.blue(a)
567
- #self.warn "\e[44m #{a.inspect} \e[0m"
568
- end
569
-
570
- @logger.blue self
571
- # message could be all components put back together (which would be @nlp_query), so start with that
572
- message_array = self.split
573
-
574
- # now iterate through constructs, blow away any words between positions comp_start and comp_end
575
- constructs.each do |c|
576
- # create a range between comp_start and comp_end, iterate through it and wipe out words between them
577
- (c.comp_start..c.comp_end).each {|x| message_array[x] = nil}
578
- # also wipe out words before comp start if it is something like in, at, on, or the
579
- if c.comp_start - 1 >= 0 && message_array[c.comp_start - 1] =~ /\b(from|in|at|on|the|are|is|for)\b/
580
- message_array[c.comp_start - 1] = nil
581
- if $1 == "the" && c.comp_start - 2 >= 0 && message_array[c.comp_start - 2] =~ /\b(for|on)\b/ # for the next three days; on the 27th;
582
- message_array[c.comp_start - 2] = nil
583
- if $1 == "on" && c.comp_start - 3 >= 0 && message_array[c.comp_start - 3] =~ /\b(is|are)\b/ # is on the 28th; are on the 21st and 22nd;
584
- message_array[c.comp_start - 3] = nil
585
- end
586
- elsif $1 == "on" && c.comp_start - 2 >= 0 && message_array[c.comp_start - 2] =~ /\b(is|are)\b/ # is on tuesday; are on tuesday and wed;
587
- message_array[c.comp_start - 2] = nil
588
- end
589
- end
590
- @logger.blue(message_array)
591
- @logger.blue(c.comp_start)
592
- @logger.blue(c.comp_end)
593
- end
594
-
595
- # reloop and wipe out words after end of constructs, if they are followed by another construct
596
- # note we already wiped out terms ahead of the constructs, so be sure to check for nil values, these indicate that a construct is followed by the nil
597
- constructs.each_with_index do |c, i|
598
- if message_array[c.comp_end+1] && message_array[c.comp_end + 1] == "and" # do something tomorrow and on friday
599
- if message_array[c.comp_end + 2].nil? || (constructs[i+1] && constructs[i+1].comp_start == c.comp_end + 2)
600
- message_array[c.comp_end + 1] = nil
601
- elsif message_array[c.comp_end + 2] == "also" && message_array[c.comp_end + 3].nil? || (constructs[i+1] && constructs[i+1].comp_start == c.comp_end + 3) # do something tomorrow and also on friday
602
- message_array[c.comp_end + 1] = nil
603
- message_array[c.comp_end + 2] = nil
604
- end
605
- end
606
- end
607
- @logger.blue("final:")
608
- @logger.blue(message_array)
609
- @message = message_array.compact.join(" ") # remove nils and join the words with spaces
610
- # we have the message, now run the case corrector to return cases to the users original input
611
- case_corrector
612
- end
613
-
614
- # returns any words in the query that appeared as input to their original case
615
- def case_corrector
616
- orig = @query.split
617
- latest = @message.split
618
- orig.each_with_index do |original_word,j|
619
- if i = latest.index(original_word.downcase)
620
- latest[i] = original_word
621
- end
622
- end
623
- @message = latest.join(" ")
408
+ def to_s
409
+ query_str
624
410
  end
625
-
626
411
 
627
412
  private
413
+ attr_accessor :query_str
414
+
628
415
  def standardize_input
629
416
  nsub!(/last\s+#{DAY_OF_WEEK}/,'5th \1') # last dayname => 5th dayname
630
417
  nsub!(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day
@@ -635,14 +422,14 @@ module Nickel
635
422
  nsub!(/before\s+12pm/,'6am to 12pm') # arbitrary
636
423
 
637
424
  # Handle 'THE' Cases
638
- # Attempt to pick out where a user entered 'the' when they really mean 'every'.
639
- # For example,
425
+ # Attempt to pick out where a user entered 'the' when they really mean 'every'.
426
+ # For example,
640
427
  # The first of every month and the 22nd of THE month => repeats monthly first xxxxxx repeats monthly 22nd xxxxxxx
641
428
  nsub!(/(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:of\s+)?(?:every|each)\s+month((?:.*)of\s+the\s+month(?:.*))/) do |m1,m2|
642
429
  ret_str = " repeats monthly " + m1
643
430
  ret_str << m2.gsub(/(?:and\s+)?(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+of\s+the\s+month/, ' repeats monthly \1 ')
644
431
  end
645
-
432
+
646
433
  # Every first sunday of the month and the last tuesday => repeats monthly first sunday xxxxxxxxx repeats monthly last tuesday xxxxxxx
647
434
  nsub!(/every\s+#{WEEK_OF_MONTH}\s+#{DAY_OF_WEEK}\s+of\s+(?:the\s+)?month((?:.*)and\s+(?:the\s+)?#{WEEK_OF_MONTH}\s+#{DAY_OF_WEEK}(?:.*))/) do |m1,m2,m3|
648
435
  ret_str = " repeats monthly " + m1 + " " + m2 + " "
@@ -653,23 +440,42 @@ module Nickel
653
440
  nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD}\s(?:of\s+)#{MONTH_OF_YEAR}\s+(?:of\s+)?#{YEAR}/) do |m1,m2,m3,m4|
654
441
  (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 + '/' + m4
655
442
  end
656
-
443
+
657
444
  # The x through the y of oct => 10/x through 10/y
658
445
  nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:through|to|until)\s+(?:the\s+)#{DATE_DD}\s(?:of\s+)?#{MONTH_OF_YEAR}/) do |m1,m2,m3|
659
446
  (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2
660
447
  end
661
448
 
449
+ # January 1 - February 15
450
+ nsub!(/#{MONTH_OF_YEAR}\s+#{DATE_DD_NB_ON_SUFFIX}\s+(?:through|to|until)\s+#{MONTH_OF_YEAR}\s#{DATE_DD_NB_ON_SUFFIX}/) do |m1,m2,m3,m4|
451
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2.gsub(/(st|nd|rd|th)/, "") + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4.gsub(/(st|nd|rd|th)/, "")
452
+ end
453
+
454
+ #Tuesday, january 1 - friday, february 15, 2013
455
+ nsub!(/(?:#{DAY_OF_WEEK})?(?:[\s,]+)#{MONTH_OF_YEAR}(?:[\s,]+)#{DATE_DD}\s+(?:through|to|until)\s+(?:#{DAY_OF_WEEK})?(?:[\s,]+)#{MONTH_OF_YEAR}(?:[\s,]+)#{DATE_DD}(?:[\s,]+)#{YEAR}/) do |m1,m2,m3,m4,m5,m6,m7|
456
+ if m7.nil?
457
+ (ZDate.months_of_year.index(m2) + 1).to_s + '/' + m3.gsub(/(st|nd|rd|th)/, "") + ' through ' + (ZDate.months_of_year.index(m5) + 1).to_s + '/' + m6
458
+ else
459
+ (ZDate.months_of_year.index(m2) + 1).to_s + '/' + m3.gsub(/(st|nd|rd|th)/, "") + '/' + m7 + ' through ' + (ZDate.months_of_year.index(m5) + 1).to_s + '/' + m6.gsub(/(st|nd|rd|th)/, "") + "/" + m7
460
+ end
461
+ end
462
+
463
+ #Tuesday, january 1 2013 - friday, february 15, 2013
464
+ nsub!(/(?:#{DAY_OF_WEEK})?(?:[\s,]+)#{MONTH_OF_YEAR}(?:[\s,]+)#{DATE_DD}\s+#{YEAR}\s+(?:through|to|until)\s+(?:#{DAY_OF_WEEK})?(?:[\s,]+)#{MONTH_OF_YEAR}(?:[\s,]+)#{DATE_DD}(?:[\s,]+)#{YEAR}/) do |m1,m2,m3,m4,m5,m6,m7,m8|
465
+ (ZDate.months_of_year.index(m2) + 1).to_s + '/' + m3 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m6) + 1).to_s + '/' + m7 + "/" + m8
466
+ end
467
+
662
468
  # Monthname x through y
663
469
  nsub!(/#{MONTH_OF_YEAR}\s+(?:the\s+)?#{DATE_DD_NB_ON_SUFFIX}\s+(?:of\s+)?(?:#{YEAR}\s+)?(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_NB_ON_SUFFIX}(?:\s+of)?(?:\s+#{YEAR})?/) do |m1,m2,m3,m4,m5|
664
470
  if m3 # $3 holds first occurrence of year
665
- (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m3 +' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m3
471
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m3 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m3
666
472
  elsif m5 # $5 holds second occurrence of year
667
- (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 +' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m5
473
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m5
668
474
  else
669
475
  (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4
670
476
  end
671
477
  end
672
-
478
+
673
479
  # Monthname x through monthname y
674
480
  # Jan 14 through jan 18 => 1/14 through 1/18
675
481
  # Oct 2 until oct 5
@@ -678,28 +484,28 @@ module Nickel
678
484
  (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + '/' + m5 + ' '
679
485
  else
680
486
  (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + ' '
681
- end
487
+ end
682
488
  end
683
-
489
+
684
490
  # Mnday the 23rd, tuesday the 24th and wed the 25th of oct => 11/23 11/24 11/25
685
491
  nsub!(/((?:#{DAY_OF_WEEK_NB}\s+the\s+#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?){1,31})of\s+#{MONTH_OF_YEAR}\s*(#{YEAR})?/) do |m1,m2,m3|
686
492
  month_str = (ZDate.months_of_year.index(m2) + 1).to_s
687
493
  if m3
688
494
  m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
689
- else
495
+ else
690
496
  m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1')
691
497
  end
692
498
  end
693
-
499
+
694
500
  # the 23rd and 24th of october => 11/23 11/24
695
- # the 23rd, 24th, and 25th of october => 11/23 11/24 11/25
501
+ # the 23rd, 24th, and 25th of october => 11/23 11/24 11/25
696
502
  # the 23rd, 24th, and 25th of october 2010 => 11/23/2010 11/24/2010 11/25/2010
697
503
  # monday and tuesday, the 23rd and 24th of july => 7/23 7/24
698
504
  nsub!(/(?:(?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:day\s+)?(?:in\s+)?(?:of\s+)#{MONTH_OF_YEAR}\s*(#{YEAR})?/) do |m1,m2,m3|
699
505
  month_str = (ZDate.months_of_year.index(m2) + 1).to_s
700
506
  if m3
701
507
  m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
702
- else
508
+ else
703
509
  m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1')
704
510
  end
705
511
  end
@@ -713,33 +519,39 @@ module Nickel
713
519
  month_str = (ZDate.months_of_year.index(m1) + 1).to_s
714
520
  m2.gsub(/\b(and|the)\b/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
715
521
  end
716
-
522
+
717
523
  nsub!(/(?:(?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR}\s+((?:(?:the\s+)?#{DATE_DD_WITHOUT_SUFFIX_NB}\s+(?:and\s+)?){1,31})#{YEAR}/) do |m1,m2,m3|
718
524
  month_str = (ZDate.months_of_year.index(m1) + 1).to_s
719
525
  m2.gsub(/\b(and|the)\b/,'').gsub(/#{DATE_DD_WITHOUT_SUFFIX}/, month_str + '/\1/' + m3)
720
526
  end
721
-
722
- # Dec 2nd, 3rd, and 4th => 12/2, 12/3, 12/4
527
+
528
+ # Dec 2nd, 3rd, and 4th => 12/2, 12/3, 12/4
723
529
  # Note: dec 5 9 to 5 will give an error, need to find these and convert to dec 5 from 9 to 5; also dec 3,4, 9 to|through 5 --> dec 3, 4 from 9 through 5
724
530
  nsub!(/(?:(?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR}\s+(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1,m2|
725
- month_str = (ZDate.months_of_year.index(m1) + 1).to_s
531
+ month_str = (ZDate.months_of_year.index(m1) + 1).to_s
726
532
  m2.gsub(/(and|the)/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) {month_str + '/' + $1} # that $1 is from the nested match!
727
533
  end
728
-
534
+
535
+ # Apr 29, 5 - 8pm
536
+ nsub!(/#{MONTH_OF_YEAR}(?:\s)+#{DATE_DD_WITHOUT_SUFFIX}(?:,)?(?:\s)+(#{TIME} through #{TIME})/) do |m1, m2, m3|
537
+ month_str = (ZDate.months_of_year.index(m1) + 1).to_s
538
+ "#{month_str}/#{m2} #{m3}"
539
+ end
540
+
729
541
  # jan 4 2-3 has to be modified, but
730
542
  # jan 24 through jan 26 cannot!
731
543
  # not real sure what this one is doing
732
- # "dec 2, 3, and 4" --> 12/2, 12/3, 12/4
733
- # "mon, tue, wed, dec 2, 3, and 4" --> 12/2, 12/3, 12/4
544
+ # "dec 2, 3, and 4" --> 12/2, 12/3, 12/4
545
+ # "mon, tue, wed, dec 2, 3, and 4" --> 12/2, 12/3, 12/4
734
546
  nsub!(/(#{MONTH_OF_YEAR_NB}\s+(?:the\s+)?(?:(?:#{DATE_DD_WITHOUT_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:to|through|until)\s+#{DATE_DD_WITHOUT_SUFFIX_NB})/) { |m1| m1.gsub(/#{DATE_DD_WITHOUT_SUFFIX}\s+(to|through|until)/, 'from \1 through ') }
735
547
  nsub!(/(?:(?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR}\s+(?:the\s+)?((?:#{DATE_DD_WITHOUT_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1,m2|
736
- month_str = (ZDate.months_of_year.index(m1) + 1).to_s
548
+ month_str = (ZDate.months_of_year.index(m1) + 1).to_s
737
549
  m2.gsub(/(and|the)/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) {month_str + '/' + $1} # $1 from nested match
738
550
  end
739
-
551
+
740
552
  # "monday 12/6" --> 12/6
741
553
  nsub!(/#{DAY_OF_WEEK_NB}\s+(#{DATE_MM_SLASH_DD})/,'\1')
742
-
554
+
743
555
  # "next friday to|until|through the following tuesday" --> 10/12 through 10/16
744
556
  # "next friday through sunday" --> 10/12 through 10/14
745
557
  # "next friday and the following sunday" --> 11/16 11/18
@@ -761,38 +573,38 @@ module Nickel
761
573
  connector = (m2 =~ /and/ ? ' ' : ' through ')
762
574
  m1 + connector + m3
763
575
  end
764
-
576
+
765
577
  # "the wed after next" --> 2 wed from today
766
578
  nsub!(/(?:the\s+)?#{DAY_OF_WEEK}\s+(?:after|following)\s+(?:the\s+)?next/,'2 \1 from today')
767
-
579
+
768
580
  # "mon and tue" --> mon tue
769
581
  nsub!(/(#{DAY_OF_WEEK}\s+and\s+#{DAY_OF_WEEK})(?:\s+and)?/,'\2 \3')
770
-
582
+
771
583
  # "mon wed every week" --> every mon wed
772
584
  nsub!(/((#{DAY_OF_WEEK}(?:\s*)){1,7})(?:of\s+)?(?:every|each)(\s+other)?\s+week/,'every \4 \1')
773
-
585
+
774
586
  # "every 2|3 weeks" --> every 2nd|3rd week
775
587
  nsub!(/(?:repeats\s+)?every\s+(2|3)\s+weeks/) {|m1| "every " + m1.to_i.ordinalize + " week"}
776
-
588
+
777
589
  # "every week on mon tue fri" --> every mon tue fri
778
590
  nsub!(/(?:repeats\s+)?every\s+(?:(other|3rd|2nd)\s+)?weeks?\s+(?:\bon\s+)?((?:#{DAY_OF_WEEK_NB}\s+){1,7})/,'every \1 \2')
779
-
591
+
780
592
  # "every mon and every tue and.... " --> every mon tue ...
781
593
  nsub!(/every\s+#{DAY_OF_WEEK}\s+(?:and\s+)?every\s+#{DAY_OF_WEEK}(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK})?(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK})?(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK})?/,'every \1 \2 \3 \4 \5')
782
-
594
+
783
595
  # monday, wednesday, and friday next week at 8
784
596
  nsub!(/((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})(?:of\s+)?(this|next)\s+week/, '\2 \1')
785
-
597
+
786
598
  # "every day this|next week" --> returns monday through friday of the closest week, kinda stupid
787
599
  # doesn't do that anymore, no date calculations allowed here, instead just formats it nicely for construct finders --> every day this|next week
788
600
  nsub!(/every\s+day\s+(?:of\s+)?(this|the|next)\s+week\b./) {|m1| m1 == 'next' ? "every day next week" : "every day this week"}
789
-
601
+
790
602
  # "every day for the next week" --> "every day this week"
791
603
  nsub!(/every\s+day\s+for\s+(the\s+)?(next|this)\s+week/, 'every day this week')
792
-
604
+
793
605
  # "this weekend" --> sat sun
794
606
  nsub!(/(every\s+day\s+|both\s+days\s+)?this\s+weekend(\s+on)?(\s+both\s+days|\s+every\s+day|\s+sat\s+sun)?/,'sat sun')
795
-
607
+
796
608
  # "this weekend including mon" --> sat sun mon
797
609
  nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+mon/,'sat sun mon')
798
610
  nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+fri/,'fri sat sun')
@@ -800,17 +612,17 @@ module Nickel
800
612
  # Note: next weekend including monday will now fail. Need to make constructors find "next sat sun mon"
801
613
  # "next weekend" --> next weekend
802
614
  nsub!(/(every\s+day\s+|both\s+days\s+)?next\s+weekend(\s+on)?(\s+both\s+days|\s+every\s+day|\s+sat\s+sun)?/,'next weekend')
803
-
615
+
804
616
  # "next weekend including mon" --> next sat sun mon
805
617
  nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+mon/,'next sat sun mon')
806
618
  nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+fri/,'next fri sat sun')
807
-
619
+
808
620
  # "every weekend" --> every sat sun
809
621
  nsub!(/every\s+weekend(?:\s+(?:and|includ(?:es?|ing))\s+(mon|fri))?/,'every sat sun' + ' \1') # regarding "every sat sun fri", order should not matter after "every" keyword
810
-
622
+
811
623
  # "weekend" --> sat sun !!! catch all
812
624
  nsub!(/weekend/,'sat sun')
813
-
625
+
814
626
  # "mon through wed" -- > mon tue wed
815
627
  # CATCH ALL FOR SPANS, TRY NOT TO USE THIS
816
628
  nsub!(/#{DAY_OF_WEEK}\s+(?:through|to|until)\s+#{DAY_OF_WEEK}/) do |m1,m2|
@@ -835,18 +647,18 @@ module Nickel
835
647
  i = (i + 1) % 7
836
648
  end
837
649
  end
838
- ret_string
650
+ ret_string
839
651
  end
840
-
652
+
841
653
  # "every day" --> repeats daily
842
654
  nsub!(/\b(repeat(?:s|ing)?|every|each)\s+da(ily|y)\b/,'repeats daily')
843
-
655
+
844
656
  # "every other week starting this|next fri" --> every other friday starting this friday
845
657
  nsub!(/every\s+(3rd|other)\s+week\s+(?:start(?:s|ing)?|begin(?:s|ning)?)\s+(this|next)\s+#{DAY_OF_WEEK}/,'every \1 \3 start \2 \3')
846
-
658
+
847
659
  # "every other|3rd friday starting this|next week" --> every other|3rd friday starting this|next friday
848
660
  nsub!(/every\s+(3rd|other)\s+#{DAY_OF_WEEK}\s+(?:start(?:s|ing)?|begin(?:s|ning)?)\s+(this|next)\s+week/,'every \1 \2 start \3 \2')
849
-
661
+
850
662
  # "repeats monthly on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday
851
663
  # "repeats every other month on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday
852
664
  # "repeats every three months on the 1st and 2nd friday" --> repeats threemonthly 1st friday 2nd friday
@@ -856,7 +668,7 @@ module Nickel
856
668
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}/) { |m1,m2| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
857
669
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)(?:other|2n?d?)\s+months?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}/) { |m1,m2| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
858
670
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)3r?d?\s+months?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}/) { |m1,m2| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
859
-
671
+
860
672
  # "repeats monthly on the 1st friday" --> repeats monthly 1st friday
861
673
  # "repeats monthly on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday
862
674
  # "repeats every other month on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday
@@ -866,7 +678,7 @@ module Nickel
866
678
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
867
679
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)(?:other|2n?d?)\s+month(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
868
680
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)3r?d?\s+month(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
869
-
681
+
870
682
  # "repeats monthly on the 1st friday saturday" --> repeats monthly 1st friday 1st saturday
871
683
  nsub!(/(?:repeats\s+)(?:(?:each|every|all)\s+)?\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})/) { |m1,m2| "repeats monthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
872
684
  nsub!(/(?:repeats\s+)(?:(?:each|every|all)\s+)?(?:other|2n?d?)\s+month(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})/) { |m1,m2| "repeats altmonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
@@ -874,13 +686,13 @@ module Nickel
874
686
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})/) { |m1,m2| "repeats monthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
875
687
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)(?:other|2n?d?)\s+month(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})/) { |m1,m2| "repeats altmonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
876
688
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)3r?d?\s+month(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})/) { |m1,m2| "repeats threemonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
877
-
689
+
878
690
  # "21st of each month" --> repeats monthly 21st
879
691
  # "on the 21st, 22nd and 25th of each month" --> repeats monthly 21st 22nd 25th
880
692
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+\bmonths?/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
881
693
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+(?:other|2n?d?)\s+months?/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
882
694
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+3r?d?\s+months?/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
883
-
695
+
884
696
  # "repeats each month on the 22nd" --> repeats monthly 22nd
885
697
  # "repeats monthly on the 22nd 23rd and 24th" --> repeats monthly 22nd 23rd 24th
886
698
  # This can ONLY handle multi-day recurrence WITHOUT independent times for each, i.e. "repeats monthly on the 22nd at noon and 24th from 1 to 9" won't work; that's going to be a tricky one.
@@ -894,71 +706,71 @@ module Nickel
894
706
  nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)3r?d?\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
895
707
  nsub!(/(?:repeats\s+)(?:(?:each|every|all)\s+)?3r?d?\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
896
708
  #nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?3r?d?\s+month(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
897
-
709
+
898
710
  # "on day 4 of every month" --> repeats monthly 4
899
711
  # "on days 4 9 and 14 of every month" --> repeats monthly 4 9 14
900
712
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+\bmonths?/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
901
713
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+(?:other|2n?d?)\s+months?/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
902
714
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+3r?d?\s+months?/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
903
-
715
+
904
716
  # "every 22nd of the month" --> repeats monthly 22
905
717
  # "every 22nd 23rd and 25th of the month" --> repeats monthly 22 23 25
906
718
  nsub!(/(?:repeats\s+)?(?:every|each)\s+((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:day\s+)?(?:of\s+)?(?:the\s+)?\bmonth/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
907
719
  nsub!(/(?:repeats\s+)?(?:every|each)\s+other\s+((?:#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:day\s+)?(?:of\s+)?(?:the\s+)?\bmonth/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
908
-
720
+
909
721
  # "every 1st and 2nd fri of the month" --> repeats monthly 1st fri 2nd fri
910
722
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1,m2| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
911
723
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+other\s+((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1,m2| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
912
-
724
+
913
725
  # "every 1st friday of the month" --> repeats monthly 1st friday
914
726
  # "every 1st friday and 2nd tuesday of the month" --> repeats monthly 1st friday 2nd tuesday
915
727
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
916
728
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+other\s+((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
917
-
729
+
918
730
  # "every 1st fri sat of the month" --> repeats monthly 1st fri 1st sat
919
731
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1,m2| "repeats monthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
920
732
  nsub!(/(?:repeats\s+)?(?:each|every|all)\s+other\s+(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) { |m1,m2| "repeats altmonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
921
-
733
+
922
734
  # "the 1st and 2nd friday of every month" --> repeats monthly 1st friday 2nd friday
923
735
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) { |m1,m2| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
924
736
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) { |m1,m2| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
925
737
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) { |m1,m2| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
926
-
738
+
927
739
  # "the 1st friday of every month" --> repeats monthly 1st friday
928
740
  # "the 1st friday and the 2nd tuesday of every month" --> repeats monthly 1st friday 2nd tuesday
929
741
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
930
742
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
931
743
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
932
-
744
+
933
745
  # "the 1st friday saturday of every month" --> repeats monthly 1st friday 1st saturday
934
746
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) { |m1,m2| "repeats monthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
935
747
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) { |m1,m2| "repeats altmonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
936
748
  nsub!(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) { |m1,m2| "repeats threemonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
937
-
749
+
938
750
  # "repeats on the 1st and second friday of the month" --> repeats monthly 1st friday 2nd friday
939
751
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) { |m1,m2| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
940
752
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) { |m1,m2| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
941
753
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) { |m1,m2| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'').split.join(" " + m2 + " ") + " " + m2 + " " }
942
-
754
+
943
755
  # "repeats on the 1st friday of the month --> repeats monthly 1st friday
944
756
  # "repeats on the 1st friday and second tuesday of the month" --> repeats monthly 1st friday 2nd tuesday
945
757
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
946
758
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
947
759
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
948
-
760
+
949
761
  # "repeats on the 1st friday saturday of the month" --> repeats monthly 1st friday 1st saturday
950
762
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) { |m1,m2| "repeats monthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
951
763
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) { |m1,m2| "repeats altmonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
952
764
  nsub!(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) { |m1,m2| "repeats threemonthly " + m1 + " " + m2.split.join(" " + m1 + " ") + " "}
953
-
765
+
954
766
  # "repeats each month" --> every month
955
767
  nsub!(/(repeats\s+)?(each|every)\s+\bmonth(ly)?/,'every month ')
956
768
  nsub!(/all\s+months/,'every month')
957
-
958
- # "repeats every other month" --> every other month
769
+
770
+ # "repeats every other month" --> every other month
959
771
  nsub!(/(repeats\s+)?(each|every)\s+(other|2n?d?)\s+month(ly)?/,'every other month ')
960
772
  nsub!(/(repeats\s+)?bimonthly/,'every other month ') # hyphens have already been replaced in spell check (bi-monthly)
961
-
773
+
962
774
  # "repeats every three months" --> every third month
963
775
  nsub!(/(repeats\s+)?(each|every)\s+3r?d?\s+month/,'every third month ')
964
776
  nsub!(/(repeats\s+)?trimonthly/,'every third month ')
@@ -966,29 +778,29 @@ module Nickel
966
778
  # All months
967
779
  nsub!(/(repeats\s+)?all\s+months/,'every month ')
968
780
  nsub!(/(repeats\s+)?all\s+other\+months/, 'every other month ')
969
-
781
+
970
782
  # All month
971
783
  nsub!(/all\s+month/, 'this month ')
972
784
  nsub!(/all\s+next\s+month/, 'next month ')
973
-
785
+
974
786
  # "repeats 2nd mon" --> repeats monthly 2nd mon
975
787
  # "repeats 2nd mon, 3rd fri, and the last sunday" --> repeats monthly 2nd mon 3rd fri 5th sun
976
788
  nsub!(/repeats\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
977
-
789
+
978
790
  # "starting at x, ending at y" --> from x to y
979
791
  nsub!(/(?:begin|start)(?:s|ing|ning)?\s+(?:at\s+)?#{TIME}\s+(?:and\s+)?end(?:s|ing)?\s+(?:at\s+)#{TIME}/,'from \1 to \2')
980
-
792
+
981
793
  # "the x through the y"
982
794
  nsub!(/^(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_WITH_SUFFIX}$/,'\1 through \2 ')
983
-
795
+
984
796
  # "x week(s) away" --> x week(s) from now
985
797
  nsub!(/([0-9]+)\s+(day|week|month)s?\s+away/,'\1 \2s from now')
986
-
798
+
987
799
  # "x days from now" --> "x days from now"
988
800
  # "in 2 weeks|days|months" --> 2 days|weeks|months from now"
989
801
  nsub!(/\b(an?|[0-9]+)\s+(day|week|month)s?\s+(?:from\s+now|away)/, '\1 \2 from now')
990
802
  nsub!(/in\s+(a|[0-9]+)\s+(week|day|month)s?/, '\1 \2 from now')
991
-
803
+
992
804
  # "x minutes|hours from now" --> "in x hours|minutes"
993
805
  # "in x hour(s)" --> 11/20/07 at 22:00
994
806
  # REDONE, no more calculations
@@ -996,10 +808,10 @@ module Nickel
996
808
  # "in x hours|minutes --> x hours|minutes from now"
997
809
  nsub!(/\b(an?|[0-9]+)\s+(hour|minute)s?\s+(?:from\s+now|away)/, '\1 \2 from now')
998
810
  nsub!(/in\s+(an?|[0-9]+)\s+(hour|minute)s?/, '\1 \2 from now')
999
-
811
+
1000
812
  # Now only
1001
813
  nsub!(/^(?:\s*)(?:right\s+)?now(?:\s*)$/, '0 minutes from now')
1002
-
814
+
1003
815
  # "a week/month from yesterday|tomorrow" --> 1 week from yesterday|tomorrow
1004
816
  nsub!(/(?:(?:a|1)\s+)?(week|month)\s+from\s+(yesterday|tomorrow)/,'1 \1 from \2')
1005
817
 
@@ -1008,8 +820,8 @@ module Nickel
1008
820
 
1009
821
  # "every 2|3 days" --> every 2nd|3rd day
1010
822
  nsub!(/every\s+(2|3)\s+days?/) {|m1| "every " + m1.to_i.ordinalize + " day"}
1011
-
1012
- # "the following" --> following
823
+
824
+ # "the following" --> following
1013
825
  nsub!(/the\s+following/,'following')
1014
826
 
1015
827
  # "friday the 12th to sunday the 14th" --> 12th through 14th
@@ -1017,7 +829,7 @@ module Nickel
1017
829
 
1018
830
  # "between 1 and 4" --> from 1 to 4
1019
831
  nsub!(/between\s+#{TIME}\s+and\s+#{TIME}/,'from \1 to \2')
1020
-
832
+
1021
833
  # "on the 3rd sat of this month" --> "3rd sat this month"
1022
834
  # "on the 3rd sat and 5th tuesday of this month" --> "3rd sat this month 5th tuesday this month"
1023
835
  # "on the 3rd sat and sunday of this month" --> "3rd sat this month 3rd sun this month"
@@ -1027,7 +839,7 @@ module Nickel
1027
839
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:this|of)\s+month/) { |m1,m2| m2.gsub(/\band\b/,'').gsub(/#{DAY_OF_WEEK}/, m1 + ' \1 this month') }
1028
840
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:this|of)\s+month/) { |m1,m2| m1.gsub(/\b(and|the)\b/,'').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' this month') }
1029
841
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:this|of)\s+month/) { |m1| m1.gsub(/\b(and|the)\b/,'').gsub(/#{DAY_OF_WEEK}/,'\1 this month') }
1030
-
842
+
1031
843
  # "on the 3rd sat of next month" --> "3rd sat next month"
1032
844
  # "on the 3rd sat and 5th tuesday of next month" --> "3rd sat next month 5th tuesday next month"
1033
845
  # "on the 3rd sat and sunday of next month" --> "3rd sat this month 3rd sun next month"
@@ -1035,7 +847,7 @@ module Nickel
1035
847
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?next\s+month/) { |m1,m2| m2.gsub(/\band\b/,'').gsub(/#{DAY_OF_WEEK}/, m1 + ' \1 next month') }
1036
848
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?next\s+month/) { |m1,m2| m1.gsub(/\b(and|the)\b/,'').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' next month') }
1037
849
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?next\s+month/) { |m1| m1.gsub(/\b(and|the)\b/,'').gsub(/#{DAY_OF_WEEK}/,'\1 next month') }
1038
-
850
+
1039
851
  # "on the 3rd sat of nov" --> "3rd sat nov"
1040
852
  # "on the 3rd sat and 5th tuesday of nov" --> "3rd sat nov 5th tuesday nov !!!!!!! walking a fine line here, 'nov 5th', but then again the entire nlp walks a pretty fine line
1041
853
  # "on the 3rd sat and sunday of nov" --> "3rd sat nov 3rd sun nov"
@@ -1043,27 +855,27 @@ module Nickel
1043
855
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/) { |m1,m2,m3| m2.gsub(/\band\b/,'').gsub(/#{DAY_OF_WEEK}/, m1 + ' \1 ' + m3) }
1044
856
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/) { |m1,m2,m3| m1.gsub(/\b(and|the)\b/,'').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' ' + m3) }
1045
857
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/) { |m1,m2| m1.gsub(/\b(and|the)\b/,'').gsub(/#{DAY_OF_WEEK}/,'\1 ' + m2) }
1046
-
858
+
1047
859
  # "on the last day of nov" --> "last day nov"
1048
860
  nsub!(/(?:\bon\s+)?(?:the\s+)?last\s+day\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/,'last day \1')
1049
861
  # "on the 1st|last day of this|the month" --> "1st|last day this month"
1050
862
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?(?:this|the)?(?:\s*)month/,'\1 day this month')
1051
863
  # "on the 1st|last day of next month" --> "1st|last day next month"
1052
864
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?next\s+month/,'\1 day next month')
1053
-
865
+
1054
866
  # "every other weekend" --> every other sat sun
1055
867
  nsub!(/every\s+other\s+weekend/,'every other sat sun')
1056
-
868
+
1057
869
  # "this week on mon "--> this mon
1058
870
  nsub!(/this\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/,'this \1')
1059
871
  # "mon of this week " --> this mon
1060
872
  nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?this\s+week/,'this \1')
1061
-
873
+
1062
874
  # "next week on mon "--> next mon
1063
875
  nsub!(/next\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/,'next \1')
1064
876
  # "mon of next week " --> next mon
1065
877
  nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?next\s+week/,'next \1')
1066
-
878
+
1067
879
  # Ordinal this month:
1068
880
  # this will slip by now
1069
881
  # the 23rd of this|the month --> 8/23
@@ -1073,7 +885,7 @@ module Nickel
1073
885
  # this month on the 23rd --> 23rd this month
1074
886
  nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:this|the)\s+month/, '\1 this month')
1075
887
  nsub!(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 this month')
1076
-
888
+
1077
889
  # Ordinal next month:
1078
890
  # this will slip by now
1079
891
  # the 23rd of next month --> 9/23
@@ -1083,59 +895,59 @@ module Nickel
1083
895
  # next month on the 23rd --> 23rd next month
1084
896
  nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:next|the\s+following)\s+month/, '\1 next month')
1085
897
  nsub!(/(?:next|the\s+following)\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 next month')
1086
-
898
+
1087
899
  # "for the next 3 days|weeks|months" --> for 3 days|weeks|months
1088
900
  nsub!(/for\s+(?:the\s+)?(?:next|following)\s+(\d+)\s+(days|weeks|months)/,'for \1 \2')
1089
-
901
+
1090
902
  # This monthname -> monthname
1091
903
  nsub!(/this\s+#{MONTH_OF_YEAR}/, '\1')
1092
-
904
+
1093
905
  # Until monthname -> through monthname
1094
906
  # through shouldn't be included here; through and until mean different things, need to fix wrapper terminology
1095
907
  # "until june --> through june"
1096
908
  nsub!(/(?:through|until)\s+(?:this\s+)?#{MONTH_OF_YEAR}\s+(?:$|\D)/, 'through \1')
1097
-
909
+
1098
910
  # the week of 1/2 -> week of 1/2
1099
911
  nsub!(/(the\s+)?week\s+(of|starting)\s+(the\s+)?/, 'week of ')
1100
-
912
+
1101
913
  # the week ending 1/2 -> week through 1/2
1102
914
  nsub!(/(the\s+)?week\s+(?:ending)\s+/, 'week through ')
1103
-
915
+
1104
916
  # clean up wrapper terminology
1105
917
  # This should always be at end of pre-process
1106
918
  nsub!(/(begin(s|ning)?|start(s|ing)?)(\s+(at|on))?/,'start')
1107
919
  nsub!(/(\bend(s|ing)?|through|until)(\s+(at|on))?/,'through')
1108
920
  nsub!(/start\s+(?:(?:this|in)\s+)?#{MONTH_OF_YEAR}/,'start \1')
1109
-
921
+
1110
922
  # 'the' cases; what this is all about is if someone enters "first sunday of the month" they mean one date. But if someone enters "first sunday of the month until december 2nd" they mean recurring
1111
923
  # Do these actually do ANYTHING anymore?
1112
924
  # "on the 3rd sat and sunday of the month" --> "repeats monthly 3rd sat 3rd sun" OR "3rd sat this month 3rd sun this month"
1113
- if self =~ /(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/
1114
- if self =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
925
+ if query_str =~ /(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/
926
+ if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
1115
927
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/) {|m1,m2| "repeats monthly " + m2.gsub(/\band\b/,'').gsub(/#{DAY_OF_WEEK}/, m1 + ' \1') }
1116
928
  else
1117
929
  nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/) {|m1,m2| m2.gsub(/\band\b/,'').gsub(/#{DAY_OF_WEEK}/, m1 + ' \1 this month') }
1118
930
  end
1119
931
  end
1120
-
932
+
1121
933
  # "on the 2nd and 3rd sat of this month" --> "repeats monthly 2nd sat 3rd sat" OR "2nd sat this month 3rd sat this month"
1122
- if self =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the)\s+month/
1123
- if self =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
934
+ if query_str =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the)\s+month/
935
+ if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
1124
936
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the)\s+month/) {|m1,m2| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2) }
1125
- else
937
+ else
1126
938
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK}\s+(?:of\s+)?(?:the)\s+month/) {|m1,m2| m1.gsub(/\b(and|the)\b/,'').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' this month') }
1127
939
  end
1128
940
  end
1129
-
941
+
1130
942
  # "on the 3rd sat and 5th tuesday of this month" --> "repeats monthly 3rd sat 5th tue" OR "3rd sat this month 5th tuesday this month"
1131
- if self =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/
1132
- if self =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
943
+ if query_str =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/
944
+ if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/
1133
945
  nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') }
1134
946
  else
1135
- nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/) { |m1| m1.gsub(/\b(and|the)\b/,'').gsub(/#{day_of_week}/,'\1 this month') }
947
+ nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/) { |m1| m1.gsub(/\b(and|the)\b/,'').gsub(/#{DAY_OF_WEEK}/,'\1 this month') }
1136
948
  end
1137
949
  end
1138
-
950
+
1139
951
  nsub!(/from\s+now\s+(through|to|until)/,'now through')
1140
952
  end
1141
953
  end