nickel 0.0.3 → 0.0.4

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.
@@ -0,0 +1,1143 @@
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]
4
+
5
+ module Nickel
6
+
7
+ class NLPQuery < String
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
+
13
+ def standardize
14
+ @query = self.dup # needed for case correcting after extract_message has been called
15
+ query_formatting # easy text manipulation, no regex involved here
16
+ query_pre_processing # puts query in the form that construct_finder understands, lots of manipulation here
17
+ self
18
+ end
19
+
20
+ def query_formatting
21
+ gsub!(/\n/,'')
22
+ downcase!
23
+ remove_unused_punctuation
24
+ replace_backslashes
25
+ run_spell_check
26
+ remove_unnecessary_words
27
+ standardize_days
28
+ standardize_months
29
+ standardize_numbers
30
+ standardize_am_pm
31
+ replace_hyphens
32
+ insert_repeats_before_words_indicating_recurrence_lame
33
+ insert_space_at_end_of_string_lame
34
+ @after_formatting = self.dup # save current state
35
+ end
36
+
37
+ # Usage:
38
+ # self.nsub!(/foo/, 'bar')
39
+ #
40
+ # nsub! is like gsub! except it logs the calling method in @changed_in.
41
+ # There is another difference: When using blocks, matched strings are
42
+ # available as block params, e.g.: # nsub!(/(match1)(match2)/) {|m1,m2|}
43
+ #
44
+ # I wrote this because I was having problems overriding gsub and passing
45
+ # a block from the new gsub to super.
46
+ def nsub!(*args)
47
+ if m = self.match(args[0]) # m will now hold the FIRST set of backreferenced matches
48
+ # there is at least one match
49
+ @changed_in ||= []
50
+ @changed_in << calling_method
51
+ if block_given?
52
+ # gsub!(args[0]) {yield(*m.to_a[1..-1])} # There is a bug here: If gsub matches more than once,
53
+ # then the first set of referenced matches will be passed to the block
54
+ 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
+ while (m_old = m.dup) && (m = m.post_match.match(args[0]))
56
+ ret_str << m.pre_match + m[0].sub(args[0]) {yield(*m.to_a[1..-1])}
57
+ end
58
+ ret_str << m_old.post_match
59
+ self.sub!(/.*/,ret_str)
60
+ else
61
+ gsub!(args[0],args[1])
62
+ end
63
+ end
64
+ end
65
+
66
+ def query_pre_processing
67
+ standardize_input
68
+ end
69
+
70
+ def remove_unused_punctuation
71
+ nsub!(/,/,' ')
72
+ nsub!(/\./,'')
73
+ nsub!(/;/,'')
74
+ nsub!(/['`]/,'')
75
+ end
76
+
77
+ def replace_backslashes
78
+ nsub!(/\\/,'/')
79
+ end
80
+
81
+ def run_spell_check
82
+ nsub!(/tomm?orr?ow|romorrow/,'tomorrow')
83
+ nsub!(/weeknd/,'weekend')
84
+ nsub!(/weekends/,'every sat sun')
85
+ nsub!(/everyother/,'every other')
86
+ nsub!(/weak/,'week')
87
+ nsub!(/everyweek/,'every week')
88
+ nsub!(/everymonth/,'every month')
89
+ nsub!(/c?h[oa]nn?[aui][ck][ck]?[ua]h?/,'hannukkah')
90
+ nsub!(/frist/,'1st')
91
+ nsub!(/eveyr|evrey/,'every')
92
+ nsub!(/fridya|friady|fridy/,'friday')
93
+ nsub!(/thurdsday/,'thursday')
94
+ nsub!(/x-?mas/,'christmas')
95
+ nsub!(/st\s+(patrick|patty|pat)s?(\s+day)?/,'st patricks day')
96
+ nsub!(/frouth/,'fourth')
97
+ nsub!(/\btill\b/,'through')
98
+ nsub!(/\bthru\b|\bthrouh\b|\bthough\b|\bthrew\b|\bthrow\b|\bthroug\b|\bthuogh\b/,'through')
99
+ nsub!(/weekdays|every\s+weekday/,'every monday through friday')
100
+ nsub!(/\bevery?day\b/,'every day')
101
+ nsub!(/eigth/,'eighth')
102
+ nsub!(/bi[-\s]monthly/,'bimonthly')
103
+ nsub!(/tri[-\s]monthly/,'trimonthly')
104
+ end
105
+
106
+ def remove_unnecessary_words
107
+ nsub!(/coming/,'')
108
+ nsub!(/o'?clock/,'')
109
+ nsub!(/\btom\b/,'tomorrow')
110
+ nsub!(/\s*in\s+(the\s+)?(morning|am)/,' am')
111
+ nsub!(/\s*in\s+(the\s+)?(afternoon|pm|evenn?ing)/,' pm')
112
+ nsub!(/\s*at\s+night/,'pm')
113
+ nsub!(/(after\s*)?noon(ish)?/,'12:00pm')
114
+ nsub!(/\bmi(dn|nd)ight\b/,'12:00am')
115
+ nsub!(/final/,'last')
116
+ nsub!(/recur(s|r?ing)?/,'repeats')
117
+ nsub!(/\beach\b/,'every')
118
+ nsub!(/running\s+(until|through)/,'through')
119
+ nsub!(/runn?(s|ing)|go(ing|e?s)/,'for')
120
+ nsub!(/next\s+occ?urr?[ae]nce(\s+is)?/,'start')
121
+ nsub!(/next\s+date(\s+it)?(\s+occ?urr?s)?(\s+is)?/,'start')
122
+ nsub!(/forever/,'repeats daily')
123
+ nsub!(/\bany(?:\s+)?day\b/,'every day')
124
+ nsub!(/^anytime$/,'every day') # user entered anytime by itself, not 'dayname anytime', caught next
125
+ nsub!(/any(\s)?time|whenever/,'all day')
126
+ end
127
+
128
+ def standardize_days
129
+ nsub!(/mondays/,'every mon')
130
+ nsub!(/monday/,'mon')
131
+ nsub!(/tuesdays/,'every tue')
132
+ nsub!(/tuesadys/,'every tue')
133
+ nsub!(/tuesday/,'tue')
134
+ nsub!(/tuesady/,'tue')
135
+ nsub!(/wednesdays/,'every wed')
136
+ nsub!(/wednesday/,'wed')
137
+ nsub!(/thursdays/,'every thu')
138
+ nsub!(/thurdsays/,'every thu')
139
+ nsub!(/thursadys/,'every thu')
140
+ nsub!(/thursday/,'thu')
141
+ nsub!(/thurdsay/,'thu')
142
+ nsub!(/thursady/,'thu')
143
+ nsub!(/\bthurd?\b/,'thu')
144
+ nsub!(/\bthurd?\b/,'thu')
145
+ nsub!(/fridays/,'every fri')
146
+ nsub!(/firdays/,'every fri')
147
+ nsub!(/friday/,'fri')
148
+ nsub!(/firday/,'fri')
149
+ nsub!(/saturdays/,'every sat')
150
+ nsub!(/saturday/,'sat')
151
+ nsub!(/sundays/,'every sun')
152
+ nsub!(/sunday/,'sun')
153
+ end
154
+
155
+ def standardize_months
156
+ nsub!(/january/,'jan')
157
+ nsub!(/february/,'feb')
158
+ nsub!(/febr/, 'feb')
159
+ nsub!(/march/,'mar')
160
+ nsub!(/april/,'apr')
161
+ nsub!(/may/,'may')
162
+ nsub!(/june/,'jun')
163
+ nsub!(/july/,'jul')
164
+ nsub!(/august/,'aug')
165
+ nsub!(/september/,'sep')
166
+ nsub!(/sept/,'sep')
167
+ nsub!(/october/,'oct')
168
+ nsub!(/november/,'nov')
169
+ nsub!(/novermber/,'nov')
170
+ nsub!(/novem/,'nov')
171
+ nsub!(/decemb?e?r?/,'dec')
172
+ end
173
+
174
+ def standardize_numbers
175
+ nsub!(/\bone\s*-?\s*hundred\b/,'100')
176
+ nsub!(/\bone\s*-?\s*hundredth\b/,'100th')
177
+ nsub!(/\bninety\s*-?\s*nine\b/,'99')
178
+ nsub!(/\bninety\s*-?\s*ninth\b/,'99th')
179
+ nsub!(/\bninety\s*-?\s*eight\b/,'98')
180
+ nsub!(/\bninety\s*-?\s*eighth\b/,'98th')
181
+ nsub!(/\bninety\s*-?\s*seven\b/,'97')
182
+ nsub!(/\bninety\s*-?\s*seventh\b/,'97th')
183
+ nsub!(/\bninety\s*-?\s*six\b/,'96')
184
+ nsub!(/\bninety\s*-?\s*sixth\b/,'96th')
185
+ nsub!(/\bninety\s*-?\s*five\b/,'95')
186
+ nsub!(/\bninety\s*-?\s*fifth\b/,'95th')
187
+ nsub!(/\bninety\s*-?\s*four\b/,'94')
188
+ nsub!(/\bninety\s*-?\s*fourth\b/,'94th')
189
+ nsub!(/\bninety\s*-?\s*three\b/,'93')
190
+ nsub!(/\bninety\s*-?\s*third\b/,'93rd')
191
+ nsub!(/\bninety\s*-?\s*two\b/,'92')
192
+ nsub!(/\bninety\s*-?\s*second\b/,'92nd')
193
+ nsub!(/\bninety\s*-?\s*one\b/,'91')
194
+ nsub!(/\bninety\s*-?\s*first\b/,'91st')
195
+ nsub!(/\bninety\b/,'90')
196
+ nsub!(/\bninetieth\b/,'90th')
197
+ nsub!(/\beighty\s*-?\s*nine\b/,'89')
198
+ nsub!(/\beighty\s*-?\s*ninth\b/,'89th')
199
+ nsub!(/\beighty\s*-?\s*eight\b/,'88')
200
+ nsub!(/\beighty\s*-?\s*eighth\b/,'88th')
201
+ nsub!(/\beighty\s*-?\s*seven\b/,'87')
202
+ nsub!(/\beighty\s*-?\s*seventh\b/,'87th')
203
+ nsub!(/\beighty\s*-?\s*six\b/,'86')
204
+ nsub!(/\beighty\s*-?\s*sixth\b/,'86th')
205
+ nsub!(/\beighty\s*-?\s*five\b/,'85')
206
+ nsub!(/\beighty\s*-?\s*fifth\b/,'85th')
207
+ nsub!(/\beighty\s*-?\s*four\b/,'84')
208
+ nsub!(/\beighty\s*-?\s*fourth\b/,'84th')
209
+ nsub!(/\beighty\s*-?\s*three\b/,'83')
210
+ nsub!(/\beighty\s*-?\s*third\b/,'83rd')
211
+ nsub!(/\beighty\s*-?\s*two\b/,'82')
212
+ nsub!(/\beighty\s*-?\s*second\b/,'82nd')
213
+ nsub!(/\beighty\s*-?\s*one\b/,'81')
214
+ nsub!(/\beighty\s*-?\s*first\b/,'81st')
215
+ nsub!(/\beighty\b/,'80')
216
+ nsub!(/\beightieth\b/,'80th')
217
+ nsub!(/\bseventy\s*-?\s*nine\b/,'79')
218
+ nsub!(/\bseventy\s*-?\s*ninth\b/,'79th')
219
+ nsub!(/\bseventy\s*-?\s*eight\b/,'78')
220
+ nsub!(/\bseventy\s*-?\s*eighth\b/,'78th')
221
+ nsub!(/\bseventy\s*-?\s*seven\b/,'77')
222
+ nsub!(/\bseventy\s*-?\s*seventh\b/,'77th')
223
+ nsub!(/\bseventy\s*-?\s*six\b/,'76')
224
+ nsub!(/\bseventy\s*-?\s*sixth\b/,'76th')
225
+ nsub!(/\bseventy\s*-?\s*five\b/,'75')
226
+ nsub!(/\bseventy\s*-?\s*fifth\b/,'75th')
227
+ nsub!(/\bseventy\s*-?\s*four\b/,'74')
228
+ nsub!(/\bseventy\s*-?\s*fourth\b/,'74th')
229
+ nsub!(/\bseventy\s*-?\s*three\b/,'73')
230
+ nsub!(/\bseventy\s*-?\s*third\b/,'73rd')
231
+ nsub!(/\bseventy\s*-?\s*two\b/,'72')
232
+ nsub!(/\bseventy\s*-?\s*second\b/,'72nd')
233
+ nsub!(/\bseventy\s*-?\s*one\b/,'71')
234
+ nsub!(/\bseventy\s*-?\s*first\b/,'71st')
235
+ nsub!(/\bseventy\b/,'70')
236
+ nsub!(/\bseventieth\b/,'70th')
237
+ nsub!(/\bsixty\s*-?\s*nine\b/,'69')
238
+ nsub!(/\bsixty\s*-?\s*ninth\b/,'69th')
239
+ nsub!(/\bsixty\s*-?\s*eight\b/,'68')
240
+ nsub!(/\bsixty\s*-?\s*eighth\b/,'68th')
241
+ nsub!(/\bsixty\s*-?\s*seven\b/,'67')
242
+ nsub!(/\bsixty\s*-?\s*seventh\b/,'67th')
243
+ nsub!(/\bsixty\s*-?\s*six\b/,'66')
244
+ nsub!(/\bsixty\s*-?\s*sixth\b/,'66th')
245
+ nsub!(/\bsixty\s*-?\s*five\b/,'65')
246
+ nsub!(/\bsixty\s*-?\s*fifth\b/,'65th')
247
+ nsub!(/\bsixty\s*-?\s*four\b/,'64')
248
+ nsub!(/\bsixty\s*-?\s*fourth\b/,'64th')
249
+ nsub!(/\bsixty\s*-?\s*three\b/,'63')
250
+ nsub!(/\bsixty\s*-?\s*third\b/,'63rd')
251
+ nsub!(/\bsixty\s*-?\s*two\b/,'62')
252
+ nsub!(/\bsixty\s*-?\s*second\b/,'62nd')
253
+ nsub!(/\bsixty\s*-?\s*one\b/,'61')
254
+ nsub!(/\bsixty\s*-?\s*first\b/,'61st')
255
+ nsub!(/\bsixty\b/,'60')
256
+ nsub!(/\bsixtieth\b/,'60th')
257
+ nsub!(/\bfifty\s*-?\s*nine\b/,'59')
258
+ nsub!(/\bfifty\s*-?\s*ninth\b/,'59th')
259
+ nsub!(/\bfifty\s*-?\s*eight\b/,'58')
260
+ nsub!(/\bfifty\s*-?\s*eighth\b/,'58th')
261
+ nsub!(/\bfifty\s*-?\s*seven\b/,'57')
262
+ nsub!(/\bfifty\s*-?\s*seventh\b/,'57th')
263
+ nsub!(/\bfifty\s*-?\s*six\b/,'56')
264
+ nsub!(/\bfifty\s*-?\s*sixth\b/,'56th')
265
+ nsub!(/\bfifty\s*-?\s*five\b/,'55')
266
+ nsub!(/\bfifty\s*-?\s*fifth\b/,'55th')
267
+ nsub!(/\bfifty\s*-?\s*four\b/,'54')
268
+ nsub!(/\bfifty\s*-?\s*fourth\b/,'54th')
269
+ nsub!(/\bfifty\s*-?\s*three\b/,'53')
270
+ nsub!(/\bfifty\s*-?\s*third\b/,'53rd')
271
+ nsub!(/\bfifty\s*-?\s*two\b/,'52')
272
+ nsub!(/\bfifty\s*-?\s*second\b/,'52nd')
273
+ nsub!(/\bfifty\s*-?\s*one\b/,'51')
274
+ nsub!(/\bfifty\s*-?\s*first\b/,'51st')
275
+ nsub!(/\bfifty\b/,'50')
276
+ nsub!(/\bfiftieth\b/,'50th')
277
+ nsub!(/\bfourty\s*-?\s*nine\b/,'49')
278
+ nsub!(/\bfourty\s*-?\s*ninth\b/,'49th')
279
+ nsub!(/\bfourty\s*-?\s*eight\b/,'48')
280
+ nsub!(/\bfourty\s*-?\s*eighth\b/,'48th')
281
+ nsub!(/\bfourty\s*-?\s*seven\b/,'47')
282
+ nsub!(/\bfourty\s*-?\s*seventh\b/,'47th')
283
+ nsub!(/\bfourty\s*-?\s*six\b/,'46')
284
+ nsub!(/\bfourty\s*-?\s*sixth\b/,'46th')
285
+ nsub!(/\bfourty\s*-?\s*five\b/,'45')
286
+ nsub!(/\bfourty\s*-?\s*fifth\b/,'45th')
287
+ nsub!(/\bfourty\s*-?\s*four\b/,'44')
288
+ nsub!(/\bfourty\s*-?\s*fourth\b/,'44th')
289
+ nsub!(/\bfourty\s*-?\s*three\b/,'43')
290
+ nsub!(/\bfourty\s*-?\s*third\b/,'43rd')
291
+ nsub!(/\bfourty\s*-?\s*two\b/,'42')
292
+ nsub!(/\bfourty\s*-?\s*second\b/,'42nd')
293
+ nsub!(/\bfourty\s*-?\s*one\b/,'41')
294
+ nsub!(/\bfourty\s*-?\s*first\b/,'41st')
295
+ nsub!(/\bfourty\b/,'40')
296
+ nsub!(/\bfourtieth\b/,'40th')
297
+ nsub!(/\bthirty\s*-?\s*nine\b/,'39')
298
+ nsub!(/\bthirty\s*-?\s*ninth\b/,'39th')
299
+ nsub!(/\bthirty\s*-?\s*eight\b/,'38')
300
+ nsub!(/\bthirty\s*-?\s*eighth\b/,'38th')
301
+ nsub!(/\bthirty\s*-?\s*seven\b/,'37')
302
+ nsub!(/\bthirty\s*-?\s*seventh\b/,'37th')
303
+ nsub!(/\bthirty\s*-?\s*six\b/,'36')
304
+ nsub!(/\bthirty\s*-?\s*sixth\b/,'36th')
305
+ nsub!(/\bthirty\s*-?\s*five\b/,'35')
306
+ nsub!(/\bthirty\s*-?\s*fifth\b/,'35th')
307
+ nsub!(/\bthirty\s*-?\s*four\b/,'34')
308
+ nsub!(/\bthirty\s*-?\s*fourth\b/,'34th')
309
+ nsub!(/\bthirty\s*-?\s*three\b/,'33')
310
+ nsub!(/\bthirty\s*-?\s*third\b/,'33rd')
311
+ nsub!(/\bthirty\s*-?\s*two\b/,'32')
312
+ nsub!(/\bthirty\s*-?\s*second\b/,'32nd')
313
+ nsub!(/\bthirty\s*-?\s*one\b/,'31')
314
+ nsub!(/\bthirty\s*-?\s*first\b/,'31st')
315
+ nsub!(/\bthirty\b/,'30')
316
+ nsub!(/\bthirtieth\b/,'30th')
317
+ nsub!(/\btwenty\s*-?\s*nine\b/,'29')
318
+ nsub!(/\btwenty\s*-?\s*ninth\b/,'29th')
319
+ nsub!(/\btwenty\s*-?\s*eight\b/,'28')
320
+ nsub!(/\btwenty\s*-?\s*eighth\b/,'28th')
321
+ nsub!(/\btwenty\s*-?\s*seven\b/,'27')
322
+ nsub!(/\btwenty\s*-?\s*seventh\b/,'27th')
323
+ nsub!(/\btwenty\s*-?\s*six\b/,'26')
324
+ nsub!(/\btwenty\s*-?\s*sixth\b/,'26th')
325
+ nsub!(/\btwenty\s*-?\s*five\b/,'25')
326
+ nsub!(/\btwenty\s*-?\s*fifth\b/,'25th')
327
+ nsub!(/\btwenty\s*-?\s*four\b/,'24')
328
+ nsub!(/\btwenty\s*-?\s*fourth\b/,'24th')
329
+ nsub!(/\btwenty\s*-?\s*three\b/,'23')
330
+ nsub!(/\btwenty\s*-?\s*third\b/,'23rd')
331
+ nsub!(/\btwenty\s*-?\s*two\b/,'22')
332
+ nsub!(/\btwenty\s*-?\s*second\b/,'22nd')
333
+ nsub!(/\btwenty\s*-?\s*one\b/,'21')
334
+ nsub!(/\btwenty\s*-?\s*first\b/,'21st')
335
+ nsub!(/\btwenty\b/,'20')
336
+ nsub!(/\btwentieth\b/,'20th')
337
+ nsub!(/\bnineteen\b/,'19')
338
+ nsub!(/\bnineteenth\b/,'19th')
339
+ nsub!(/\beighteen\b/,'18')
340
+ nsub!(/\beighteenth\b/,'18th')
341
+ nsub!(/\bseventeen\b/,'17')
342
+ nsub!(/\bseventeenth\b/,'17th')
343
+ nsub!(/\bsixteen\b/,'16')
344
+ nsub!(/\bsixteenth\b/,'16th')
345
+ nsub!(/\bfifteen\b/,'15')
346
+ nsub!(/\bfifteenth\b/,'15th')
347
+ nsub!(/\bfourteen\b/,'14')
348
+ nsub!(/\bfourteenth\b/,'14th')
349
+ nsub!(/\bthirteen/,'13')
350
+ nsub!(/\bthirteenth/,'13th')
351
+ nsub!(/\btwelve\b/,'12')
352
+ nsub!(/\btwelfth\b/,'12th')
353
+ nsub!(/\beleven\b/,'11')
354
+ nsub!(/\beleventh\b/,'11th')
355
+ nsub!(/\bten\b/,'10')
356
+ nsub!(/\btenth\b/,'10th')
357
+ nsub!(/\bnine\b/,'9')
358
+ nsub!(/\bninth\b/,'9th')
359
+ nsub!(/\beight\b/,'8')
360
+ nsub!(/\beighth\b/,'8th')
361
+ nsub!(/\bseven\b/,'7')
362
+ nsub!(/\bseventh\b/,'7th')
363
+ nsub!(/\bsix\b/,'6')
364
+ nsub!(/\bsixth\b/,'6th')
365
+ nsub!(/\bfive\b/,'5')
366
+ nsub!(/\bfifth\b/,'5th')
367
+ nsub!(/\bfour\b/,'4')
368
+ nsub!(/\bfourth\b/,'4th')
369
+ nsub!(/\bthree\b/,'3')
370
+ nsub!(/\bthird\b/,'3rd')
371
+ nsub!(/\btwo\b/,'2')
372
+ nsub!(/\bsecond\b/,'2nd')
373
+ nsub!(/\bone\b/,'1')
374
+ nsub!(/\bfirst\b/,'1st')
375
+ nsub!(/\bzero\b/,'0')
376
+ nsub!(/\bzeroth\b/,'0th')
377
+ end
378
+
379
+ def standardize_am_pm
380
+ nsub!(/([0-9])(?:\s+)?a\b/,'\1am') # allows 5a as 5am
381
+ nsub!(/([0-9])(?:\s+)?p\b/,'\1pm') # allows 5p as 5pm
382
+ nsub!(/\s+am\b/,'am') # removes any spaces before am, shouldn't I check for preceeding digits?
383
+ nsub!(/\s+pm\b/,'pm') # removes any spaces before pm, shouldn't I check for preceeding digits?
384
+ end
385
+
386
+ def replace_hyphens
387
+ nsub!(/--?/,' through ')
388
+ end
389
+
390
+ def insert_repeats_before_words_indicating_recurrence_lame
391
+ comps = self.split
392
+ (daily_index = comps.index("daily")) && comps[daily_index - 1] != "repeats" && comps[daily_index] = "repeats daily"
393
+ (weekly_index = comps.index("weekly")) && comps[weekly_index - 1] != "repeats" && comps[weekly_index] = "repeats weekly"
394
+ (monthly_index = comps.index("monthly")) && comps[monthly_index - 1] != "repeats" && comps[monthly_index] = "repeats monthly"
395
+ if (rejoin = comps.join(' ')) != self
396
+ nsub!(/.+/,rejoin)
397
+ end
398
+ end
399
+
400
+ def insert_space_at_end_of_string_lame
401
+ # nsub!(/(.+)/,'\1 ') # I don't really want to be notified about this
402
+ gsub!(/(.+)/,'\1 ')
403
+ end
404
+
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: hstr = "0" + mdata[1] # what the user meant based on the number of digits they provided
478
+ when 2: hstr = mdata[1] # e.g. "11" means 11:00
479
+ when 3: hstr = "0" + mdata[1][0..0]; mstr = mdata[1][1..2] # e.g. "530" means 5:30
480
+ when 4: 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(" ")
624
+ end
625
+
626
+
627
+ private
628
+ def standardize_input
629
+ nsub!(/last\s+#{DAY_OF_WEEK}/,'5th \1') # last dayname => 5th dayname
630
+ nsub!(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day
631
+ nsub!(/^(through|until)/,'today through') # ^through => today through
632
+ nsub!(/every\s*(night|morning)/,'every day')
633
+ nsub!(/tonight/,'today')
634
+ nsub!(/this(?:\s+)?morning/,'today')
635
+ nsub!(/before\s+12pm/,'6am to 12pm') # arbitrary
636
+
637
+ # Handle 'THE' Cases
638
+ # Attempt to pick out where a user entered 'the' when they really mean 'every'.
639
+ # For example,
640
+ # The first of every month and the 22nd of THE month => repeats monthly first xxxxxx repeats monthly 22nd xxxxxxx
641
+ nsub!(/(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:of\s+)?(?:every|each)\s+month((?:.*)of\s+the\s+month(?:.*))/) do |m1,m2|
642
+ ret_str = " repeats monthly " + m1
643
+ ret_str << m2.gsub(/(?:and\s+)?(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+of\s+the\s+month/, ' repeats monthly \1 ')
644
+ end
645
+
646
+ # Every first sunday of the month and the last tuesday => repeats monthly first sunday xxxxxxxxx repeats monthly last tuesday xxxxxxx
647
+ 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
+ ret_str = " repeats monthly " + m1 + " " + m2 + " "
649
+ ret_str << m3.gsub(/and\s+(?:the\s+)?#{WEEK_OF_MONTH}\s+#{DAY_OF_WEEK}(?:\s+)?(?:of\s+)?(?:the\s+)?(?:month\s+)?/, ' repeats monthly \1 \2 ')
650
+ end
651
+
652
+ # The x through the y of oct z => 10/x/z through 10/y/z
653
+ 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
+ (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 + '/' + m4
655
+ end
656
+
657
+ # The x through the y of oct => 10/x through 10/y
658
+ 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
+ (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2
660
+ end
661
+
662
+ # Monthname x through y
663
+ 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
+ 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
666
+ 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
668
+ else
669
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4
670
+ end
671
+ end
672
+
673
+ # Monthname x through monthname y
674
+ # Jan 14 through jan 18 => 1/14 through 1/18
675
+ # Oct 2 until oct 5
676
+ nsub!(/#{MONTH_OF_YEAR}\s+#{DATE_DD_NB_ON_SUFFIX}\s+(?:to|through|until)\s+#{MONTH_OF_YEAR}\s+#{DATE_DD_NB_ON_SUFFIX}\s+(?:of\s+)?(?:#{YEAR})?/) do |m1,m2,m3,m4,m5|
677
+ if m5
678
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + '/' + m5 + ' '
679
+ else
680
+ (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + ' '
681
+ end
682
+ end
683
+
684
+ # Mnday the 23rd, tuesday the 24th and wed the 25th of oct => 11/23 11/24 11/25
685
+ 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
+ month_str = (ZDate.months_of_year.index(m2) + 1).to_s
687
+ if m3
688
+ m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
689
+ else
690
+ m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1')
691
+ end
692
+ end
693
+
694
+ # the 23rd and 24th of october => 11/23 11/24
695
+ # the 23rd, 24th, and 25th of october => 11/23 11/24 11/25
696
+ # the 23rd, 24th, and 25th of october 2010 => 11/23/2010 11/24/2010 11/25/2010
697
+ # monday and tuesday, the 23rd and 24th of july => 7/23 7/24
698
+ 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
+ month_str = (ZDate.months_of_year.index(m2) + 1).to_s
700
+ if m3
701
+ m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
702
+ else
703
+ m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1')
704
+ end
705
+ end
706
+
707
+ # Match date with year first.
708
+ # Don't allow mixing of suffixes, e.g. "dec 3rd 2008 at 4 and dec 5 2008 9 to 5"
709
+ # Dec 2nd, 3rd, and 5th 2008 => 12/2/2008 12/2/2008 12/5/2008
710
+ # Mon nov 23rd 08
711
+ # Dec 2, 3, 5, 2008 => 12/2/2008 12/3/2008 12/5/2008
712
+ nsub!(/(?:(?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR}\s+((?:(?:the\s+)?#{DATE_DD_WITH_SUFFIX_NB}\s+(?:and\s+)?){1,31})#{YEAR}/) do |m1,m2,m3|
713
+ month_str = (ZDate.months_of_year.index(m1) + 1).to_s
714
+ m2.gsub(/\b(and|the)\b/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3)
715
+ end
716
+
717
+ 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
+ month_str = (ZDate.months_of_year.index(m1) + 1).to_s
719
+ m2.gsub(/\b(and|the)\b/,'').gsub(/#{DATE_DD_WITHOUT_SUFFIX}/, month_str + '/\1/' + m3)
720
+ end
721
+
722
+ # Dec 2nd, 3rd, and 4th => 12/2, 12/3, 12/4
723
+ # 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
+ 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
726
+ m2.gsub(/(and|the)/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) {month_str + '/' + $1} # that $1 is from the nested match!
727
+ end
728
+
729
+ # jan 4 2-3 has to be modified, but
730
+ # jan 24 through jan 26 cannot!
731
+ # 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
734
+ 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
+ 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
737
+ m2.gsub(/(and|the)/,'').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) {month_str + '/' + $1} # $1 from nested match
738
+ end
739
+
740
+ # "monday 12/6" --> 12/6
741
+ nsub!(/#{DAY_OF_WEEK_NB}\s+(#{DATE_MM_SLASH_DD})/,'\1')
742
+
743
+ # "next friday to|until|through the following tuesday" --> 10/12 through 10/16
744
+ # "next friday through sunday" --> 10/12 through 10/14
745
+ # "next friday and the following sunday" --> 11/16 11/18
746
+ # we are not going to do date calculations here anymore, so instead:
747
+ # next friday to|until|through the following tuesday" --> next friday through tuesday
748
+ # next friday and the following sunday --> next friday and sunday
749
+ nsub!(/next\s+#{DAY_OF_WEEK}\s+(to|until|through|and)\s+(?:the\s+)?(?:following|next)?(?:\s+)?#{DAY_OF_WEEK}/) do |m1,m2,m3|
750
+ connector = (m2 =~ /and/ ? ' ' : ' through ')
751
+ "next " + m1 + connector + m3
752
+ end
753
+
754
+ # "this friday to|until|through the following tuesday" --> 10/5 through 10/9
755
+ # "this friday through following sunday" --> 10/5 through 10/7
756
+ # "this friday and the following monday" --> 11/9 11/12
757
+ # No longer performing date calculation
758
+ # this friday and the following monday --> fri mon
759
+ # this friday through the following tuesday --> fri through tues
760
+ nsub!(/(?:this\s+)?#{DAY_OF_WEEK}\s+(to|until|through|and)\s+(?:the\s+)?(?:this|following)(?:\s+)?#{DAY_OF_WEEK}/) do |m1,m2,m3|
761
+ connector = (m2 =~ /and/ ? ' ' : ' through ')
762
+ m1 + connector + m3
763
+ end
764
+
765
+ # "the wed after next" --> 2 wed from today
766
+ nsub!(/(?:the\s+)?#{DAY_OF_WEEK}\s+(?:after|following)\s+(?:the\s+)?next/,'2 \1 from today')
767
+
768
+ # "mon and tue" --> mon tue
769
+ nsub!(/(#{DAY_OF_WEEK}\s+and\s+#{DAY_OF_WEEK})(?:\s+and)?/,'\2 \3')
770
+
771
+ # "mon wed every week" --> every mon wed
772
+ nsub!(/((#{DAY_OF_WEEK}(?:\s+)?){1,7})(?:of\s+)?(?:every|each)(\s+other)?\s+week/,'every \4 \1')
773
+
774
+ # "every 2|3 weeks" --> every 2nd|3rd week
775
+ nsub!(/(?:repeats\s+)?every\s+(2|3)\s+weeks/) {|m1| "every " + m1.to_i.ordinalize + " week"}
776
+
777
+ # "every week on mon tue fri" --> every mon tue fri
778
+ nsub!(/(?:repeats\s+)?every\s+(?:(other|3rd|2nd)\s+)?weeks?\s+(?:\bon\s+)?((?:#{DAY_OF_WEEK_NB}\s+){1,7})/,'every \1 \2')
779
+
780
+ # "every mon and every tue and.... " --> every mon tue ...
781
+ 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
+
783
+ # monday, wednesday, and friday next week at 8
784
+ nsub!(/((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})(?:of\s+)?(this|next)\s+week/, '\2 \1')
785
+
786
+ # "every day this|next week" --> returns monday through friday of the closest week, kinda stupid
787
+ # doesn't do that anymore, no date calculations allowed here, instead just formats it nicely for construct finders --> every day this|next week
788
+ 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
+
790
+ # "every day for the next week" --> "every day this week"
791
+ nsub!(/every\s+day\s+for\s+(the\s+)?(next|this)\s+week/, 'every day this week')
792
+
793
+ # "this weekend" --> sat sun
794
+ 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
+
796
+ # "this weekend including mon" --> sat sun mon
797
+ nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+mon/,'sat sun mon')
798
+ nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+fri/,'fri sat sun')
799
+
800
+ # Note: next weekend including monday will now fail. Need to make constructors find "next sat sun mon"
801
+ # "next weekend" --> next weekend
802
+ 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
+
804
+ # "next weekend including mon" --> next sat sun mon
805
+ nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+mon/,'next sat sun mon')
806
+ nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+fri/,'next fri sat sun')
807
+
808
+ # "every weekend" --> every sat sun
809
+ 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
+
811
+ # "weekend" --> sat sun !!! catch all
812
+ nsub!(/weekend/,'sat sun')
813
+
814
+ # "mon through wed" -- > mon tue wed
815
+ # CATCH ALL FOR SPANS, TRY NOT TO USE THIS
816
+ nsub!(/#{DAY_OF_WEEK}\s+(?:through|to|until)\s+#{DAY_OF_WEEK}/) do |m1,m2|
817
+ index1 = ZDate.days_of_week.index(m1)
818
+ index2 = ZDate.days_of_week.index(m2)
819
+ i = index1
820
+ ret_string = ''
821
+ if index2 > index1
822
+ while i <= index2
823
+ ret_string << ZDate.days_of_week[i] + ' '
824
+ i += 1
825
+ end
826
+ elsif index2 < index1
827
+ begin
828
+ ret_string << ZDate.days_of_week[i] + ' '
829
+ i = (i + 1) % 7
830
+ end while i != index2 + 1 # wrap until it hits index2
831
+ else
832
+ # indices are the same, one week event
833
+ 8.times do
834
+ ret_string << ZDate.days_of_week[i] + ' '
835
+ i = (i + 1) % 7
836
+ end
837
+ end
838
+ ret_string
839
+ end
840
+
841
+ # "every day" --> repeats daily
842
+ nsub!(/\b(repeat(?:s|ing)?|every|each)\s+da(ily|y)\b/,'repeats daily')
843
+
844
+ # "every other week starting this|next fri" --> every other friday starting this friday
845
+ 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
+
847
+ # "every other|3rd friday starting this|next week" --> every other|3rd friday starting this|next friday
848
+ 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
+
850
+ # "repeats monthly on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday
851
+ # "repeats every other month on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday
852
+ # "repeats every three months on the 1st and 2nd friday" --> repeats threemonthly 1st friday 2nd friday
853
+ 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 + " " }
854
+ 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 + " " }
855
+ 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 + " " }
856
+ 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
+ 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
+ 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
+
860
+ # "repeats monthly on the 1st friday" --> repeats monthly 1st friday
861
+ # "repeats monthly on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday
862
+ # "repeats every other month on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday
863
+ 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/,'') + " "}
864
+ 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/,'') + " "}
865
+ 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/,'') + " "}
866
+ 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
+ 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
+ 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
+
870
+ # "repeats monthly on the 1st friday saturday" --> repeats monthly 1st friday 1st saturday
871
+ 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
+ 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 + " ") + " "}
873
+ 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 + " ") + " "}
874
+ 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
+ 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
+ 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
+
878
+ # "21st of each month" --> repeats monthly 21st
879
+ # "on the 21st, 22nd and 25th of each month" --> repeats monthly 21st 22nd 25th
880
+ 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
+ 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
+ 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
+
884
+ # "repeats each month on the 22nd" --> repeats monthly 22nd
885
+ # "repeats monthly on the 22nd 23rd and 24th" --> repeats monthly 22nd 23rd 24th
886
+ # 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.
887
+ nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
888
+ nsub!(/(?:repeats\s+)(?:(?:each|every|all)\s+)?\bmonth(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
889
+ #nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?\bmonth(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
890
+ nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?\bmonthly\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
891
+ nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)(?:other|2n?d?)\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " " }
892
+ nsub!(/(?:repeats\s+)(?:(?:each|every|all)\s+)?(?:other|2n?d?)\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
893
+ #nsub!(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?(?:other|2n?d?)\s+month(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "}
894
+ 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
+ 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
+ #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
+
898
+ # "on day 4 of every month" --> repeats monthly 4
899
+ # "on days 4 9 and 14 of every month" --> repeats monthly 4 9 14
900
+ 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
+ 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
+ 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
+
904
+ # "every 22nd of the month" --> repeats monthly 22
905
+ # "every 22nd 23rd and 25th of the month" --> repeats monthly 22 23 25
906
+ 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
+ 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
+
909
+ # "every 1st and 2nd fri of the month" --> repeats monthly 1st fri 2nd fri
910
+ 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
+ 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
+
913
+ # "every 1st friday of the month" --> repeats monthly 1st friday
914
+ # "every 1st friday and 2nd tuesday of the month" --> repeats monthly 1st friday 2nd tuesday
915
+ 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
+ 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
+
918
+ # "every 1st fri sat of the month" --> repeats monthly 1st fri 1st sat
919
+ 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
+ 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
+
922
+ # "the 1st and 2nd friday of every month" --> repeats monthly 1st friday 2nd friday
923
+ 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
+ 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
+ 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
+
927
+ # "the 1st friday of every month" --> repeats monthly 1st friday
928
+ # "the 1st friday and the 2nd tuesday of every month" --> repeats monthly 1st friday 2nd tuesday
929
+ 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
+ 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
+ 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
+
933
+ # "the 1st friday saturday of every month" --> repeats monthly 1st friday 1st saturday
934
+ 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
+ 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
+ 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
+
938
+ # "repeats on the 1st and second friday of the month" --> repeats monthly 1st friday 2nd friday
939
+ 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
+ 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
+ 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
+
943
+ # "repeats on the 1st friday of the month --> repeats monthly 1st friday
944
+ # "repeats on the 1st friday and second tuesday of the month" --> repeats monthly 1st friday 2nd tuesday
945
+ 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
+ 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
+ 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
+
949
+ # "repeats on the 1st friday saturday of the month" --> repeats monthly 1st friday 1st saturday
950
+ 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
+ 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
+ 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
+
954
+ # "repeats each month" --> every month
955
+ nsub!(/(repeats\s+)?(each|every)\s+\bmonth(ly)?/,'every month ')
956
+ nsub!(/all\s+months/,'every month')
957
+
958
+ # "repeats every other month" --> every other month
959
+ nsub!(/(repeats\s+)?(each|every)\s+(other|2n?d?)\s+month(ly)?/,'every other month ')
960
+ nsub!(/(repeats\s+)?bimonthly/,'every other month ') # hyphens have already been replaced in spell check (bi-monthly)
961
+
962
+ # "repeats every three months" --> every third month
963
+ nsub!(/(repeats\s+)?(each|every)\s+3r?d?\s+month/,'every third month ')
964
+ nsub!(/(repeats\s+)?trimonthly/,'every third month ')
965
+
966
+ # All months
967
+ nsub!(/(repeats\s+)?all\s+months/,'every month ')
968
+ nsub!(/(repeats\s+)?all\s+other\+months/, 'every other month ')
969
+
970
+ # All month
971
+ nsub!(/all\s+month/, 'this month ')
972
+ nsub!(/all\s+next\s+month/, 'next month ')
973
+
974
+ # "repeats 2nd mon" --> repeats monthly 2nd mon
975
+ # "repeats 2nd mon, 3rd fri, and the last sunday" --> repeats monthly 2nd mon 3rd fri 5th sun
976
+ 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
+
978
+ # "starting at x, ending at y" --> from x to y
979
+ 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
+
981
+ # "the x through the y"
982
+ nsub!(/^(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_WITH_SUFFIX}$/,'\1 through \2 ')
983
+
984
+ # "x week(s) away" --> x week(s) from now
985
+ nsub!(/([0-9]+)\s+(day|week|month)s?\s+away/,'\1 \2s from now')
986
+
987
+ # "x days from now" --> "x days from now"
988
+ # "in 2 weeks|days|months" --> 2 days|weeks|months from now"
989
+ nsub!(/\b(an?|[0-9]+)\s+(day|week|month)s?\s+(?:from\s+now|away)/, '\1 \2 from now')
990
+ nsub!(/in\s+(a|[0-9]+)\s+(week|day|month)s?/, '\1 \2 from now')
991
+
992
+ # "x minutes|hours from now" --> "in x hours|minutes"
993
+ # "in x hour(s)" --> 11/20/07 at 22:00
994
+ # REDONE, no more calculations
995
+ # "x minutes|hours from now" --> "x hours|minutes from now"
996
+ # "in x hours|minutes --> x hours|minutes from now"
997
+ nsub!(/\b(an?|[0-9]+)\s+(hour|minute)s?\s+(?:from\s+now|away)/, '\1 \2 from now')
998
+ nsub!(/in\s+(an?|[0-9]+)\s+(hour|minute)s?/, '\1 \2 from now')
999
+
1000
+ # Now only
1001
+ nsub!(/^(?:\s+)?(?:right\s+)?now(?:\s+)?$/, '0 minutes from now')
1002
+
1003
+ # "a week/month from yesterday|tomorrow" --> 1 week from yesterday|tomorrow
1004
+ nsub!(/(?:(?:a|1)\s+)?(week|month)\s+from\s+(yesterday|tomorrow)/,'1 \1 from \2')
1005
+
1006
+ # "a week/month from yesterday|tomorrow" --> 1 week from monday
1007
+ nsub!(/(?:(?:a|1)\s+)?(week|month)\s+from\s+#{DAY_OF_WEEK}/,'1 \1 from \2')
1008
+
1009
+ # "every 2|3 days" --> every 2nd|3rd day
1010
+ nsub!(/every\s+(2|3)\s+days?/) {|m1| "every " + m1.to_i.ordinalize + " day"}
1011
+
1012
+ # "the following" --> following
1013
+ nsub!(/the\s+following/,'following')
1014
+
1015
+ # "friday the 12th to sunday the 14th" --> 12th through 14th
1016
+ nsub!(/#{DAY_OF_WEEK}\s+the\s+#{DATE_DD_WITH_SUFFIX}\s+(?:to|through|until)\s+#{DAY_OF_WEEK}\s+the\s+#{DATE_DD_WITH_SUFFIX}/,'\2 through \4')
1017
+
1018
+ # "between 1 and 4" --> from 1 to 4
1019
+ nsub!(/between\s+#{TIME}\s+and\s+#{TIME}/,'from \1 to \2')
1020
+
1021
+ # "on the 3rd sat of this month" --> "3rd sat this month"
1022
+ # "on the 3rd sat and 5th tuesday of this month" --> "3rd sat this month 5th tuesday this month"
1023
+ # "on the 3rd sat and sunday of this month" --> "3rd sat this month 3rd sun this month"
1024
+ # "on the 2nd and 3rd sat of this month" --> "2nd sat this month 3rd sat this month"
1025
+ # This is going to be dicey, I'm going to remove 'the' from the following regexprsns:
1026
+ # The 'the' case will be handled AFTER wrapper substitution at end of this method
1027
+ 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
+ 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
+ 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
+
1031
+ # "on the 3rd sat of next month" --> "3rd sat next month"
1032
+ # "on the 3rd sat and 5th tuesday of next month" --> "3rd sat next month 5th tuesday next month"
1033
+ # "on the 3rd sat and sunday of next month" --> "3rd sat this month 3rd sun next month"
1034
+ # "on the 2nd and 3rd sat of next month" --> "2nd sat this month 3rd sat next month"
1035
+ 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
+ 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
+ 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
+
1039
+ # "on the 3rd sat of nov" --> "3rd sat nov"
1040
+ # "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
+ # "on the 3rd sat and sunday of nov" --> "3rd sat nov 3rd sun nov"
1042
+ # "on the 2nd and 3rd sat of nov" --> "2nd sat nov 3rd sat nov"
1043
+ 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
+ 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
+ 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
+
1047
+ # "on the last day of nov" --> "last day nov"
1048
+ nsub!(/(?:\bon\s+)?(?:the\s+)?last\s+day\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/,'last day \1')
1049
+ # "on the 1st|last day of this|the month" --> "1st|last day this month"
1050
+ nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?(?:this|the)?(?:\s+)?month/,'\1 day this month')
1051
+ # "on the 1st|last day of next month" --> "1st|last day next month"
1052
+ nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?next\s+month/,'\1 day next month')
1053
+
1054
+ # "every other weekend" --> every other sat sun
1055
+ nsub!(/every\s+other\s+weekend/,'every other sat sun')
1056
+
1057
+ # "this week on mon "--> this mon
1058
+ nsub!(/this\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/,'this \1')
1059
+ # "mon of this week " --> this mon
1060
+ nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?this\s+week/,'this \1')
1061
+
1062
+ # "next week on mon "--> next mon
1063
+ nsub!(/next\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/,'next \1')
1064
+ # "mon of next week " --> next mon
1065
+ nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?next\s+week/,'next \1')
1066
+
1067
+ # Ordinal this month:
1068
+ # this will slip by now
1069
+ # the 23rd of this|the month --> 8/23
1070
+ # this month on the 23rd --> 8/23
1071
+ # REDONE, no date calculations
1072
+ # the 23rd of this|the month --> 23rd this month
1073
+ # this month on the 23rd --> 23rd this month
1074
+ nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:this|the)\s+month/, '\1 this month')
1075
+ nsub!(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 this month')
1076
+
1077
+ # Ordinal next month:
1078
+ # this will slip by now
1079
+ # the 23rd of next month --> 9/23
1080
+ # next month on the 23rd --> 9/23
1081
+ # REDONE no date calculations
1082
+ # the 23rd of next month --> 23rd next month
1083
+ # next month on the 23rd --> 23rd next month
1084
+ nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:next|the\s+following)\s+month/, '\1 next month')
1085
+ nsub!(/(?:next|the\s+following)\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 next month')
1086
+
1087
+ # "for the next 3 days|weeks|months" --> for 3 days|weeks|months
1088
+ nsub!(/for\s+(?:the\s+)?(?:next|following)\s+(\d+)\s+(days|weeks|months)/,'for \1 \2')
1089
+
1090
+ # This monthname -> monthname
1091
+ nsub!(/this\s+#{MONTH_OF_YEAR}/, '\1')
1092
+
1093
+ # Until monthname -> through monthname
1094
+ # through shouldn't be included here; through and until mean different things, need to fix wrapper terminology
1095
+ # "until june --> through june"
1096
+ nsub!(/(?:through|until)\s+(?:this\s+)?#{MONTH_OF_YEAR}\s+(?:$|\D)/, 'through \1')
1097
+
1098
+ # the week of 1/2 -> week of 1/2
1099
+ nsub!(/(the\s+)?week\s+(of|starting)\s+(the\s+)?/, 'week of ')
1100
+
1101
+ # the week ending 1/2 -> week through 1/2
1102
+ nsub!(/(the\s+)?week\s+(?:ending)\s+/, 'week through ')
1103
+
1104
+ # clean up wrapper terminology
1105
+ # This should always be at end of pre-process
1106
+ nsub!(/(begin(s|ning)?|start(s|ing)?)(\s+(at|on))?/,'start')
1107
+ nsub!(/(\bend(s|ing)?|through|until)(\s+(at|on))?/,'through')
1108
+ nsub!(/start\s+(?:(?:this|in)\s+)?#{MONTH_OF_YEAR}/,'start \1')
1109
+
1110
+ # '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
+ # Do these actually do ANYTHING anymore?
1112
+ # "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}/
1115
+ 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
+ else
1117
+ 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
+ end
1119
+ end
1120
+
1121
+ # "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}/
1124
+ 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
1126
+ 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
+ end
1128
+ end
1129
+
1130
+ # "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}/
1133
+ 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
+ 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') }
1136
+ end
1137
+ end
1138
+
1139
+ nsub!(/from\s+now\s+(through|to|until)/,'now through')
1140
+ end
1141
+ end
1142
+ end
1143
+