nickel 0.0.6 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/License.txt +3 -1
- data/README.md +110 -0
- data/Rakefile +10 -18
- data/bin/run_specs.sh +14 -0
- data/lib/nickel.rb +4 -23
- data/lib/nickel/construct.rb +25 -28
- data/lib/nickel/construct_finder.rb +251 -244
- data/lib/nickel/construct_interpreter.rb +68 -69
- data/lib/nickel/nlp.rb +67 -31
- data/lib/nickel/{query.rb → nlp_query.rb} +160 -348
- data/lib/nickel/{query_constants.rb → nlp_query_constants.rb} +2 -6
- data/lib/nickel/occurrence.rb +48 -67
- data/lib/nickel/version.rb +3 -0
- data/lib/nickel/zdate.rb +244 -162
- data/lib/nickel/ztime.rb +152 -72
- data/nickel.gemspec +31 -38
- data/spec/lib/nickel/nlp_spec.rb +14 -0
- data/spec/lib/nickel/occurrence_spec.rb +40 -0
- data/spec/lib/nickel/zdate_spec.rb +94 -0
- data/spec/lib/nickel/ztime_spec.rb +331 -0
- data/spec/lib/nickel_spec.rb +1859 -0
- data/spec/spec_helper.rb +22 -0
- metadata +124 -35
- data/History.txt +0 -11
- data/README.rdoc +0 -69
- data/lib/nickel/instance_from_hash.rb +0 -13
- data/lib/nickel/ruby_ext/calling_method.rb +0 -10
- data/lib/nickel/ruby_ext/to_s2.rb +0 -12
- data/spec/nickel_spec.rb +0 -165
- data/test/compare.rb +0 -109
- data/test/nlp_test.rb +0 -813
- data/test/nlp_tests_helper.rb +0 -55
- data/test/zdate_test.rb +0 -44
- data/test/ztime_test.rb +0 -429
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
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, :
|
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
|
-
|
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
|
-
|
35
|
-
@occurrences.
|
36
|
-
@
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
107
|
+
end
|
72
108
|
end
|
73
109
|
|
@@ -1,25 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
7
|
+
class NLPQuery
|
8
8
|
include NLPQueryConstants
|
9
|
-
|
10
|
-
|
11
|
-
|
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 =
|
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
|
-
|
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 =
|
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 =
|
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 <<
|
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
|
-
|
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 =
|
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(' ')) !=
|
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
|
-
|
406
|
-
|
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
|
1114
|
-
if
|
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
|
1123
|
-
if
|
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
|
1132
|
-
if
|
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(/#{
|
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
|