From 213cd34fd033744e00b9e3a6fb7497bdf5c3d897 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sat, 22 Nov 2014 21:59:06 +0000 Subject: [PATCH 01/10] Moved all punctuation substitutions into the same method --- lib/nickel/nlp_query.rb | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index 5f0dced..b438e0c 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -20,19 +20,16 @@ def standardize end def query_formatting - query_str.gsub!(/\n/, '') query_str.downcase! remove_unused_punctuation - replace_backslashes run_spell_check remove_unnecessary_words standardize_days standardize_months standardize_numbers standardize_am_pm - replace_hyphens insert_repeats_before_words_indicating_recurrence_lame - insert_space_at_end_of_string_lame + query_str.concat(' ') @after_formatting = query_str.dup # save current state end @@ -70,14 +67,13 @@ def query_pre_processing end def remove_unused_punctuation + nsub!(/\n/, '') nsub!(/,/, ' ') nsub!(/\./, '') nsub!(/;/, '') nsub!(/['`]/, '') - end - - def replace_backslashes nsub!(/\\/, '/') + nsub!(/--?/, ' through ') end def run_spell_check @@ -385,10 +381,6 @@ def standardize_am_pm nsub!(/\s+pm\b/, 'pm') # removes any spaces before pm, shouldn't I check for preceeding digits? end - def replace_hyphens - nsub!(/--?/, ' through ') - end - def insert_repeats_before_words_indicating_recurrence_lame comps = query_str.split (daily_index = comps.index('daily')) && comps[daily_index - 1] != 'repeats' && comps[daily_index] = 'repeats daily' @@ -399,11 +391,6 @@ def insert_repeats_before_words_indicating_recurrence_lame end end - def insert_space_at_end_of_string_lame - # nsub!(/(.+)/,'\1 ') # I don't really want to be notified about this - query_str.gsub!(/(.+)/, '\1 ') - end - def to_s query_str end From 5f3cf51ea1ffd8c86442366accf9b9792decf51e Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sat, 22 Nov 2014 22:01:18 +0000 Subject: [PATCH 02/10] Fixed up insert repeats before words to use regexp --- lib/nickel/nlp_query.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index b438e0c..3ab2015 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -382,13 +382,9 @@ def standardize_am_pm end def insert_repeats_before_words_indicating_recurrence_lame - comps = query_str.split - (daily_index = comps.index('daily')) && comps[daily_index - 1] != 'repeats' && comps[daily_index] = 'repeats daily' - (weekly_index = comps.index('weekly')) && comps[weekly_index - 1] != 'repeats' && comps[weekly_index] = 'repeats weekly' - (monthly_index = comps.index('monthly')) && comps[monthly_index - 1] != 'repeats' && comps[monthly_index] = 'repeats monthly' - if (rejoin = comps.join(' ')) != query_str - nsub!(/.+/, rejoin) - end + nsub!(/(? Date: Sat, 22 Nov 2014 22:19:50 +0000 Subject: [PATCH 03/10] Moved a lot of nsub calls from nlp_query out to new substitution classes Should be easier to maintain and internationalize --- lib/nickel/nlp_query.rb | 333 +------------------- lib/nickel/substitutions/formatting.rb | 65 ++++ lib/nickel/substitutions/standardization.rb | 270 ++++++++++++++++ lib/nickel/substitutor.rb | 23 ++ 4 files changed, 362 insertions(+), 329 deletions(-) create mode 100644 lib/nickel/substitutions/formatting.rb create mode 100644 lib/nickel/substitutions/standardization.rb create mode 100644 lib/nickel/substitutor.rb diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index 3ab2015..89a631c 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -1,6 +1,8 @@ require 'nickel/zdate' require 'nickel/ztime' require 'nickel/nlp_query_constants' +require 'nickel/substitutions/formatting' +require 'nickel/substitutions/standardization' module Nickel class NLPQuery @@ -21,14 +23,8 @@ def standardize def query_formatting query_str.downcase! - remove_unused_punctuation - run_spell_check - remove_unnecessary_words - standardize_days - standardize_months - standardize_numbers - standardize_am_pm - insert_repeats_before_words_indicating_recurrence_lame + Substitutions::Formatting.apply(self) + Substitutions::Standardization.apply(self) query_str.concat(' ') @after_formatting = query_str.dup # save current state end @@ -66,327 +62,6 @@ def query_pre_processing standardize_input end - def remove_unused_punctuation - nsub!(/\n/, '') - nsub!(/,/, ' ') - nsub!(/\./, '') - nsub!(/;/, '') - nsub!(/['`]/, '') - nsub!(/\\/, '/') - nsub!(/--?/, ' through ') - end - - def run_spell_check - nsub!(/tomm?orr?ow|romorrow/, 'tomorrow') - nsub!(/weeknd/, 'weekend') - nsub!(/weekends/, 'every sat sun') - nsub!(/everyother/, 'every other') - nsub!(/weak/, 'week') - nsub!(/everyweek/, 'every week') - nsub!(/everymonth/, 'every month') - nsub!(/c?h[oa]nn?[aui][ck][ck]?[ua]h?/, 'hannukkah') - nsub!(/frist/, '1st') - nsub!(/eveyr|evrey/, 'every') - nsub!(/fridya|friady|fridy/, 'friday') - nsub!(/thurdsday/, 'thursday') - nsub!(/x-?mas/, 'christmas') - nsub!(/st\s+(patrick|patty|pat)s?(\s+day)?/, 'st patricks day') - nsub!(/frouth/, 'fourth') - nsub!(/\btill\b/, 'through') - nsub!(/\bthru\b|\bthrouh\b|\bthough\b|\bthrew\b|\bthrow\b|\bthroug\b|\bthuogh\b/, 'through') - nsub!(/weekdays|every\s+weekday/, 'every monday through friday') - nsub!(/\bevery?day\b/, 'every day') - nsub!(/eigth/, 'eighth') - nsub!(/bi[-\s]monthly/, 'bimonthly') - nsub!(/tri[-\s]monthly/, 'trimonthly') - end - - def remove_unnecessary_words - nsub!(/coming/, '') - nsub!(/o'?clock/, '') - nsub!(/\btom\b/, 'tomorrow') - nsub!(/\s*in\s+(the\s+)?(morning|am)/, ' am') - nsub!(/\s*in\s+(the\s+)?(afternoon|pm|evenn?ing)/, ' pm') - nsub!(/\s*at\s+night/, 'pm') - nsub!(/(after\s*)?noon(ish)?/, '12:00pm') - nsub!(/\bmi(dn|nd)ight\b/, '12:00am') - nsub!(/final/, 'last') - nsub!(/recur(s|r?ing)?/, 'repeats') - nsub!(/\beach\b/, 'every') - nsub!(/running\s+(until|through)/, 'through') - nsub!(/runn?(s|ing)|go(ing|e?s)/, 'for') - nsub!(/next\s+occ?urr?[ae]nce(\s+is)?/, 'start') - nsub!(/next\s+date(\s+it)?(\s+occ?urr?s)?(\s+is)?/, 'start') - nsub!(/forever/, 'repeats daily') - nsub!(/\bany(?:\s*)day\b/, 'every day') - nsub!(/^anytime$/, 'every day') # user entered anytime by itself, not 'dayname anytime', caught next - nsub!(/any(\s)?time|whenever/, 'all day') - end - - def standardize_days - nsub!(/mondays/, 'every mon') - nsub!(/monday/, 'mon') - nsub!(/tuesdays/, 'every tue') - nsub!(/tuesadys/, 'every tue') - nsub!(/tuesday/, 'tue') - nsub!(/tuesady/, 'tue') - nsub!(/wednesdays/, 'every wed') - nsub!(/wednesday/, 'wed') - nsub!(/thursdays/, 'every thu') - nsub!(/thurdsays/, 'every thu') - nsub!(/thursadys/, 'every thu') - nsub!(/thursday/, 'thu') - nsub!(/thurdsay/, 'thu') - nsub!(/thursady/, 'thu') - nsub!(/\bthurd?\b/, 'thu') - nsub!(/\bthurs?d?\b/, 'thu') - nsub!(/fridays/, 'every fri') - nsub!(/firdays/, 'every fri') - nsub!(/friday/, 'fri') - nsub!(/firday/, 'fri') - nsub!(/saturdays/, 'every sat') - nsub!(/saturday/, 'sat') - nsub!(/sundays/, 'every sun') - nsub!(/sunday/, 'sun') - end - - def standardize_months - nsub!(/january/, 'jan') - nsub!(/february/, 'feb') - nsub!(/febr/, 'feb') - nsub!(/march/, 'mar') - nsub!(/april/, 'apr') - nsub!(/may/, 'may') - nsub!(/june/, 'jun') - nsub!(/july/, 'jul') - nsub!(/august/, 'aug') - nsub!(/september/, 'sep') - nsub!(/sept/, 'sep') - nsub!(/october/, 'oct') - nsub!(/november/, 'nov') - nsub!(/novermber/, 'nov') - nsub!(/novem/, 'nov') - nsub!(/decemb?e?r?/, 'dec') - end - - def standardize_numbers - nsub!(/\bone\s*-?\s*hundred\b/, '100') - nsub!(/\bone\s*-?\s*hundredth\b/, '100th') - nsub!(/\bninety\s*-?\s*nine\b/, '99') - nsub!(/\bninety\s*-?\s*ninth\b/, '99th') - nsub!(/\bninety\s*-?\s*eight\b/, '98') - nsub!(/\bninety\s*-?\s*eighth\b/, '98th') - nsub!(/\bninety\s*-?\s*seven\b/, '97') - nsub!(/\bninety\s*-?\s*seventh\b/, '97th') - nsub!(/\bninety\s*-?\s*six\b/, '96') - nsub!(/\bninety\s*-?\s*sixth\b/, '96th') - nsub!(/\bninety\s*-?\s*five\b/, '95') - nsub!(/\bninety\s*-?\s*fifth\b/, '95th') - nsub!(/\bninety\s*-?\s*four\b/, '94') - nsub!(/\bninety\s*-?\s*fourth\b/, '94th') - nsub!(/\bninety\s*-?\s*three\b/, '93') - nsub!(/\bninety\s*-?\s*third\b/, '93rd') - nsub!(/\bninety\s*-?\s*two\b/, '92') - nsub!(/\bninety\s*-?\s*second\b/, '92nd') - nsub!(/\bninety\s*-?\s*one\b/, '91') - nsub!(/\bninety\s*-?\s*first\b/, '91st') - nsub!(/\bninety\b/, '90') - nsub!(/\bninetieth\b/, '90th') - nsub!(/\beighty\s*-?\s*nine\b/, '89') - nsub!(/\beighty\s*-?\s*ninth\b/, '89th') - nsub!(/\beighty\s*-?\s*eight\b/, '88') - nsub!(/\beighty\s*-?\s*eighth\b/, '88th') - nsub!(/\beighty\s*-?\s*seven\b/, '87') - nsub!(/\beighty\s*-?\s*seventh\b/, '87th') - nsub!(/\beighty\s*-?\s*six\b/, '86') - nsub!(/\beighty\s*-?\s*sixth\b/, '86th') - nsub!(/\beighty\s*-?\s*five\b/, '85') - nsub!(/\beighty\s*-?\s*fifth\b/, '85th') - nsub!(/\beighty\s*-?\s*four\b/, '84') - nsub!(/\beighty\s*-?\s*fourth\b/, '84th') - nsub!(/\beighty\s*-?\s*three\b/, '83') - nsub!(/\beighty\s*-?\s*third\b/, '83rd') - nsub!(/\beighty\s*-?\s*two\b/, '82') - nsub!(/\beighty\s*-?\s*second\b/, '82nd') - nsub!(/\beighty\s*-?\s*one\b/, '81') - nsub!(/\beighty\s*-?\s*first\b/, '81st') - nsub!(/\beighty\b/, '80') - nsub!(/\beightieth\b/, '80th') - nsub!(/\bseventy\s*-?\s*nine\b/, '79') - nsub!(/\bseventy\s*-?\s*ninth\b/, '79th') - nsub!(/\bseventy\s*-?\s*eight\b/, '78') - nsub!(/\bseventy\s*-?\s*eighth\b/, '78th') - nsub!(/\bseventy\s*-?\s*seven\b/, '77') - nsub!(/\bseventy\s*-?\s*seventh\b/, '77th') - nsub!(/\bseventy\s*-?\s*six\b/, '76') - nsub!(/\bseventy\s*-?\s*sixth\b/, '76th') - nsub!(/\bseventy\s*-?\s*five\b/, '75') - nsub!(/\bseventy\s*-?\s*fifth\b/, '75th') - nsub!(/\bseventy\s*-?\s*four\b/, '74') - nsub!(/\bseventy\s*-?\s*fourth\b/, '74th') - nsub!(/\bseventy\s*-?\s*three\b/, '73') - nsub!(/\bseventy\s*-?\s*third\b/, '73rd') - nsub!(/\bseventy\s*-?\s*two\b/, '72') - nsub!(/\bseventy\s*-?\s*second\b/, '72nd') - nsub!(/\bseventy\s*-?\s*one\b/, '71') - nsub!(/\bseventy\s*-?\s*first\b/, '71st') - nsub!(/\bseventy\b/, '70') - nsub!(/\bseventieth\b/, '70th') - nsub!(/\bsixty\s*-?\s*nine\b/, '69') - nsub!(/\bsixty\s*-?\s*ninth\b/, '69th') - nsub!(/\bsixty\s*-?\s*eight\b/, '68') - nsub!(/\bsixty\s*-?\s*eighth\b/, '68th') - nsub!(/\bsixty\s*-?\s*seven\b/, '67') - nsub!(/\bsixty\s*-?\s*seventh\b/, '67th') - nsub!(/\bsixty\s*-?\s*six\b/, '66') - nsub!(/\bsixty\s*-?\s*sixth\b/, '66th') - nsub!(/\bsixty\s*-?\s*five\b/, '65') - nsub!(/\bsixty\s*-?\s*fifth\b/, '65th') - nsub!(/\bsixty\s*-?\s*four\b/, '64') - nsub!(/\bsixty\s*-?\s*fourth\b/, '64th') - nsub!(/\bsixty\s*-?\s*three\b/, '63') - nsub!(/\bsixty\s*-?\s*third\b/, '63rd') - nsub!(/\bsixty\s*-?\s*two\b/, '62') - nsub!(/\bsixty\s*-?\s*second\b/, '62nd') - nsub!(/\bsixty\s*-?\s*one\b/, '61') - nsub!(/\bsixty\s*-?\s*first\b/, '61st') - nsub!(/\bsixty\b/, '60') - nsub!(/\bsixtieth\b/, '60th') - nsub!(/\bfifty\s*-?\s*nine\b/, '59') - nsub!(/\bfifty\s*-?\s*ninth\b/, '59th') - nsub!(/\bfifty\s*-?\s*eight\b/, '58') - nsub!(/\bfifty\s*-?\s*eighth\b/, '58th') - nsub!(/\bfifty\s*-?\s*seven\b/, '57') - nsub!(/\bfifty\s*-?\s*seventh\b/, '57th') - nsub!(/\bfifty\s*-?\s*six\b/, '56') - nsub!(/\bfifty\s*-?\s*sixth\b/, '56th') - nsub!(/\bfifty\s*-?\s*five\b/, '55') - nsub!(/\bfifty\s*-?\s*fifth\b/, '55th') - nsub!(/\bfifty\s*-?\s*four\b/, '54') - nsub!(/\bfifty\s*-?\s*fourth\b/, '54th') - nsub!(/\bfifty\s*-?\s*three\b/, '53') - nsub!(/\bfifty\s*-?\s*third\b/, '53rd') - nsub!(/\bfifty\s*-?\s*two\b/, '52') - nsub!(/\bfifty\s*-?\s*second\b/, '52nd') - nsub!(/\bfifty\s*-?\s*one\b/, '51') - nsub!(/\bfifty\s*-?\s*first\b/, '51st') - nsub!(/\bfifty\b/, '50') - nsub!(/\bfiftieth\b/, '50th') - nsub!(/\bfourty\s*-?\s*nine\b/, '49') - nsub!(/\bfourty\s*-?\s*ninth\b/, '49th') - nsub!(/\bfourty\s*-?\s*eight\b/, '48') - nsub!(/\bfourty\s*-?\s*eighth\b/, '48th') - nsub!(/\bfourty\s*-?\s*seven\b/, '47') - nsub!(/\bfourty\s*-?\s*seventh\b/, '47th') - nsub!(/\bfourty\s*-?\s*six\b/, '46') - nsub!(/\bfourty\s*-?\s*sixth\b/, '46th') - nsub!(/\bfourty\s*-?\s*five\b/, '45') - nsub!(/\bfourty\s*-?\s*fifth\b/, '45th') - nsub!(/\bfourty\s*-?\s*four\b/, '44') - nsub!(/\bfourty\s*-?\s*fourth\b/, '44th') - nsub!(/\bfourty\s*-?\s*three\b/, '43') - nsub!(/\bfourty\s*-?\s*third\b/, '43rd') - nsub!(/\bfourty\s*-?\s*two\b/, '42') - nsub!(/\bfourty\s*-?\s*second\b/, '42nd') - nsub!(/\bfourty\s*-?\s*one\b/, '41') - nsub!(/\bfourty\s*-?\s*first\b/, '41st') - nsub!(/\bfourty\b/, '40') - nsub!(/\bfourtieth\b/, '40th') - nsub!(/\bthirty\s*-?\s*nine\b/, '39') - nsub!(/\bthirty\s*-?\s*ninth\b/, '39th') - nsub!(/\bthirty\s*-?\s*eight\b/, '38') - nsub!(/\bthirty\s*-?\s*eighth\b/, '38th') - nsub!(/\bthirty\s*-?\s*seven\b/, '37') - nsub!(/\bthirty\s*-?\s*seventh\b/, '37th') - nsub!(/\bthirty\s*-?\s*six\b/, '36') - nsub!(/\bthirty\s*-?\s*sixth\b/, '36th') - nsub!(/\bthirty\s*-?\s*five\b/, '35') - nsub!(/\bthirty\s*-?\s*fifth\b/, '35th') - nsub!(/\bthirty\s*-?\s*four\b/, '34') - nsub!(/\bthirty\s*-?\s*fourth\b/, '34th') - nsub!(/\bthirty\s*-?\s*three\b/, '33') - nsub!(/\bthirty\s*-?\s*third\b/, '33rd') - nsub!(/\bthirty\s*-?\s*two\b/, '32') - nsub!(/\bthirty\s*-?\s*second\b/, '32nd') - nsub!(/\bthirty\s*-?\s*one\b/, '31') - nsub!(/\bthirty\s*-?\s*first\b/, '31st') - nsub!(/\bthirty\b/, '30') - nsub!(/\bthirtieth\b/, '30th') - nsub!(/\btwenty\s*-?\s*nine\b/, '29') - nsub!(/\btwenty\s*-?\s*ninth\b/, '29th') - nsub!(/\btwenty\s*-?\s*eight\b/, '28') - nsub!(/\btwenty\s*-?\s*eighth\b/, '28th') - nsub!(/\btwenty\s*-?\s*seven\b/, '27') - nsub!(/\btwenty\s*-?\s*seventh\b/, '27th') - nsub!(/\btwenty\s*-?\s*six\b/, '26') - nsub!(/\btwenty\s*-?\s*sixth\b/, '26th') - nsub!(/\btwenty\s*-?\s*five\b/, '25') - nsub!(/\btwenty\s*-?\s*fifth\b/, '25th') - nsub!(/\btwenty\s*-?\s*four\b/, '24') - nsub!(/\btwenty\s*-?\s*fourth\b/, '24th') - nsub!(/\btwenty\s*-?\s*three\b/, '23') - nsub!(/\btwenty\s*-?\s*third\b/, '23rd') - nsub!(/\btwenty\s*-?\s*two\b/, '22') - nsub!(/\btwenty\s*-?\s*second\b/, '22nd') - nsub!(/\btwenty\s*-?\s*one\b/, '21') - nsub!(/\btwenty\s*-?\s*first\b/, '21st') - nsub!(/\btwenty\b/, '20') - nsub!(/\btwentieth\b/, '20th') - nsub!(/\bnineteen\b/, '19') - nsub!(/\bnineteenth\b/, '19th') - nsub!(/\beighteen\b/, '18') - nsub!(/\beighteenth\b/, '18th') - nsub!(/\bseventeen\b/, '17') - nsub!(/\bseventeenth\b/, '17th') - nsub!(/\bsixteen\b/, '16') - nsub!(/\bsixteenth\b/, '16th') - nsub!(/\bfifteen\b/, '15') - nsub!(/\bfifteenth\b/, '15th') - nsub!(/\bfourteen\b/, '14') - nsub!(/\bfourteenth\b/, '14th') - nsub!(/\bthirteen/, '13') - nsub!(/\bthirteenth/, '13th') - nsub!(/\btwelve\b/, '12') - nsub!(/\btwelfth\b/, '12th') - nsub!(/\beleven\b/, '11') - nsub!(/\beleventh\b/, '11th') - nsub!(/\bten\b/, '10') - nsub!(/\btenth\b/, '10th') - nsub!(/\bnine\b/, '9') - nsub!(/\bninth\b/, '9th') - nsub!(/\beight\b/, '8') - nsub!(/\beighth\b/, '8th') - nsub!(/\bseven\b/, '7') - nsub!(/\bseventh\b/, '7th') - nsub!(/\bsix\b/, '6') - nsub!(/\bsixth\b/, '6th') - nsub!(/\bfive\b/, '5') - nsub!(/\bfifth\b/, '5th') - nsub!(/\bfour\b/, '4') - nsub!(/\bfourth\b/, '4th') - nsub!(/\bthree\b/, '3') - nsub!(/\bthird\b/, '3rd') - nsub!(/\btwo\b/, '2') - nsub!(/\bsecond\b/, '2nd') - nsub!(/\bone\b/, '1') - nsub!(/\bfirst\b/, '1st') - nsub!(/\bzero\b/, '0') - nsub!(/\bzeroth\b/, '0th') - end - - def standardize_am_pm - nsub!(/([0-9])(?:\s*)a\b/, '\1am') # allows 5a as 5am - nsub!(/([0-9])(?:\s*)p\b/, '\1pm') # allows 5p as 5pm - nsub!(/\s+am\b/, 'am') # removes any spaces before am, shouldn't I check for preceeding digits? - nsub!(/\s+pm\b/, 'pm') # removes any spaces before pm, shouldn't I check for preceeding digits? - end - - def insert_repeats_before_words_indicating_recurrence_lame - nsub!(/(? Date: Sat, 22 Nov 2014 22:27:32 +0000 Subject: [PATCH 04/10] Got rid of spell checking Impossible to maintain --- lib/nickel/substitutions/formatting.rb | 10 ---------- lib/nickel/substitutions/standardization.rb | 10 ---------- spec/lib/nickel_spec.rb | 4 ++-- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/nickel/substitutions/formatting.rb b/lib/nickel/substitutions/formatting.rb index b1acc75..db18491 100644 --- a/lib/nickel/substitutions/formatting.rb +++ b/lib/nickel/substitutions/formatting.rb @@ -16,23 +16,13 @@ class Formatting sub(/--?/, ' through ') # spell check - sub(/tomm?orr?ow|romorrow/, 'tomorrow') - sub(/weeknd/, 'weekend') sub(/weekends/, 'every sat sun') sub(/everyother/, 'every other') - sub(/weak/, 'week') sub(/everyweek/, 'every week') sub(/everymonth/, 'every month') - sub(/frist/, '1st') - sub(/eveyr|evrey/, 'every') - sub(/fridya|friady|fridy/, 'friday') - sub(/thurdsday/, 'thursday') - sub(/frouth/, 'fourth') sub(/\btill\b/, 'through') - sub(/\bthru\b|\bthrouh\b|\bthough\b|\bthrew\b|\bthrow\b|\bthroug\b|\bthuogh\b/, 'through') sub(/weekdays|every\s+weekday/, 'every monday through friday') sub(/\bevery?day\b/, 'every day') - sub(/eigth/, 'eighth') sub(/bi[-\s]monthly/, 'bimonthly') sub(/tri[-\s]monthly/, 'trimonthly') diff --git a/lib/nickel/substitutions/standardization.rb b/lib/nickel/substitutions/standardization.rb index 02bd692..177d5b1 100644 --- a/lib/nickel/substitutions/standardization.rb +++ b/lib/nickel/substitutions/standardization.rb @@ -15,23 +15,14 @@ class Standardization sub(/mondays/, 'every mon') sub(/monday/, 'mon') sub(/tuesdays/, 'every tue') - sub(/tuesadys/, 'every tue') sub(/tuesday/, 'tue') - sub(/tuesady/, 'tue') sub(/wednesdays/, 'every wed') sub(/wednesday/, 'wed') sub(/thursdays/, 'every thu') - sub(/thurdsays/, 'every thu') - sub(/thursadys/, 'every thu') sub(/thursday/, 'thu') - sub(/thurdsay/, 'thu') - sub(/thursady/, 'thu') - sub(/\bthurd?\b/, 'thu') sub(/\bthurs?d?\b/, 'thu') sub(/fridays/, 'every fri') - sub(/firdays/, 'every fri') sub(/friday/, 'fri') - sub(/firday/, 'fri') sub(/saturdays/, 'every sat') sub(/saturday/, 'sat') sub(/sundays/, 'every sun') @@ -51,7 +42,6 @@ class Standardization sub(/sept/, 'sep') sub(/october/, 'oct') sub(/november/, 'nov') - sub(/novermber/, 'nov') sub(/novem/, 'nov') sub(/decemb?e?r?/, 'dec') diff --git a/spec/lib/nickel_spec.rb b/spec/lib/nickel_spec.rb index 8ccf6fd..90f1b6b 100644 --- a/spec/lib/nickel_spec.rb +++ b/spec/lib/nickel_spec.rb @@ -825,8 +825,8 @@ end end - context "when the query is 'the twentyeigth'" do - let(:query) { 'the twentyeigth' } + context "when the query is 'the twentyeighth'" do + let(:query) { 'the twentyeighth' } let(:run_date) { Time.local(2010, 3, 20) } describe '#occurrences' do From a0f67fbd8fdc2eb676023f66c55c441bfd3b0419 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sat, 22 Nov 2014 22:33:07 +0000 Subject: [PATCH 05/10] Pulled a few more substitutions out of nlp query, before I get into the block-based substitutions --- lib/nickel/nlp_query.rb | 8 -------- lib/nickel/substitutions/standardization.rb | 11 +++++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index 89a631c..29f5e23 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -71,14 +71,6 @@ def to_s attr_accessor :query_str def standardize_input - nsub!(/last\s+#{DAY_OF_WEEK}/, '5th \1') # last dayname => 5th dayname - nsub!(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day - nsub!(/^(through|until)/, 'today through') # ^through => today through - nsub!(/every\s*(night|morning)/, 'every day') - nsub!(/tonight/, 'today') - nsub!(/this(?:\s*)morning/, 'today') - nsub!(/before\s+12pm/, '6am to 12pm') # arbitrary - # Handle 'THE' Cases # Attempt to pick out where a user entered 'the' when they really mean 'every'. # For example, diff --git a/lib/nickel/substitutions/standardization.rb b/lib/nickel/substitutions/standardization.rb index 177d5b1..ab938e0 100644 --- a/lib/nickel/substitutions/standardization.rb +++ b/lib/nickel/substitutions/standardization.rb @@ -1,9 +1,11 @@ require 'nickel/substitutor' +require 'nickel/nlp_query_constants' module Nickel module Substitutions class Standardization extend Substitutor + include NLPQueryConstants substitutions do # holidays @@ -254,6 +256,15 @@ class Standardization sub(/([0-9])(?:\s*)p\b/, '\1pm') # allows 5p as 5pm sub(/\s+am\b/, 'am') # removes any spaces before am, shouldn't I check for preceeding digits? sub(/\s+pm\b/, 'pm') # removes any spaces before pm, shouldn't I check for preceeding digits? + + # miscellaneous + sub(/last\s+#{DAY_OF_WEEK.source}/, '5th \1') # last dayname => 5th dayname + sub(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day + sub(/^(through|until)/, 'today through') # ^through => today through + sub(/every\s*(night|morning)/, 'every day') + sub(/tonight/, 'today') + sub(/this(?:\s*)morning/, 'today') + sub(/before\s+12pm/, '6am to 12pm') # arbitrary end end end From 79e86437b87e906c62c9debf257288ac7faefe5d Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sun, 23 Nov 2014 08:02:33 +0000 Subject: [PATCH 06/10] Moved most pre processing substitutions out to a pre processing substitutor --- lib/nickel/nlp_query.rb | 556 +---------------- lib/nickel/substitutions/pre_processing.rb | 654 ++++++++++++++++++++ lib/nickel/substitutions/standardization.rb | 9 - 3 files changed, 687 insertions(+), 532 deletions(-) create mode 100644 lib/nickel/substitutions/pre_processing.rb diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index 29f5e23..f314a85 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -3,6 +3,7 @@ require 'nickel/nlp_query_constants' require 'nickel/substitutions/formatting' require 'nickel/substitutions/standardization' +require 'nickel/substitutions/pre_processing' module Nickel class NLPQuery @@ -59,547 +60,56 @@ def nsub!(*args) end def query_pre_processing - standardize_input - end - - def to_s - query_str - end - - private - - attr_accessor :query_str - - def standardize_input - # Handle 'THE' Cases - # Attempt to pick out where a user entered 'the' when they really mean 'every'. - # For example, - # The first of every month and the 22nd of THE month => repeats monthly first xxxxxx repeats monthly 22nd xxxxxxx - nsub!(/(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:of\s+)?(?:every|each)\s+month((?:.*)of\s+the\s+month(?:.*))/) do |m1, m2| - ret_str = ' repeats monthly ' + m1 - ret_str << m2.gsub(/(?:and\s+)?(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+of\s+the\s+month/, ' repeats monthly \1 ') - end - - # Every first sunday of the month and the last tuesday => repeats monthly first sunday xxxxxxxxx repeats monthly last tuesday xxxxxxx - 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| - ret_str = ' repeats monthly ' + m1 + ' ' + m2 + ' ' - 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 ') - end - - # The x through the y of oct z => 10/x/z through 10/y/z - 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| - (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 + '/' + m4 - end - - # The x through the y of oct => 10/x through 10/y - nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:through|to|until)\s+(?:the\s+)#{DATE_DD}\s(?:of\s+)?#{MONTH_OF_YEAR}/) do |m1, m2, m3| - (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 - end - - # January 1 - February 15 - 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| - (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)/, '') - end - - # Tuesday, january 1 - friday, february 15, 2013 - 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| - if m7.nil? - (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 - else - (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 - end - end - - # Tuesday, january 1 2013 - friday, february 15, 2013 - 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| - (ZDate.months_of_year.index(m2) + 1).to_s + '/' + m3 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m6) + 1).to_s + '/' + m7 + '/' + m8 - end - - # Monthname x through y - 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| - if m3 # $3 holds first occurrence of year - (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m3 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m3 - elsif m5 # $5 holds second occurrence of year - (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m5 - else - (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 - end - end - - # Monthname x through monthname y - # Jan 14 through jan 18 => 1/14 through 1/18 - # Oct 2 until oct 5 - 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| - if m5 - (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + '/' + m5 + ' ' - else - (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + ' ' - end - end - - # Mnday the 23rd, tuesday the 24th and wed the 25th of oct => 11/23 11/24 11/25 - 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| - month_str = (ZDate.months_of_year.index(m2) + 1).to_s - if m3 - m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3) - else - m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1') - end - end - - # the 23rd and 24th of october => 11/23 11/24 - # the 23rd, 24th, and 25th of october => 11/23 11/24 11/25 - # the 23rd, 24th, and 25th of october 2010 => 11/23/2010 11/24/2010 11/25/2010 - # monday and tuesday, the 23rd and 24th of july => 7/23 7/24 - 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| - month_str = (ZDate.months_of_year.index(m2) + 1).to_s - if m3 - m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3) - else - m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1') - end - end - - # Match date with year first. - # Don't allow mixing of suffixes, e.g. "dec 3rd 2008 at 4 and dec 5 2008 9 to 5" - # Dec 2nd, 3rd, and 5th 2008 => 12/2/2008 12/2/2008 12/5/2008 - # Mon nov 23rd 08 - # Dec 2, 3, 5, 2008 => 12/2/2008 12/3/2008 12/5/2008 - 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| - month_str = (ZDate.months_of_year.index(m1) + 1).to_s - m2.gsub(/\b(and|the)\b/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/, month_str + '/\1/' + m3) - end - - 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| - month_str = (ZDate.months_of_year.index(m1) + 1).to_s - m2.gsub(/\b(and|the)\b/, '').gsub(/#{DATE_DD_WITHOUT_SUFFIX}/, month_str + '/\1/' + m3) - end - - # Dec 2nd, 3rd, and 4th => 12/2, 12/3, 12/4 - # 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 - 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| - month_str = (ZDate.months_of_year.index(m1) + 1).to_s - m2.gsub(/(and|the)/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) { month_str + '/' + Regexp.last_match(1) } # last match is from the nested match! - end - - # Apr 29, 5 - 8pm - nsub!(/#{MONTH_OF_YEAR}(?:\s)+#{DATE_DD_WITHOUT_SUFFIX}(?:,)?(?:\s)+(#{TIME} through #{TIME})/) do |m1, m2, m3| - month_str = (ZDate.months_of_year.index(m1) + 1).to_s - "#{month_str}/#{m2} #{m3}" - end - - # jan 4 2-3 has to be modified, but - # jan 24 through jan 26 cannot! - # not real sure what this one is doing - # "dec 2, 3, and 4" --> 12/2, 12/3, 12/4 - # "mon, tue, wed, dec 2, 3, and 4" --> 12/2, 12/3, 12/4 - 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 ') } - 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| - month_str = (ZDate.months_of_year.index(m1) + 1).to_s - m2.gsub(/(and|the)/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX}/) { month_str + '/' + Regexp.last_match(1) } # last match is from nested match - end - - # "monday 12/6" --> 12/6 - nsub!(/#{DAY_OF_WEEK_NB}\s+(#{DATE_MM_SLASH_DD})/, '\1') - - # "next friday to|until|through the following tuesday" --> 10/12 through 10/16 - # "next friday through sunday" --> 10/12 through 10/14 - # "next friday and the following sunday" --> 11/16 11/18 - # we are not going to do date calculations here anymore, so instead: - # next friday to|until|through the following tuesday" --> next friday through tuesday - # next friday and the following sunday --> next friday and sunday - nsub!(/next\s+#{DAY_OF_WEEK}\s+(to|until|through|and)\s+(?:the\s+)?(?:following|next)?(?:\s*)#{DAY_OF_WEEK}/) do |m1, m2, m3| - connector = (m2 =~ /and/ ? ' ' : ' through ') - 'next ' + m1 + connector + m3 - end - - # "this friday to|until|through the following tuesday" --> 10/5 through 10/9 - # "this friday through following sunday" --> 10/5 through 10/7 - # "this friday and the following monday" --> 11/9 11/12 - # No longer performing date calculation - # this friday and the following monday --> fri mon - # this friday through the following tuesday --> fri through tues - nsub!(/(?:this\s+)?#{DAY_OF_WEEK}\s+(to|until|through|and)\s+(?:the\s+)?(?:this|following)(?:\s*)#{DAY_OF_WEEK}/) do |m1, m2, m3| - connector = (m2 =~ /and/ ? ' ' : ' through ') - m1 + connector + m3 - end - - # "the wed after next" --> 2 wed from today - nsub!(/(?:the\s+)?#{DAY_OF_WEEK}\s+(?:after|following)\s+(?:the\s+)?next/, '2 \1 from today') - - # "mon and tue" --> mon tue - nsub!(/(#{DAY_OF_WEEK}\s+and\s+#{DAY_OF_WEEK})(?:\s+and)?/, '\2 \3') - - # "mon wed every week" --> every mon wed - nsub!(/((#{DAY_OF_WEEK}(?:\s*)){1,7})(?:of\s+)?(?:every|each)(\s+other)?\s+week/, 'every \4 \1') - - # "every 2|3 weeks" --> every 2nd|3rd week - nsub!(/(?:repeats\s+)?every\s+(2|3)\s+weeks/) { |m1| 'every ' + m1.to_i.ordinalize + ' week' } - - # "every week on mon tue fri" --> every mon tue fri - nsub!(/(?:repeats\s+)?every\s+(?:(other|3rd|2nd)\s+)?weeks?\s+(?:\bon\s+)?((?:#{DAY_OF_WEEK_NB}\s+){1,7})/, 'every \1 \2') - - # "every mon and every tue and.... " --> every mon tue ... - 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') - - # monday, wednesday, and friday next week at 8 - nsub!(/((?:#{DAY_OF_WEEK_NB}\s+(?:and\s+)?){1,7})(?:of\s+)?(this|next)\s+week/, '\2 \1') - - # "every day this|next week" --> returns monday through friday of the closest week, kinda stupid - # doesn't do that anymore, no date calculations allowed here, instead just formats it nicely for construct finders --> every day this|next week - nsub!(/every\s+day\s+(?:of\s+)?(this|the|next)\s+week\b./) { |m1| m1 == 'next' ? 'every day next week' : 'every day this week' } - - # "every day for the next week" --> "every day this week" - nsub!(/every\s+day\s+for\s+(the\s+)?(next|this)\s+week/, 'every day this week') - - # "this weekend" --> sat sun - 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') - - # "this weekend including mon" --> sat sun mon - nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+mon/, 'sat sun mon') - nsub!(/sat\s+sun\s+(and|includ(es?|ing))\s+fri/, 'fri sat sun') - - # Note: next weekend including monday will now fail. Need to make constructors find "next sat sun mon" - # "next weekend" --> next weekend - 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') - - # "next weekend including mon" --> next sat sun mon - nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+mon/, 'next sat sun mon') - nsub!(/next\s+weekend\s+(and|includ(es?|ing))\s+fri/, 'next fri sat sun') - - # "every weekend" --> every sat sun - 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 - - # "weekend" --> sat sun !!! catch all - nsub!(/weekend/, 'sat sun') - - # "mon through wed" -- > mon tue wed - # CATCH ALL FOR SPANS, TRY NOT TO USE THIS - nsub!(/#{DAY_OF_WEEK}\s+(?:through|to|until)\s+#{DAY_OF_WEEK}/) do |m1, m2| - index1 = ZDate.days_of_week.index(m1) - index2 = ZDate.days_of_week.index(m2) - i = index1 - ret_string = '' - if index2 > index1 - while i <= index2 - ret_string << ZDate.days_of_week[i] + ' ' - i += 1 - end - elsif index2 < index1 - loop do - ret_string << ZDate.days_of_week[i] + ' ' - i = (i + 1) % 7 - break if i != index2 + 1 # wrap until it hits index2 - end - else - # indices are the same, one week event - 8.times do - ret_string << ZDate.days_of_week[i] + ' ' - i = (i + 1) % 7 - end - end - ret_string - end - - # "every day" --> repeats daily - nsub!(/\b(repeat(?:s|ing)?|every|each)\s+da(ily|y)\b/, 'repeats daily') - - # "every other week starting this|next fri" --> every other friday starting this friday - 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') - - # "every other|3rd friday starting this|next week" --> every other|3rd friday starting this|next friday - 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') - - # "repeats monthly on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday - # "repeats every other month on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday - # "repeats every three months on the 1st and 2nd friday" --> repeats threemonthly 1st friday 2nd friday - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - - # "repeats monthly on the 1st friday" --> repeats monthly 1st friday - # "repeats monthly on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday - # "repeats every other month on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "repeats monthly on the 1st friday saturday" --> repeats monthly 1st friday 1st saturday - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - - # "21st of each month" --> repeats monthly 21st - # "on the 21st, 22nd and 25th of each month" --> repeats monthly 21st 22nd 25th - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "repeats each month on the 22nd" --> repeats monthly 22nd - # "repeats monthly on the 22nd 23rd and 24th" --> repeats monthly 22nd 23rd 24th - # 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. - 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/, '') + ' ' } - 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/, '') + ' ' } - # 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/,'') + " "} - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - # 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/,'') + " "} - 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/, '') + ' ' } - 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/, '') + ' ' } - # 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/,'') + " "} - - # "on day 4 of every month" --> repeats monthly 4 - # "on days 4 9 and 14 of every month" --> repeats monthly 4 9 14 - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "every 22nd of the month" --> repeats monthly 22 - # "every 22nd 23rd and 25th of the month" --> repeats monthly 22 23 25 - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "every 1st and 2nd fri of the month" --> repeats monthly 1st fri 2nd fri - 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 + ' ' } - 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 + ' ' } - - # "every 1st friday of the month" --> repeats monthly 1st friday - # "every 1st friday and 2nd tuesday of the month" --> repeats monthly 1st friday 2nd tuesday - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "every 1st fri sat of the month" --> repeats monthly 1st fri 1st sat - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - - # "the 1st and 2nd friday of every month" --> repeats monthly 1st friday 2nd friday - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - - # "the 1st friday of every month" --> repeats monthly 1st friday - # "the 1st friday and the 2nd tuesday of every month" --> repeats monthly 1st friday 2nd tuesday - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "the 1st friday saturday of every month" --> repeats monthly 1st friday 1st saturday - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - - # "repeats on the 1st and second friday of the month" --> repeats monthly 1st friday 2nd friday - 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 + ' ' } - 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 + ' ' } - 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 + ' ' } - - # "repeats on the 1st friday of the month --> repeats monthly 1st friday - # "repeats on the 1st friday and second tuesday of the month" --> repeats monthly 1st friday 2nd tuesday - 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/, '') + ' ' } - 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/, '') + ' ' } - 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/, '') + ' ' } - - # "repeats on the 1st friday saturday of the month" --> repeats monthly 1st friday 1st saturday - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - 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 + ' ') + ' ' } - - # "repeats each month" --> every month - nsub!(/(repeats\s+)?(each|every)\s+\bmonth(ly)?/, 'every month ') - nsub!(/all\s+months/, 'every month') - - # "repeats every other month" --> every other month - nsub!(/(repeats\s+)?(each|every)\s+(other|2n?d?)\s+month(ly)?/, 'every other month ') - nsub!(/(repeats\s+)?bimonthly/, 'every other month ') # hyphens have already been replaced in spell check (bi-monthly) - - # "repeats every three months" --> every third month - nsub!(/(repeats\s+)?(each|every)\s+3r?d?\s+month/, 'every third month ') - nsub!(/(repeats\s+)?trimonthly/, 'every third month ') - - # All months - nsub!(/(repeats\s+)?all\s+months/, 'every month ') - nsub!(/(repeats\s+)?all\s+other\+months/, 'every other month ') - - # All month - nsub!(/all\s+month/, 'this month ') - nsub!(/all\s+next\s+month/, 'next month ') - - # "repeats 2nd mon" --> repeats monthly 2nd mon - # "repeats 2nd mon, 3rd fri, and the last sunday" --> repeats monthly 2nd mon 3rd fri 5th sun - 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/, '') + ' ' } - - # "starting at x, ending at y" --> from x to y - nsub!(/(?:begin|start)(?:s|ing|ning)?\s+(?:at\s+)?#{TIME}\s+(?:and\s+)?end(?:s|ing)?\s+(?:at\s+)#{TIME}/, 'from \1 to \2') - - # "the x through the y" - nsub!(/^(?:the\s+)?#{DATE_DD_WITH_SUFFIX}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_WITH_SUFFIX}$/, '\1 through \2 ') - - # "x week(s) away" --> x week(s) from now - nsub!(/([0-9]+)\s+(day|week|month)s?\s+away/, '\1 \2s from now') - - # "x days from now" --> "x days from now" - # "in 2 weeks|days|months" --> 2 days|weeks|months from now" - nsub!(/\b(an?|[0-9]+)\s+(day|week|month)s?\s+(?:from\s+now|away)/, '\1 \2 from now') - nsub!(/in\s+(a|[0-9]+)\s+(week|day|month)s?/, '\1 \2 from now') - - # "x minutes|hours from now" --> "in x hours|minutes" - # "in x hour(s)" --> 11/20/07 at 22:00 - # REDONE, no more calculations - # "x minutes|hours from now" --> "x hours|minutes from now" - # "in x hours|minutes --> x hours|minutes from now" - nsub!(/\b(an?|[0-9]+)\s+(hour|minute)s?\s+(?:from\s+now|away)/, '\1 \2 from now') - nsub!(/in\s+(an?|[0-9]+)\s+(hour|minute)s?/, '\1 \2 from now') - - # Now only - nsub!(/^(?:\s*)(?:right\s+)?now(?:\s*)$/, '0 minutes from now') - - # "a week/month from yesterday|tomorrow" --> 1 week from yesterday|tomorrow - nsub!(/(?:(?:a|1)\s+)?(week|month)\s+from\s+(yesterday|tomorrow)/, '1 \1 from \2') - - # "a week/month from yesterday|tomorrow" --> 1 week from monday - nsub!(/(?:(?:a|1)\s+)?(week|month)\s+from\s+#{DAY_OF_WEEK}/, '1 \1 from \2') - - # "every 2|3 days" --> every 2nd|3rd day - nsub!(/every\s+(2|3)\s+days?/) { |m1| 'every ' + m1.to_i.ordinalize + ' day' } - - # "the following" --> following - nsub!(/the\s+following/, 'following') - - # "friday the 12th to sunday the 14th" --> 12th through 14th - 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') - - # "between 1 and 4" --> from 1 to 4 - nsub!(/between\s+#{TIME}\s+and\s+#{TIME}/, 'from \1 to \2') - - # "on the 3rd sat of this month" --> "3rd sat this month" - # "on the 3rd sat and 5th tuesday of this month" --> "3rd sat this month 5th tuesday this month" - # "on the 3rd sat and sunday of this month" --> "3rd sat this month 3rd sun this month" - # "on the 2nd and 3rd sat of this month" --> "2nd sat this month 3rd sat this month" - # This is going to be dicey, I'm going to remove 'the' from the following regexprsns: - # The 'the' case will be handled AFTER wrapper substitution at end of this method - 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') } - 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') } - 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') } - - # "on the 3rd sat of next month" --> "3rd sat next month" - # "on the 3rd sat and 5th tuesday of next month" --> "3rd sat next month 5th tuesday next month" - # "on the 3rd sat and sunday of next month" --> "3rd sat this month 3rd sun next month" - # "on the 2nd and 3rd sat of next month" --> "2nd sat this month 3rd sat next month" - 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') } - 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') } - 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') } - - # "on the 3rd sat of nov" --> "3rd sat nov" - # "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 - # "on the 3rd sat and sunday of nov" --> "3rd sat nov 3rd sun nov" - # "on the 2nd and 3rd sat of nov" --> "2nd sat nov 3rd sat nov" - 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) } - 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) } - 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) } - - # "on the last day of nov" --> "last day nov" - nsub!(/(?:\bon\s+)?(?:the\s+)?last\s+day\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR}/, 'last day \1') - # "on the 1st|last day of this|the month" --> "1st|last day this month" - nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?(?:this|the)?(?:\s*)month/, '\1 day this month') - # "on the 1st|last day of next month" --> "1st|last day next month" - nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?next\s+month/, '\1 day next month') - - # "every other weekend" --> every other sat sun - nsub!(/every\s+other\s+weekend/, 'every other sat sun') - - # "this week on mon "--> this mon - nsub!(/this\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/, 'this \1') - # "mon of this week " --> this mon - nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?this\s+week/, 'this \1') - - # "next week on mon "--> next mon - nsub!(/next\s+week\s+(?:on\s+)?#{DAY_OF_WEEK}/, 'next \1') - # "mon of next week " --> next mon - nsub!(/#{DAY_OF_WEEK}\s+(?:of\s+)?next\s+week/, 'next \1') - - # Ordinal this month: - # this will slip by now - # the 23rd of this|the month --> 8/23 - # this month on the 23rd --> 8/23 - # REDONE, no date calculations - # the 23rd of this|the month --> 23rd this month - # this month on the 23rd --> 23rd this month - nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:this|the)\s+month/, '\1 this month') - nsub!(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 this month') - - # Ordinal next month: - # this will slip by now - # the 23rd of next month --> 9/23 - # next month on the 23rd --> 9/23 - # REDONE no date calculations - # the 23rd of next month --> 23rd next month - # next month on the 23rd --> 23rd next month - nsub!(/(?:the\s+)?#{DATE_DD}\s+(?:of\s+)?(?:next|the\s+following)\s+month/, '\1 next month') - nsub!(/(?:next|the\s+following)\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD}/, '\1 next month') - - # "for the next 3 days|weeks|months" --> for 3 days|weeks|months - nsub!(/for\s+(?:the\s+)?(?:next|following)\s+(\d+)\s+(days|weeks|months)/, 'for \1 \2') - - # This monthname -> monthname - nsub!(/this\s+#{MONTH_OF_YEAR}/, '\1') - - # Until monthname -> through monthname - # through shouldn't be included here; through and until mean different things, need to fix wrapper terminology - # "until june --> through june" - nsub!(/(?:through|until)\s+(?:this\s+)?#{MONTH_OF_YEAR}\s+(?:$|\D)/, 'through \1') - - # the week of 1/2 -> week of 1/2 - nsub!(/(the\s+)?week\s+(of|starting)\s+(the\s+)?/, 'week of ') - - # the week ending 1/2 -> week through 1/2 - nsub!(/(the\s+)?week\s+(?:ending)\s+/, 'week through ') - - # clean up wrapper terminology - # This should always be at end of pre-process - nsub!(/(begin(s|ning)?|start(s|ing)?)(\s+(at|on))?/, 'start') - nsub!(/(\bend(s|ing)?|through|until)(\s+(at|on))?/, 'through') - nsub!(/start\s+(?:(?:this|in)\s+)?#{MONTH_OF_YEAR}/, 'start \1') + Substitutions::PreProcessing.apply(self) # '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 # Do these actually do ANYTHING anymore? # "on the 3rd sat and sunday of the month" --> "repeats monthly 3rd sat 3rd sun" OR "3rd sat this month 3rd sun this month" - 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/ - if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/ - 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') } + if query_str =~ /(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/ + if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD.source}/ + nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/) do |m1, m2| + 'repeats monthly ' + m2.gsub(/\band\b/, '').gsub(/#{DAY_OF_WEEK.source}/, m1 + ' \1') + end else - 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') } + nsub!(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:the)\s+month/) do |m1, m2| + m2.gsub(/\band\b/, '').gsub(/#{DAY_OF_WEEK.source}/, m1 + ' \1 this month') + end end end # "on the 2nd and 3rd sat of this month" --> "repeats monthly 2nd sat 3rd sat" OR "2nd sat this month 3rd sat this month" - 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/ - if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/ - 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) } + if query_str =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:the)\s+month/ + if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD.source}/ + nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:the)\s+month/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2) + end else - 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') } + nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:the)\s+month/) do |m1, m2| + m1.gsub(/\b(and|the)\b/, '').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' this month') + end end end # "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" - 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/ - if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD}/ - 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/, '') } + if query_str =~ /(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/ + if query_str =~ /(start|through)\s+#{DATE_MM_SLASH_DD.source}/ + nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + end else - 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') } + nsub!(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the)\s+month/) do |m1| + m1.gsub(/\b(and|the)\b/, '').gsub(/#{DAY_OF_WEEK.source}/, '\1 this month') + end end end + end - nsub!(/from\s+now\s+(through|to|until)/, 'now through') + def to_s + query_str end + + private + + attr_accessor :query_str end end diff --git a/lib/nickel/substitutions/pre_processing.rb b/lib/nickel/substitutions/pre_processing.rb new file mode 100644 index 0000000..fab97d6 --- /dev/null +++ b/lib/nickel/substitutions/pre_processing.rb @@ -0,0 +1,654 @@ +require 'nickel/substitutor' +require 'nickel/nlp_query_constants' + +module Nickel + module Substitutions + class PreProcessing + extend Substitutor + include NLPQueryConstants + + substitutions do + sub(/last\s+#{DAY_OF_WEEK.source}/, '5th \1') # last dayname => 5th dayname + sub(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day + sub(/^(through|until)/, 'today through') # ^through => today through + sub(/every\s*(night|morning)/, 'every day') + sub(/tonight/, 'today') + sub(/this(?:\s*)morning/, 'today') + sub(/before\s+12pm/, '6am to 12pm') # arbitrary + + # Handle 'THE' Cases + # Attempt to pick out where a user entered 'the' when they really mean 'every'. + # For example, + # The first of every month and the 22nd of THE month => repeats monthly first xxxxxx repeats monthly 22nd xxxxxxx + sub(/(?:the\s+)?#{DATE_DD_WITH_SUFFIX.source}\s+(?:of\s+)?(?:every|each)\s+month((?:.*)of\s+the\s+month(?:.*))/) do |m1, m2| + ret_str = ' repeats monthly ' + m1 + ret_str << m2.gsub(/(?:and\s+)?(?:the\s+)?#{DATE_DD_WITH_SUFFIX.source}\s+of\s+the\s+month/, ' repeats monthly \1 ') + end + + # Every first sunday of the month and the last tuesday => repeats monthly first sunday xxxxxxxxx repeats monthly last tuesday xxxxxxx + sub(/every\s+#{WEEK_OF_MONTH.source}\s+#{DAY_OF_WEEK.source}\s+of\s+(?:the\s+)?month((?:.*)and\s+(?:the\s+)?#{WEEK_OF_MONTH.source}\s+#{DAY_OF_WEEK.source}(?:.*))/) do |m1, m2, m3| + ret_str = ' repeats monthly ' + m1 + ' ' + m2 + ' ' + ret_str << m3.gsub(/and\s+(?:the\s+)?#{WEEK_OF_MONTH.source}\s+#{DAY_OF_WEEK.source}(?:\s*)(?:of\s+)?(?:the\s+)?(?:month\s+)?/, ' repeats monthly \1 \2 ') + end + + # The x through the y of oct z => 10/x/z through 10/y/z + sub(/(?:the\s+)?#{DATE_DD.source}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD.source}\s(?:of\s+)#{MONTH_OF_YEAR.source}\s+(?:of\s+)?#{YEAR.source}/) do |m1, m2, m3, m4| + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 + '/' + m4 + end + + # The x through the y of oct => 10/x through 10/y + sub(/(?:the\s+)?#{DATE_DD.source}\s+(?:through|to|until)\s+(?:the\s+)#{DATE_DD.source}\s(?:of\s+)?#{MONTH_OF_YEAR.source}/) do |m1, m2, m3| + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m1 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m2 + end + + # January 1 - February 15 + sub(/#{MONTH_OF_YEAR.source}\s+#{DATE_DD_NB_ON_SUFFIX.source}\s+(?:through|to|until)\s+#{MONTH_OF_YEAR.source}\s#{DATE_DD_NB_ON_SUFFIX.source}/) do |m1, m2, m3, m4| + (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)/, '') + end + + # Tuesday, january 1 - friday, february 15, 2013 + sub(/(?:#{DAY_OF_WEEK.source})?(?:[\s,]+)#{MONTH_OF_YEAR.source}(?:[\s,]+)#{DATE_DD.source}\s+(?:through|to|until)\s+(?:#{DAY_OF_WEEK.source})?(?:[\s,]+)#{MONTH_OF_YEAR.source}(?:[\s,]+)#{DATE_DD.source}(?:[\s,]+)#{YEAR.source}/) do |m1, m2, m3, m4, m5, m6, m7| + if m7.nil? + (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 + else + (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 + end + end + + # Tuesday, january 1 2013 - friday, february 15, 2013 + sub(/(?:#{DAY_OF_WEEK.source})?(?:[\s,]+)#{MONTH_OF_YEAR.source}(?:[\s,]+)#{DATE_DD.source}\s+#{YEAR.source}\s+(?:through|to|until)\s+(?:#{DAY_OF_WEEK.source})?(?:[\s,]+)#{MONTH_OF_YEAR.source}(?:[\s,]+)#{DATE_DD.source}(?:[\s,]+)#{YEAR.source}/) do |m1, m2, m3, m4, m5, m6, m7, m8| + (ZDate.months_of_year.index(m2) + 1).to_s + '/' + m3 + '/' + m4 + ' through ' + (ZDate.months_of_year.index(m6) + 1).to_s + '/' + m7 + '/' + m8 + end + + # Monthname x through y + sub(/#{MONTH_OF_YEAR.source}\s+(?:the\s+)?#{DATE_DD_NB_ON_SUFFIX.source}\s+(?:of\s+)?(?:#{YEAR.source}\s+)?(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_NB_ON_SUFFIX.source}(?:\s+of)?(?:\s+#{YEAR.source})?/) do |m1, m2, m3, m4, m5| + if m3 # $3 holds first occurrence of year + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m3 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m3 + elsif m5 # $5 holds second occurrence of year + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + '/' + m5 + else + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m4 + end + end + + # Monthname x through monthname y + # Jan 14 through jan 18 => 1/14 through 1/18 + # Oct 2 until oct 5 + sub(/#{MONTH_OF_YEAR.source}\s+#{DATE_DD_NB_ON_SUFFIX.source}\s+(?:to|through|until)\s+#{MONTH_OF_YEAR.source}\s+#{DATE_DD_NB_ON_SUFFIX.source}\s+(?:of\s+)?(?:#{YEAR.source})?/) do |m1, m2, m3, m4, m5| + if m5 + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + '/' + m5 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + '/' + m5 + ' ' + else + (ZDate.months_of_year.index(m1) + 1).to_s + '/' + m2 + ' through ' + (ZDate.months_of_year.index(m3) + 1).to_s + '/' + m4 + ' ' + end + end + + # Mnday the 23rd, tuesday the 24th and wed the 25th of oct => 11/23 11/24 11/25 + sub(/((?:#{DAY_OF_WEEK_NB.source}\s+the\s+#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?){1,31})of\s+#{MONTH_OF_YEAR.source}\s*(#{YEAR.source})?/) do |m1, m2, m3| + month_str = (ZDate.months_of_year.index(m2) + 1).to_s + if m3 + m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK.source}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/, month_str + '/\1/' + m3) + else + m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK.source}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/, month_str + '/\1') + end + end + + # the 23rd and 24th of october => 11/23 11/24 + # the 23rd, 24th, and 25th of october => 11/23 11/24 11/25 + # the 23rd, 24th, and 25th of october 2010 => 11/23/2010 11/24/2010 11/25/2010 + # monday and tuesday, the 23rd and 24th of july => 7/23 7/24 + sub(/(?:(?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:day\s+)?(?:in\s+)?(?:of\s+)#{MONTH_OF_YEAR.source}\s*(#{YEAR.source})?/) do |m1, m2, m3| + month_str = (ZDate.months_of_year.index(m2) + 1).to_s + if m3 + m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK.source}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/, month_str + '/\1/' + m3) + else + m1.gsub(/\b(and|the)\b|#{DAY_OF_WEEK.source}/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/, month_str + '/\1') + end + end + + # Match date with year first. + # Don't allow mixing of suffixes, e.g. "dec 3rd 2008 at 4 and dec 5 2008 9 to 5" + # Dec 2nd, 3rd, and 5th 2008 => 12/2/2008 12/2/2008 12/5/2008 + # Mon nov 23rd 08 + # Dec 2, 3, 5, 2008 => 12/2/2008 12/3/2008 12/5/2008 + sub(/(?:(?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR.source}\s+((?:(?:the\s+)?#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?){1,31})#{YEAR.source}/) do |m1, m2, m3| + month_str = (ZDate.months_of_year.index(m1) + 1).to_s + m2.gsub(/\b(and|the)\b/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/, month_str + '/\1/' + m3) + end + + sub(/(?:(?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR.source}\s+((?:(?:the\s+)?#{DATE_DD_WITHOUT_SUFFIX_NB.source}\s+(?:and\s+)?){1,31})#{YEAR.source}/) do |m1, m2, m3| + month_str = (ZDate.months_of_year.index(m1) + 1).to_s + m2.gsub(/\b(and|the)\b/, '').gsub(/#{DATE_DD_WITHOUT_SUFFIX.source}/, month_str + '/\1/' + m3) + end + + # Dec 2nd, 3rd, and 4th => 12/2, 12/3, 12/4 + # 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 + sub(/(?:(?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR.source}\s+(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1, m2| + month_str = (ZDate.months_of_year.index(m1) + 1).to_s + m2.gsub(/(and|the)/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/) { month_str + '/' + Regexp.last_match(1) } # last match is from the nested match! + end + + # Apr 29, 5 - 8pm + sub(/#{MONTH_OF_YEAR.source}(?:\s)+#{DATE_DD_WITHOUT_SUFFIX.source}(?:,)?(?:\s)+(#{TIME.source} through #{TIME.source})/) do |m1, m2, m3| + month_str = (ZDate.months_of_year.index(m1) + 1).to_s + "#{month_str}/#{m2} #{m3}" + end + + # jan 4 2-3 has to be modified, but + # jan 24 through jan 26 cannot! + # not real sure what this one is doing + # "dec 2, 3, and 4" --> 12/2, 12/3, 12/4 + # "mon, tue, wed, dec 2, 3, and 4" --> 12/2, 12/3, 12/4 + sub(/(#{MONTH_OF_YEAR_NB.source}\s+(?:the\s+)?(?:(?:#{DATE_DD_WITHOUT_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:to|through|until)\s+#{DATE_DD_WITHOUT_SUFFIX_NB.source})/) do |m1| + m1.gsub(/#{DATE_DD_WITHOUT_SUFFIX.source}\s+(to|through|until)/, 'from \1 through ') + end + sub(/(?:(?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})?#{MONTH_OF_YEAR.source}\s+(?:the\s+)?((?:#{DATE_DD_WITHOUT_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1, m2| + month_str = (ZDate.months_of_year.index(m1) + 1).to_s + m2.gsub(/(and|the)/, '').gsub(/#{DATE_DD_NB_ON_SUFFIX.source}/) { month_str + '/' + Regexp.last_match(1) } # last match is from nested match + end + + # "monday 12/6" --> 12/6 + sub(/#{DAY_OF_WEEK_NB.source}\s+(#{DATE_MM_SLASH_DD.source})/, '\1') + + # "next friday to|until|through the following tuesday" --> 10/12 through 10/16 + # "next friday through sunday" --> 10/12 through 10/14 + # "next friday and the following sunday" --> 11/16 11/18 + # we are not going to do date calculations here anymore, so instead: + # next friday to|until|through the following tuesday" --> next friday through tuesday + # next friday and the following sunday --> next friday and sunday + sub(/next\s+#{DAY_OF_WEEK.source}\s+(to|until|through|and)\s+(?:the\s+)?(?:following|next)?(?:\s*)#{DAY_OF_WEEK.source}/) do |m1, m2, m3| + connector = (m2 =~ /and/ ? ' ' : ' through ') + 'next ' + m1 + connector + m3 + end + + # "this friday to|until|through the following tuesday" --> 10/5 through 10/9 + # "this friday through following sunday" --> 10/5 through 10/7 + # "this friday and the following monday" --> 11/9 11/12 + # No longer performing date calculation + # this friday and the following monday --> fri mon + # this friday through the following tuesday --> fri through tues + sub(/(?:this\s+)?#{DAY_OF_WEEK.source}\s+(to|until|through|and)\s+(?:the\s+)?(?:this|following)(?:\s*)#{DAY_OF_WEEK.source}/) do |m1, m2, m3| + connector = (m2 =~ /and/ ? ' ' : ' through ') + m1 + connector + m3 + end + + # "the wed after next" --> 2 wed from today + sub(/(?:the\s+)?#{DAY_OF_WEEK.source}\s+(?:after|following)\s+(?:the\s+)?next/, '2 \1 from today') + + # "mon and tue" --> mon tue + sub(/(#{DAY_OF_WEEK.source}\s+and\s+#{DAY_OF_WEEK.source})(?:\s+and)?/, '\2 \3') + + # "mon wed every week" --> every mon wed + sub(/((#{DAY_OF_WEEK.source}(?:\s*)){1,7})(?:of\s+)?(?:every|each)(\s+other)?\s+week/, 'every \4 \1') + + # "every week on mon tue fri" --> every mon tue fri + sub(/(?:repeats\s+)?every\s+(?:(other|3rd|2nd)\s+)?weeks?\s+(?:\bon\s+)?((?:#{DAY_OF_WEEK_NB.source}\s+){1,7})/, 'every \1 \2') + + # "every mon and every tue and.... " --> every mon tue ... + sub(/every\s+#{DAY_OF_WEEK.source}\s+(?:and\s+)?every\s+#{DAY_OF_WEEK.source}(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK.source})?(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK.source})?(?:\s+(?:and\s+)?every\s+#{DAY_OF_WEEK.source})?/, 'every \1 \2 \3 \4 \5') + + # monday, wednesday, and friday next week at 8 + sub(/((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){1,7})(?:of\s+)?(this|next)\s+week/, '\2 \1') + + # "every day this|next week" --> returns monday through friday of the closest week, kinda stupid + # doesn't do that anymore, no date calculations allowed here, instead just formats it nicely for construct finders --> every day this|next week + sub(/every\s+day\s+(?:of\s+)?(this|the|next)\s+week\b./) do |m1| + m1 == 'next' ? 'every day next week' : 'every day this week' + end + + # "every day for the next week" --> "every day this week" + sub(/every\s+day\s+for\s+(the\s+)?(next|this)\s+week/, 'every day this week') + + # "this weekend" --> sat sun + sub(/(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') + + # "this weekend including mon" --> sat sun mon + sub(/sat\s+sun\s+(and|includ(es?|ing))\s+mon/, 'sat sun mon') + sub(/sat\s+sun\s+(and|includ(es?|ing))\s+fri/, 'fri sat sun') + + # Note: next weekend including monday will now fail. Need to make constructors find "next sat sun mon" + # "next weekend" --> next weekend + sub(/(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') + + # "next weekend including mon" --> next sat sun mon + sub(/next\s+weekend\s+(and|includ(es?|ing))\s+mon/, 'next sat sun mon') + sub(/next\s+weekend\s+(and|includ(es?|ing))\s+fri/, 'next fri sat sun') + + # "every weekend" --> every sat sun + sub(/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 + + # "weekend" --> sat sun !!! catch all + sub(/weekend/, 'sat sun') + + # "mon through wed" -- > mon tue wed + # CATCH ALL FOR SPANS, TRY NOT TO USE THIS + sub(/#{DAY_OF_WEEK.source}\s+(?:through|to|until)\s+#{DAY_OF_WEEK.source}/) do |m1, m2| + index1 = ZDate.days_of_week.index(m1) + index2 = ZDate.days_of_week.index(m2) + i = index1 + ret_string = '' + if index2 > index1 + while i <= index2 + ret_string << ZDate.days_of_week[i] + ' ' + i += 1 + end + elsif index2 < index1 + loop do + ret_string << ZDate.days_of_week[i] + ' ' + i = (i + 1) % 7 + break if i != index2 + 1 # wrap until it hits index2 + end + else + # indices are the same, one week event + 8.times do + ret_string << ZDate.days_of_week[i] + ' ' + i = (i + 1) % 7 + end + end + ret_string + end + + # "every day" --> repeats daily + sub(/\b(repeat(?:s|ing)?|every|each)\s+da(ily|y)\b/, 'repeats daily') + + # "every other week starting this|next fri" --> every other friday starting this friday + sub(/every\s+(3rd|other)\s+week\s+(?:start(?:s|ing)?|begin(?:s|ning)?)\s+(this|next)\s+#{DAY_OF_WEEK.source}/, 'every \1 \3 start \2 \3') + + # "every other|3rd friday starting this|next week" --> every other|3rd friday starting this|next friday + sub(/every\s+(3rd|other)\s+#{DAY_OF_WEEK.source}\s+(?:start(?:s|ing)?|begin(?:s|ning)?)\s+(this|next)\s+week/, 'every \1 \2 start \3 \2') + + # "repeats monthly on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday + # "repeats every other month on the 1st and 2nd friday" --> repeats monthly 1st friday 2nd friday + # "repeats every three months on the 1st and 2nd friday" --> repeats threemonthly 1st friday 2nd friday + sub(/(?: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.source}/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?: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.source}/) do |m1, m2| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?: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.source}/) do |m1, m2| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?: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.source}/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?: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.source}/) do |m1, m2| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?: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.source}/) do |m1, m2| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + + # "repeats monthly on the 1st friday" --> repeats monthly 1st friday + # "repeats monthly on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday + # "repeats every other month on the 1st friday, second tuesday, and third friday" --> repeats monthly 1st friday 2nd tuesday 3rd friday + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?: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.source}\s+(?:and\s+)?(?:the\s+)?){1,31})/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "repeats monthly on the 1st friday saturday" --> repeats monthly 1st friday 1st saturday + sub(/(?:repeats\s+)(?:(?:each|every|all)\s+)?\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})/) do |m1, m2| + 'repeats monthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?: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.source}\s+){2,7})/) do |m1, m2| + 'repeats altmonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?: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.source}\s+){2,7})/) do |m1, m2| + 'repeats threemonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:ly|s)?\s+(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})/) do |m1, m2| + 'repeats monthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?: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.source}\s+){2,7})/) do |m1, m2| + 'repeats altmonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?: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.source}\s+){2,7})/) do |m1, m2| + 'repeats threemonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + + # "21st of each month" --> repeats monthly 21st + # "on the 21st, 22nd and 25th of each month" --> repeats monthly 21st 22nd 25th + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+\bmonths?/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+(?:other|2n?d?)\s+months?/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,31})(?:days?\s+)?(?:of\s+)?(?:each|all|every)\s+3r?d?\s+months?/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "repeats each month on the 22nd" --> repeats monthly 22nd + # "repeats monthly on the 22nd 23rd and 24th" --> repeats monthly 22nd 23rd 24th + # 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. + sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)\bmonth(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)(?:(?:each|every|all)\s+)?\bmonth(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + # sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?\bmonth(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats monthly " + m1.gsub(/\b(and|the)\b/,'') + " "} + sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?\bmonthly\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)(?:other|2n?d?)\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)(?:(?:each|every|all)\s+)?(?:other|2n?d?)\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + # sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?(?:other|2n?d?)\s+month(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats altmonthly " + m1.gsub(/\b(and|the)\b/,'') + " "} + sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)3r?d?\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)(?:(?:each|every|all)\s+)?3r?d?\s+month(?:s|ly)?\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + # sub(/(?:repeats\s+)?(?:(?:each|every|all)\s+)?3r?d?\s+month(?:s|ly)\s+(?:on\s+)?(?:the\s+)?((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) { |m1| "repeats threemonthly " + m1.gsub(/\b(and|the)\b/,'') + " "} + + # "on day 4 of every month" --> repeats monthly 4 + # "on days 4 9 and 14 of every month" --> repeats monthly 4 9 14 + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+\bmonths?/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+(?:other|2n?d?)\s+months?/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:day|date)s?\s+((?:#{DATE_DD_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)(every|all|each)\s+3r?d?\s+months?/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "every 22nd of the month" --> repeats monthly 22 + # "every 22nd 23rd and 25th of the month" --> repeats monthly 22 23 25 + sub(/(?:repeats\s+)?(?:every|each)\s+((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:day\s+)?(?:of\s+)?(?:the\s+)?\bmonth/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:every|each)\s+other\s+((?:#{DATE_DD_WITH_SUFFIX_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:day\s+)?(?:of\s+)?(?:the\s+)?\bmonth/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "every 1st and 2nd fri of the month" --> repeats monthly 1st fri 2nd fri + sub(/(?:repeats\s+)?(?:each|every|all)\s+((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?:repeats\s+)?(?:each|every|all)\s+other\s+((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1, m2| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + + # "every 1st friday of the month" --> repeats monthly 1st friday + # "every 1st friday and 2nd tuesday of the month" --> repeats monthly 1st friday 2nd tuesday + sub(/(?:repeats\s+)?(?:each|every|all)\s+((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:each|every|all)\s+other\s+((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "every 1st fri sat of the month" --> repeats monthly 1st fri 1st sat + sub(/(?:repeats\s+)?(?:each|every|all)\s+(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)?(?:each|every|all)\s+other\s+(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:the\s+)?(?:(?:each|every|all)\s+)?\bmonths?/) do |m1, m2| + 'repeats altmonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + + # "the 1st and 2nd friday of every month" --> repeats monthly 1st friday 2nd friday + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) do |m1, m2| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) do |m1, m2| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + + # "the 1st friday of every month" --> repeats monthly 1st friday + # "the 1st friday and the 2nd tuesday of every month" --> repeats monthly 1st friday 2nd tuesday + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "the 1st friday saturday of every month" --> repeats monthly 1st friday 1st saturday + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)(?:other|2n?d?)\s+months?/) do |m1, m2| + 'repeats altmonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)?(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all)\s+)3r?d?\s+months?/) do |m1, m2| + 'repeats threemonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + + # "repeats on the 1st and second friday of the month" --> repeats monthly 1st friday 2nd friday + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) do |m1, m2| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,5})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) do |m1, m2| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '').split.join(' ' + m2 + ' ') + ' ' + m2 + ' ' + end + + # "repeats on the 1st friday of the month --> repeats monthly 1st friday + # "repeats on the 1st friday and second tuesday of the month" --> repeats monthly 1st friday 2nd tuesday + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) do |m1| + 'repeats altmonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) do |m1| + 'repeats threemonthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "repeats on the 1st friday saturday of the month" --> repeats monthly 1st friday 1st saturday + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?\bmonths?/) do |m1, m2| + 'repeats monthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?(?:other|2n?d?)\s+months?/) do |m1, m2| + 'repeats altmonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + sub(/(?:repeats\s+)(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+){2,7})(?:of\s+)?(?:(?:every|each|all|the)\s+)?3r?d?\s+months?/) do |m1, m2| + 'repeats threemonthly ' + m1 + ' ' + m2.split.join(' ' + m1 + ' ') + ' ' + end + + # "repeats each month" --> every month + sub(/(repeats\s+)?(each|every)\s+\bmonth(ly)?/, 'every month ') + sub(/all\s+months/, 'every month') + + # "repeats every other month" --> every other month + sub(/(repeats\s+)?(each|every)\s+(other|2n?d?)\s+month(ly)?/, 'every other month ') + sub(/(repeats\s+)?bimonthly/, 'every other month ') # hyphens have already been replaced in spell check (bi-monthly) + + # "repeats every three months" --> every third month + sub(/(repeats\s+)?(each|every)\s+3r?d?\s+month/, 'every third month ') + sub(/(repeats\s+)?trimonthly/, 'every third month ') + + # All months + sub(/(repeats\s+)?all\s+months/, 'every month ') + sub(/(repeats\s+)?all\s+other\+months/, 'every other month ') + + # All month + sub(/all\s+month/, 'this month ') + sub(/all\s+next\s+month/, 'next month ') + + # "repeats 2nd mon" --> repeats monthly 2nd mon + # "repeats 2nd mon, 3rd fri, and the last sunday" --> repeats monthly 2nd mon 3rd fri 5th sun + sub(/repeats\s+(?:\bon\s+)?(?:the\s+)?((?:(?:1|2|3|4|5)(?:st|nd|rd|th)?\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})/) do |m1| + 'repeats monthly ' + m1.gsub(/\b(and|the)\b/, '') + ' ' + end + + # "starting at x, ending at y" --> from x to y + sub(/(?:begin|start)(?:s|ing|ning)?\s+(?:at\s+)?#{TIME.source}\s+(?:and\s+)?end(?:s|ing)?\s+(?:at\s+)#{TIME.source}/, 'from \1 to \2') + + # "the x through the y" + sub(/^(?:the\s+)?#{DATE_DD_WITH_SUFFIX.source}\s+(?:through|to|until)\s+(?:the\s+)?#{DATE_DD_WITH_SUFFIX.source}$/, '\1 through \2 ') + + # "x week(s) away" --> x week(s) from now + sub(/([0-9]+)\s+(day|week|month)s?\s+away/, '\1 \2s from now') + + # "x days from now" --> "x days from now" + # "in 2 weeks|days|months" --> 2 days|weeks|months from now" + sub(/\b(an?|[0-9]+)\s+(day|week|month)s?\s+(?:from\s+now|away)/, '\1 \2 from now') + sub(/in\s+(a|[0-9]+)\s+(week|day|month)s?/, '\1 \2 from now') + + # "x minutes|hours from now" --> "in x hours|minutes" + # "in x hour(s)" --> 11/20/07 at 22:00 + # REDONE, no more calculations + # "x minutes|hours from now" --> "x hours|minutes from now" + # "in x hours|minutes --> x hours|minutes from now" + sub(/\b(an?|[0-9]+)\s+(hour|minute)s?\s+(?:from\s+now|away)/, '\1 \2 from now') + sub(/in\s+(an?|[0-9]+)\s+(hour|minute)s?/, '\1 \2 from now') + + # Now only + sub(/^(?:\s*)(?:right\s+)?now(?:\s*)$/, '0 minutes from now') + + # "a week/month from yesterday|tomorrow" --> 1 week from yesterday|tomorrow + sub(/(?:(?:a|1)\s+)?(week|month)\s+from\s+(yesterday|tomorrow)/, '1 \1 from \2') + + # "a week/month from yesterday|tomorrow" --> 1 week from monday + sub(/(?:(?:a|1)\s+)?(week|month)\s+from\s+#{DAY_OF_WEEK.source}/, '1 \1 from \2') + + # "the following" --> following + sub(/the\s+following/, 'following') + + # "friday the 12th to sunday the 14th" --> 12th through 14th + sub(/#{DAY_OF_WEEK.source}\s+the\s+#{DATE_DD_WITH_SUFFIX.source}\s+(?:to|through|until)\s+#{DAY_OF_WEEK.source}\s+the\s+#{DATE_DD_WITH_SUFFIX.source}/, '\2 through \4') + + # "between 1 and 4" --> from 1 to 4 + sub(/between\s+#{TIME.source}\s+and\s+#{TIME.source}/, 'from \1 to \2') + + # "on the 3rd sat of this month" --> "3rd sat this month" + # "on the 3rd sat and 5th tuesday of this month" --> "3rd sat this month 5th tuesday this month" + # "on the 3rd sat and sunday of this month" --> "3rd sat this month 3rd sun this month" + # "on the 2nd and 3rd sat of this month" --> "2nd sat this month 3rd sat this month" + # This is going to be dicey, I'm going to remove 'the' from the following regexprsns: + # The 'the' case will be handled AFTER wrapper substitution at end of this method + sub(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:this|of)\s+month/) do |m1, m2| + m2.gsub(/\band\b/, '').gsub(/#{DAY_OF_WEEK.source}/, m1 + ' \1 this month') + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:this|of)\s+month/) do |m1, m2| + m1.gsub(/\b(and|the)\b/, '').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' this month') + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:this|of)\s+month/) do |m1| + m1.gsub(/\b(and|the)\b/, '').gsub(/#{DAY_OF_WEEK.source}/, '\1 this month') + end + + # "on the 3rd sat of next month" --> "3rd sat next month" + # "on the 3rd sat and 5th tuesday of next month" --> "3rd sat next month 5th tuesday next month" + # "on the 3rd sat and sunday of next month" --> "3rd sat this month 3rd sun next month" + # "on the 2nd and 3rd sat of next month" --> "2nd sat this month 3rd sat next month" + sub(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?next\s+month/) do |m1, m2| + m2.gsub(/\band\b/, '').gsub(/#{DAY_OF_WEEK.source}/, m1 + ' \1 next month') + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?next\s+month/) do |m1, m2| + m1.gsub(/\b(and|the)\b/, '').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' next month') + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?next\s+month/) do |m1| + m1.gsub(/\b(and|the)\b/, '').gsub(/#{DAY_OF_WEEK.source}/, '\1 next month') + end + + # "on the 3rd sat of nov" --> "3rd sat nov" + # "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 + # "on the 3rd sat and sunday of nov" --> "3rd sat nov 3rd sun nov" + # "on the 2nd and 3rd sat of nov" --> "2nd sat nov 3rd sat nov" + sub(/(?:\bon\s+)?(?:the\s+)?(1st|2nd|3rd|4th|5th)\s+((?:#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?){2,7})(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR.source}/) do |m1, m2, m3| + m2.gsub(/\band\b/, '').gsub(/#{DAY_OF_WEEK.source}/, m1 + ' \1 ' + m3) + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+(?:and\s+)?(?:the\s+)?){2,7})#{DAY_OF_WEEK.source}\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR.source}/) do |m1, m2, m3| + m1.gsub(/\b(and|the)\b/, '').gsub(/(1st|2nd|3rd|4th|5th)/, '\1 ' + m2 + ' ' + m3) + end + sub(/(?:\bon\s+)?(?:the\s+)?((?:(?:1st|2nd|3rd|4th|5th)\s+#{DAY_OF_WEEK_NB.source}\s+(?:and\s+)?(?:the\s+)?){1,10})(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR.source}/) do |m1, m2| + m1.gsub(/\b(and|the)\b/, '').gsub(/#{DAY_OF_WEEK.source}/, '\1 ' + m2) + end + + # "on the last day of nov" --> "last day nov" + sub(/(?:\bon\s+)?(?:the\s+)?last\s+day\s+(?:of\s+)?(?:in\s+)?#{MONTH_OF_YEAR.source}/, 'last day \1') + # "on the 1st|last day of this|the month" --> "1st|last day this month" + sub(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?(?:this|the)?(?:\s*)month/, '\1 day this month') + # "on the 1st|last day of next month" --> "1st|last day next month" + sub(/(?:\bon\s+)?(?:the\s+)?(1st|last)\s+day\s+(?:of\s+)?next\s+month/, '\1 day next month') + + # "every other weekend" --> every other sat sun + sub(/every\s+other\s+weekend/, 'every other sat sun') + + # "this week on mon "--> this mon + sub(/this\s+week\s+(?:on\s+)?#{DAY_OF_WEEK.source}/, 'this \1') + # "mon of this week " --> this mon + sub(/#{DAY_OF_WEEK.source}\s+(?:of\s+)?this\s+week/, 'this \1') + + # "next week on mon "--> next mon + sub(/next\s+week\s+(?:on\s+)?#{DAY_OF_WEEK.source}/, 'next \1') + # "mon of next week " --> next mon + sub(/#{DAY_OF_WEEK.source}\s+(?:of\s+)?next\s+week/, 'next \1') + + # Ordinal this month: + # this will slip by now + # the 23rd of this|the month --> 8/23 + # this month on the 23rd --> 8/23 + # REDONE, no date calculations + # the 23rd of this|the month --> 23rd this month + # this month on the 23rd --> 23rd this month + sub(/(?:the\s+)?#{DATE_DD.source}\s+(?:of\s+)?(?:this|the)\s+month/, '\1 this month') + sub(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD.source}/, '\1 this month') + + # Ordinal next month: + # this will slip by now + # the 23rd of next month --> 9/23 + # next month on the 23rd --> 9/23 + # REDONE no date calculations + # the 23rd of next month --> 23rd next month + # next month on the 23rd --> 23rd next month + sub(/(?:the\s+)?#{DATE_DD.source}\s+(?:of\s+)?(?:next|the\s+following)\s+month/, '\1 next month') + sub(/(?:next|the\s+following)\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD.source}/, '\1 next month') + + # "for the next 3 days|weeks|months" --> for 3 days|weeks|months + sub(/for\s+(?:the\s+)?(?:next|following)\s+(\d+)\s+(days|weeks|months)/, 'for \1 \2') + + # This monthname -> monthname + sub(/this\s+#{MONTH_OF_YEAR.source}/, '\1') + + # Until monthname -> through monthname + # through shouldn't be included here; through and until mean different things, need to fix wrapper terminology + # "until june --> through june" + sub(/(?:through|until)\s+(?:this\s+)?#{MONTH_OF_YEAR.source}\s+(?:$|\D)/, 'through \1') + + # the week of 1/2 -> week of 1/2 + sub(/(the\s+)?week\s+(of|starting)\s+(the\s+)?/, 'week of ') + + # the week ending 1/2 -> week through 1/2 + sub(/(the\s+)?week\s+(?:ending)\s+/, 'week through ') + + # clean up wrapper terminology + # This should always be at end of pre-process + sub(/(begin(s|ning)?|start(s|ing)?)(\s+(at|on))?/, 'start') + sub(/(\bend(s|ing)?|through|until)(\s+(at|on))?/, 'through') + sub(/start\s+(?:(?:this|in)\s+)?#{MONTH_OF_YEAR.source}/, 'start \1') + + sub(/from\s+now\s+(through|to|until)/, 'now through') + end + end + end +end diff --git a/lib/nickel/substitutions/standardization.rb b/lib/nickel/substitutions/standardization.rb index ab938e0..5361a7b 100644 --- a/lib/nickel/substitutions/standardization.rb +++ b/lib/nickel/substitutions/standardization.rb @@ -256,15 +256,6 @@ class Standardization sub(/([0-9])(?:\s*)p\b/, '\1pm') # allows 5p as 5pm sub(/\s+am\b/, 'am') # removes any spaces before am, shouldn't I check for preceeding digits? sub(/\s+pm\b/, 'pm') # removes any spaces before pm, shouldn't I check for preceeding digits? - - # miscellaneous - sub(/last\s+#{DAY_OF_WEEK.source}/, '5th \1') # last dayname => 5th dayname - sub(/\ba\s+(week|month|day)/, '1 \1') # a month|week|day => 1 month|week|day - sub(/^(through|until)/, 'today through') # ^through => today through - sub(/every\s*(night|morning)/, 'every day') - sub(/tonight/, 'today') - sub(/this(?:\s*)morning/, 'today') - sub(/before\s+12pm/, '6am to 12pm') # arbitrary end end end From 9e960663e301bee901d6c03b08dacb637ec33ecb Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sun, 23 Nov 2014 08:06:21 +0000 Subject: [PATCH 07/10] Made it possible to defined multiple substitution blocks --- lib/nickel/substitutor.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/nickel/substitutor.rb b/lib/nickel/substitutor.rb index da4980b..b216f38 100644 --- a/lib/nickel/substitutor.rb +++ b/lib/nickel/substitutor.rb @@ -1,11 +1,17 @@ module Nickel module Substitutor + def self.extended(mod) + mod.instance_variable_set(:@blocks, []) + end + def apply(nlp_query) - Evaluator.new(nlp_query).instance_eval(&@substitutions) + @blocks.each do |blk| + Evaluator.new(nlp_query).instance_eval(&blk) + end end def substitutions(&block) - @substitutions = block + @blocks << block end protected From 83ba70048030a4ebc5e44a76967f7289af393d41 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sun, 23 Nov 2014 09:49:31 +0000 Subject: [PATCH 08/10] Made nsub!#changed_in include the line number as well as the class --- lib/nickel/nlp_query.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nickel/nlp_query.rb b/lib/nickel/nlp_query.rb index f314a85..001d71c 100644 --- a/lib/nickel/nlp_query.rb +++ b/lib/nickel/nlp_query.rb @@ -11,6 +11,7 @@ class NLPQuery def initialize(query_str) @query_str = query_str.dup + @changed_in = [] end attr_reader :after_formatting, :changed_in @@ -42,8 +43,7 @@ def query_formatting def nsub!(*args) if m = query_str.match(args[0]) # m will now hold the FIRST set of backreferenced matches # there is at least one match - @changed_in ||= [] - @changed_in << caller[1][/(\w+)\W*$/, 1] + @changed_in << caller[1] if block_given? # query_str.gsub!(args[0]) {yield(*m.to_a[1..-1])} # There is a bug here: If gsub matches more than once, # then the first set of referenced matches will be passed to the block From f32e6df34069ec1a9e3cc7e1816114656a86bb79 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sun, 23 Nov 2014 20:16:15 +0000 Subject: [PATCH 09/10] Added some extra test cases to check the last few pre processing substitutions --- lib/nickel/substitutions/pre_processing.rb | 2 +- spec/lib/nickel_spec.rb | 82 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/lib/nickel/substitutions/pre_processing.rb b/lib/nickel/substitutions/pre_processing.rb index fab97d6..9242277 100644 --- a/lib/nickel/substitutions/pre_processing.rb +++ b/lib/nickel/substitutions/pre_processing.rb @@ -612,7 +612,7 @@ class PreProcessing # the 23rd of this|the month --> 23rd this month # this month on the 23rd --> 23rd this month sub(/(?:the\s+)?#{DATE_DD.source}\s+(?:of\s+)?(?:this|the)\s+month/, '\1 this month') - sub(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD.source}/, '\1 this month') + sub(/this\s+month\s+(?:(?:on|the)\s+)?(?:(?:on|the)\s+)?#{DATE_DD.source}(?!\s+#{DAY_OF_WEEK.source}\s+this\s+month)/, '\1 this month') # Ordinal next month: # this will slip by now diff --git a/spec/lib/nickel_spec.rb b/spec/lib/nickel_spec.rb index 90f1b6b..78a448e 100644 --- a/spec/lib/nickel_spec.rb +++ b/spec/lib/nickel_spec.rb @@ -2255,5 +2255,87 @@ end end end + + context "when the query is 'first sunday of the month'" do + let(:query) { 'first sunday of the month' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the first sunday of the current month' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :single, start_date: Nickel::ZDate.new('20141102')) + ] + end + end + end + + context "when the query is 'first sunday of the month until january 31st'" do + let(:query) { 'first sunday of the month until january 31st' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the first sunday of the current month' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :daymonthly, start_date: Nickel::ZDate.new('20141207'), end_date: Nickel::ZDate.new('20150131'), interval: 1, day_of_week: 6, week_of_month: 1) + ] + end + end + end + + context "when the query is 'the 2nd and 3rd saturday of the month'" do + let(:query) { 'the 2nd and 3rd saturday of this month' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the second and third sunday of the current month' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :single, start_date: Nickel::ZDate.new('20141108')), + Nickel::Occurrence.new(type: :single, start_date: Nickel::ZDate.new('20141115')) + ] + end + end + end + + context "when the query is 'on the 2nd and 3rd saturday of the month until january 31st" do + let(:query) { 'on the 2nd and 3rd saturday of the month until january 31st' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the second and third saturday of every month up until january' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :daymonthly, start_date: Nickel::ZDate.new('20141213'), end_date: Nickel::ZDate.new('20150131'), interval: 1, day_of_week: 5, week_of_month: 2), + Nickel::Occurrence.new(type: :daymonthly, start_date: Nickel::ZDate.new('20141220'), end_date: Nickel::ZDate.new('20150131'), interval: 1, day_of_week: 5, week_of_month: 3) + ] + end + end + end + + context "when the query is 'on the 3rd saturday and 5th tuesday of the month'" do + let(:query) { 'on the 3rd saturday and 5th tuesday of the month' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the third saturday and the last tuesday of the current month' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :single, start_date: Nickel::ZDate.new('20141115')), + Nickel::Occurrence.new(type: :single, start_date: Nickel::ZDate.new('20141125')) + ] + end + end + end + + context "when the query is 'on the 3rd saturday and 5th tuesday of the month until january 31st'" do + let(:query) { 'on the 3rd saturday and 5th tuesday of the month until january 31st' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is the third saturday and the last tuesday of every month up until january' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :daymonthly, start_date: Nickel::ZDate.new('20141220'), end_date: Nickel::ZDate.new('20150131'), interval: 1, day_of_week: 5, week_of_month: 3), + Nickel::Occurrence.new(type: :daymonthly, start_date: Nickel::ZDate.new('20141125'), end_date: Nickel::ZDate.new('20150131'), interval: 1, day_of_week: 1, week_of_month: -1) + ] + end + end + end end end From 8871c174d638db6ab95ae5fd587193463756f5a9 Mon Sep 17 00:00:00 2001 From: Iain Beeston Date: Sun, 23 Nov 2014 21:25:30 +0000 Subject: [PATCH 10/10] Added back in a substitution for every n days/weeks/months Only works for days right now - construct interpreter needs fixing for that I think --- lib/nickel/substitutions/pre_processing.rb | 13 ++++++++ spec/lib/nickel_spec.rb | 39 ++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/nickel/substitutions/pre_processing.rb b/lib/nickel/substitutions/pre_processing.rb index 9242277..ffbb2b7 100644 --- a/lib/nickel/substitutions/pre_processing.rb +++ b/lib/nickel/substitutions/pre_processing.rb @@ -180,6 +180,19 @@ class PreProcessing # "mon wed every week" --> every mon wed sub(/((#{DAY_OF_WEEK.source}(?:\s*)){1,7})(?:of\s+)?(?:every|each)(\s+other)?\s+week/, 'every \4 \1') + # "every 2|3 weeks" --> every 2nd|3rd week + sub(/(?:repeats\s+)?every\s+(\d+)\s+(day|week|month)s?/) do |m1, m2| + ordinal = case m1 + when '2' then + 'nd' + when '3' then + 'rd' + else + 'th' + end + 'every ' + m1 + ordinal + ' ' + m2 + end + # "every week on mon tue fri" --> every mon tue fri sub(/(?:repeats\s+)?every\s+(?:(other|3rd|2nd)\s+)?weeks?\s+(?:\bon\s+)?((?:#{DAY_OF_WEEK_NB.source}\s+){1,7})/, 'every \1 \2') diff --git a/spec/lib/nickel_spec.rb b/spec/lib/nickel_spec.rb index 78a448e..2678fb6 100644 --- a/spec/lib/nickel_spec.rb +++ b/spec/lib/nickel_spec.rb @@ -2337,5 +2337,44 @@ end end end + + context "when the query is 'every 3 days'" do + let(:query) { 'every 3 days' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is every 3rd day' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :daily, start_date: Nickel::ZDate.new('20141123'), interval: 3), + ] + end + end + end + + context "when the query is 'every 2 weeks'", :broken do + let(:query) { 'every 2 weeks' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is every 2nd week' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :weekly, start_date: Nickel::ZDate.new('20141123'), interval: 2), + ] + end + end + end + + context "when the query is 'every 4 months'", :broken do + let(:query) { 'every 4 months' } + let(:run_date) { Time.local(2014, 11, 23) } + + describe '#occurrences' do + it 'is every 4 months' do + expect(n.occurrences).to match_array [ + Nickel::Occurrence.new(type: :datemonthly, start_date: Nickel::ZDate.new('20141123'), interval: 1, date_of_month: 23), + ] + end + end + end end end