lib/nickel/construct_finder.rb
require 'nickel/construct'
require 'nickel/zdate'
require 'nickel/ztime'
module Nickel
class ConstructFinder
attr_reader :constructs, :components
def initialize(query, curdate, curtime)
@curdate = curdate
@curtime = curtime
@components = query.split
@pos = 0 # iterator
@constructs = []
end
def run
while @pos < @components.size
big_if_on_current_word
@pos += 1
end
end
def reset_instance_vars
@day_index = nil
@month_index = nil
@week_num = nil
@date_array = nil
@length = nil
@time1 = nil
@time2 = nil
@date1 = nil
@date2 = nil
end
def big_if_on_current_word
reset_instance_vars
if match_every
if match_every_dayname
found_every_dayname # every tue
elsif match_every_day
found_every_day # every day
elsif match_every_other
if match_every_other_dayname
found_every_other_dayname # every other fri
elsif match_every_other_day
found_every_other_day # every other day
end
elsif match_every_3rd
if match_every_3rd_dayname
found_every_3rd_dayname # every third fri
elsif match_every_3rd_day
found_every_3rd_day # every third day
end
end
elsif match_repeats
if match_repeats_daily
found_repeats_daily # repeats daily
elsif match_repeats_altdaily
found_repeats_altdaily # repeats altdaily
elsif match_repeats_weekly_vague
found_repeats_weekly_vague # repeats weekly
elsif match_repeats_altweekly_vague
found_repeats_altweekly_vague # repeats altweekly
elsif match_repeats_monthly
if match_repeats_daymonthly
found_repeats_daymonthly # repeats monthly 1st fri
elsif match_repeats_datemonthly
found_repeats_datemonthly # repeats monthly 22nd
end
elsif match_repeats_altmonthly
if match_repeats_altmonthly_daymonthly
found_repeats_altmonthly_daymonthly # repeats altmonthly 1st fri
elsif match_repeats_altmonthly_datemonthly
found_repeats_altmonthly_datemonthly # repeats altmonthly 22nd
end
elsif match_repeats_threemonthly
if match_repeats_threemonthly_daymonthly
found_repeats_threemonthly_daymonthly # repeats threemonthly 1st fri
elsif match_repeats_threemonthly_datemonthly
found_repeats_threemonthly_datemonthly # repeats threemonthly 22nd
end
end
elsif match_for_x
if match_for_x_days
found_for_x_days # for 10 days
elsif match_for_x_weeks
found_for_x_weeks # for 10 weeks
elsif match_for_x_months
found_for_x_months # for 10 months
end
elsif match_this
if match_this_dayname
found_this_dayname # this fri
elsif match_this_week
found_this_week # this week
elsif match_this_month
found_this_month # this month (implies 9/1 to 9/30)
end # SHOULDN'T "this" HAVE "this weekend" ???
elsif match_next
if match_next_weekend
found_next_weekend # next weekend --- never hit?
elsif match_next_dayname
found_next_dayname # next tuesday
elsif match_next_x
if match_next_x_days
found_next_x_days # next 5 days --- shouldn't this be a wrapper?
elsif match_next_x_weeks
found_next_x_weeks # next 5 weeks --- shouldn't this be a wrapper?
elsif match_next_x_months
found_next_x_months # next 5 months --- shouldn't this be a wrapper?
elsif match_next_x_years
found_next_x_years # next 5 years --- shouldn't this be a wrapper?
end
elsif match_next_week
found_next_week
elsif match_next_month
found_next_month # next month (implies 10/1 to 10/31)
end
elsif match_week
if match_week_of_date
found_week_of_date # week of 1/2
elsif match_week_through_date
found_week_through_date # week through 1/2 (as in, week ending 1/2)
end
elsif match_x_weeks_from
if match_x_weeks_from_dayname
found_x_weeks_from_dayname # 5 weeks from tuesday
elsif match_x_weeks_from_this_dayname
found_x_weeks_from_this_dayname # 5 weeks from this tuesday
elsif match_x_weeks_from_next_dayname
found_x_weeks_from_next_dayname # 5 weeks from next tuesday
elsif match_x_weeks_from_tomorrow
found_x_weeks_from_tomorrow # 5 weeks from tomorrow
elsif match_x_weeks_from_now
found_x_weeks_from_now # 5 weeks from now
elsif match_x_weeks_from_yesterday
found_x_weeks_from_yesterday # 5 weeks from yesterday
end
elsif match_x_months_from
if match_x_months_from_dayname
found_x_months_from_dayname # 2 months from wed
elsif match_x_months_from_this_dayname
found_x_months_from_this_dayname # 2 months from this wed
elsif match_x_months_from_next_dayname
found_x_months_from_next_dayname # 2 months from next wed
elsif match_x_months_from_tomorrow
found_x_months_from_tomorrow # 2 months from tomorrow
elsif match_x_months_from_now
found_x_months_from_now # 2 months from now
elsif match_x_months_from_yesterday
found_x_months_from_yesterday # 2 months from yesterday
end
elsif match_x_days_from
if match_x_days_from_now
found_x_days_from_now # 5 days from now
elsif match_x_days_from_dayname
found_x_days_from_dayname # 5 days from monday
end
elsif match_x_dayname_from
if match_x_dayname_from_now
found_x_dayname_from_now # 2 fridays from now
elsif match_x_dayname_from_tomorrow
found_x_dayname_from_tomorrow # 2 fridays from tomorrow
elsif match_x_dayname_from_yesterday
found_x_dayname_from_yesterday # 2 fridays from yesterday
elsif match_x_dayname_from_this
found_x_dayname_from_this # 2 fridays from this one
elsif match_x_dayname_from_next
found_x_dayname_from_next # 2 fridays from next friday
end
elsif match_x_minutes_from_now
found_x_minutes_from_now # 5 minutes from now
elsif match_x_hours_from_now
found_x_hours_from_now # 5 hours from now
elsif match_ordinal_dayname
if match_ordinal_dayname_this_month
found_ordinal_dayname_this_month # 2nd friday this month
elsif match_ordinal_dayname_next_month
found_ordinal_dayname_next_month # 2nd friday next month
elsif match_ordinal_dayname_monthname
found_ordinal_dayname_monthname # 2nd friday december
end
elsif match_ordinal_this_month
found_ordinal_this_month # 28th this month
elsif match_ordinal_next_month
found_ordinal_next_month # 28th next month
elsif match_first_day
if match_first_day_this_month
found_first_day_this_month # first day this month
elsif match_first_day_next_month
found_first_day_next_month # first day next month
elsif match_first_day_monthname
found_first_day_monthname # first day january (well this is stupid, "first day of january" gets preprocessed into "1/1", so what is the point of this?)
end
elsif match_last_day
if match_last_day_this_month
found_last_day_this_month # last day this month
elsif match_last_day_next_month
found_last_day_next_month # last day next month
elsif match_last_day_monthname
found_last_day_monthname # last day november
end
elsif match_at
if match_at_time
if match_at_time_through_time
found_at_time_through_time # at 2 through 5pm
else
found_at_time # at 2
end
end
elsif match_all_day
found_all_day # all day
elsif match_tomorrow
if match_tomorrow_through
if match_tomorrow_through_dayname
found_tomorrow_through_dayname # tomorrow through friday
elsif match_tomorrow_through_date
found_tomorrow_through_date # tomorrow through august 20th
end
else
found_tomorrow # tomorrow
end
elsif match_now
if match_now_through
if match_now_through_dayname
found_now_through_dayname # today through friday
elsif match_now_through_following_dayname
found_now_through_following_dayname # REDUNDANT, PREPROCESS THIS OUT
elsif match_now_through_date
found_now_through_date # today through 10/1
elsif match_now_through_tomorrow
found_now_through_tomorrow # today through tomorrow
elsif match_now_through_next_dayname
found_now_through_next_dayname # today through next friday
end
else
found_now # today
end
elsif match_dayname
if match_dayname_the_ordinal
found_dayname_the_ordinal # monday the 21st
elsif match_dayname_x_weeks_from_next
found_dayname_x_weeks_from_next # monday 2 weeks from next
elsif match_dayname_x_weeks_from_this
found_dayname_x_weeks_from_this # monday 2 weeks from this
else
found_dayname # monday (also monday tuesday wed...)
end
elsif match_through_monthname
found_through_monthname # through december (implies through 11/30)
elsif match_monthname
found_monthname # december (implies 12/1 to 12/31)
# 5th constructor
elsif match_start
found_start
elsif match_through
found_through
elsif match_time # match time second to last
if match_time_through_time
found_time_through_time # 10 to 4
else
found_time # 10
end
elsif match_date # match date last
if match_date_through_date
found_date_through_date # 5th through the 16th
else
found_date # 5th
end
end
end # end def big_if_on_current_word
def match_every
@components[@pos] == 'every'
end
def match_every_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 1]) # if "every [day]"
end
def found_every_dayname
day_array = [@day_index]
j = 2
while @components[@pos + j] && ZDate.days_of_week.index(@components[@pos + j]) # if "every mon tue wed"
day_array << ZDate.days_of_week.index(@components[@pos + j])
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :weekly, repeats_on: day_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_every_day
@components[@pos + 1] == 'day'
end
def found_every_day
@constructs << RecurrenceConstruct.new(repeats: :daily, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_every_other
@components[@pos + 1] =~ /other|2nd/
end
def match_every_other_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 2]) # if "every other mon"
end
def found_every_other_dayname
day_array = [@day_index]
j = 3
while @components[@pos + j] && ZDate.days_of_week.index(@components[@pos + j]) # if "every other mon tue wed
day_array << ZDate.days_of_week.index(@components[@pos + j])
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :altweekly, repeats_on: day_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_every_other_day
@components[@pos + 2] == 'day' # if "every other day"
end
def found_every_other_day
@constructs << RecurrenceConstruct.new(repeats: :altdaily, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_every_3rd
@components[@pos + 1] == '3rd'
end
def match_every_3rd_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 2]) # if "every 3rd tue"
end
def found_every_3rd_dayname
day_array = [@day_index]
j = 3
while @components[@pos + j] && ZDate.days_of_week.index(@components[@pos + j]) # if "every 3rd tue wed thu
day_array << ZDate.days_of_week.index(@components[@pos + j])
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :threeweekly, repeats_on: day_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_every_3rd_day
@components[@pos + 2] == 'day' # if "every 3rd day"
end
def found_every_3rd_day
@constructs << RecurrenceConstruct.new(repeats: :threedaily, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_repeats
@components[@pos] == 'repeats'
end
def match_repeats_daily
@components[@pos + 1] == 'daily'
end
def found_repeats_daily
@constructs << RecurrenceConstruct.new(repeats: :daily, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_repeats_altdaily
@components[@pos + 1] == 'altdaily'
end
def found_repeats_altdaily
@constructs << RecurrenceConstruct.new(repeats: :altdaily, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_repeats_weekly_vague
@components[@pos + 1] == 'weekly'
end
def found_repeats_weekly_vague
@constructs << RecurrenceConstruct.new(repeats: :weekly, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_repeats_altweekly_vague
@components[@pos + 1] == 'altweekly'
end
def found_repeats_altweekly_vague
@constructs << RecurrenceConstruct.new(repeats: :altweekly, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_repeats_monthly
@components[@pos + 1] == 'monthly'
end
def match_repeats_daymonthly
@components[@pos + 2] && @components[@pos + 3] && (@week_num = @components[@pos + 2].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + 3])) # "repeats monthly 2nd wed"
end
def found_repeats_daymonthly
rep_array = [[@week_num, @day_index]] # That is NOT a typo, not sure what I meant by that! maybe the nested array
j = 4
while @components[@pos + j] && @components[@pos + j + 1] && (@week_num = @components[@pos + j].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + j + 1]))
rep_array << [@week_num, @day_index]
j += 2
end
@constructs << RecurrenceConstruct.new(repeats: :daymonthly, repeats_on: rep_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_repeats_datemonthly
@components[@pos + 2] && ConstructFinder.ordinal_only?(@components[@pos + 2]) && @date_array = [@components[@pos + 2].to_i] # repeats monthly 22nd
end
def found_repeats_datemonthly
j = 3
while @components[@pos + j] && ConstructFinder.ordinal_only?(@components[@pos + j])
@date_array << @components[@pos + j].to_i
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :datemonthly, repeats_on: @date_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_repeats_altmonthly
@components[@pos + 1] == 'altmonthly'
end
def match_repeats_altmonthly_daymonthly
@components[@pos + 2] && @components[@pos + 3] && (@week_num = @components[@pos + 2].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + 3])) # "repeats altmonthly 2nd wed"
end
def found_repeats_altmonthly_daymonthly
rep_array = [[@week_num, @day_index]]
j = 4
while @components[@pos + j] && @components[@pos + j + 1] && (@week_num = @components[@pos + j].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + j + 1]))
rep_array << [@week_num, @day_index]
j += 2
end
@constructs << RecurrenceConstruct.new(repeats: :altdaymonthly, repeats_on: rep_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_repeats_altmonthly_datemonthly
@components[@pos + 2] && ConstructFinder.ordinal_only?(@components[@pos + 2]) && @date_array = [@components[@pos + 2].to_i] # repeats altmonthly 22nd
end
def found_repeats_altmonthly_datemonthly
j = 3
while @components[@pos + j] && ConstructFinder.ordinal_only?(@components[@pos + j])
@date_array << @components[@pos + j].to_i
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :altdatemonthly, repeats_on: @date_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_repeats_threemonthly
@components[@pos + 1] == 'threemonthly'
end
def match_repeats_threemonthly_daymonthly
@components[@pos + 2] && @components[@pos + 3] && (@week_num = @components[@pos + 2].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + 3])) # "repeats threemonthly 2nd wed"
end
def found_repeats_threemonthly_daymonthly
rep_array = [[@week_num, @day_index]] # That is NOT a typo
j = 4
while @components[@pos + j] && @components[@pos + j + 1] && (@week_num = @components[@pos + j].to_i) && @week_num > 0 && @week_num <= 5 && (@day_index = ZDate.days_of_week.index(@components[@pos + j + 1]))
rep_array << [@week_num, @day_index]
j += 2
end
@constructs << RecurrenceConstruct.new(repeats: :threedaymonthly, repeats_on: rep_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_repeats_threemonthly_datemonthly
@components[@pos + 2] && ConstructFinder.ordinal_only?(@components[@pos + 2]) && @date_array = [@components[@pos + 2].to_i] # repeats threemonthly 22nd
end
def found_repeats_threemonthly_datemonthly
j = 3
while @components[@pos + j] && ConstructFinder.ordinal_only?(@components[@pos + j])
@date_array << @components[@pos + j].to_i
j += 1
end
@constructs << RecurrenceConstruct.new(repeats: :threedatemonthly, repeats_on: @date_array, comp_start: @pos, comp_end: @pos += (j - 1), found_in: __method__)
end
def match_for_x
@components[@pos] == 'for' && ConstructFinder.digits_only?(@components[@pos + 1]) && @length = @components[@pos + 1].to_i
end
def match_for_x_days
@components[@pos + 2] =~ /days?/
end
def found_for_x_days
@constructs << WrapperConstruct.new(wrapper_type: 2, wrapper_length: @length, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_for_x_weeks
@components[@pos + 2] =~ /weeks?/
end
def found_for_x_weeks
@constructs << WrapperConstruct.new(wrapper_type: 3, wrapper_length: @length, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_for_x_months
@components[@pos + 2] =~ /months?/
end
def found_for_x_months
@constructs << WrapperConstruct.new(wrapper_type: 4, wrapper_length: @length, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_this
@components[@pos] == 'this'
end
def match_this_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 1])
end
def found_this_dayname
day_to_add = @curdate.this(@day_index)
@constructs << DateConstruct.new(date: day_to_add, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
while @components[@pos + 1] && @day_index = ZDate.days_of_week.index(@components[@pos + 1])
# note @pos gets incremented on each pass
@constructs << DateConstruct.new(date: day_to_add = day_to_add.this(@day_index), comp_start: @pos + 1, comp_end: @pos += 1, found_in: __method__)
end
end
def match_this_week
@components[@pos + 1] =~ /weeks?/
end
def found_this_week
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_days(7), comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_this_month
@components[@pos + 1] =~ /months?/
end
def found_this_month
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.end_of_month, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_next
@components[@pos] == 'next'
end
def match_next_weekend
@components[@pos + 1] == 'weekend' # "next weekend"
end
def found_next_weekend
dsc = DateSpanConstruct.new(start_date: @curdate.next(5), comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
dsc.end_date = dsc.start_date.add_days(1)
@constructs << dsc
end
def match_next_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 1]) # if "next [day]"
end
def found_next_dayname
day_to_add = @curdate.next(@day_index)
@constructs << DateConstruct.new(date: day_to_add, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
while @components[@pos + 1] && @day_index = ZDate.days_of_week.index(@components[@pos + 1])
# note @pos gets incremented on each pass
@constructs << DateConstruct.new(date: day_to_add = day_to_add.this(@day_index), comp_start: @pos + 1, comp_end: @pos += 1, found_in: __method__)
end
end
def match_next_x
@components[@pos + 1] && ConstructFinder.digits_only?(@components[@pos + 1]) && @length = @components[@pos + 1].to_i
end
def match_next_x_days
@components[@pos + 2] =~ /days?/ # "next x days"
end
def found_next_x_days
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_days(@length), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_next_x_weeks
@components[@pos + 2] =~ /weeks?/ # "next x weeks"
end
def found_next_x_weeks
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_weeks(@length), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_next_x_months
@components[@pos + 2] =~ /months?/ # "next x months"
end
def found_next_x_months
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_months(@length), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_next_x_years
@components[@pos + 2] =~ /years?/ # "next x years"
end
def found_next_x_years
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_years(@length), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_next_week
@components[@pos + 1] =~ /weeks?/
end
def found_next_week
sd = @curdate.add_days(7)
ed = sd.add_days(7)
@constructs << DateSpanConstruct.new(start_date: sd, end_date: ed, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_next_month
# note it is important that all other uses of "next month" come after indicating words such as "every day next month"; otherwise they will be converted here
@components[@pos + 1] =~ /months?/
end
def found_next_month
sd = @curdate.add_months(1).beginning_of_month
ed = sd.end_of_month
@constructs << DateSpanConstruct.new(start_date: sd, end_date: ed, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_week
@components[@pos] == 'week'
end
def match_week_of_date
@components[@pos + 1] == 'of' && @date1 = ZDate.interpret(@components[@pos + 2], @curdate)
end
def found_week_of_date
@constructs << DateSpanConstruct.new(start_date: @date1, end_date: @date1.add_days(7), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_week_through_date
@components[@pos + 1] == 'through' && @date1 = ZDate.interpret(@components[@pos + 2], @curdate)
end
def found_week_through_date
@constructs << DateSpanConstruct.new(start_date: @date1.sub_days(7), end_date: @date1, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_x_weeks_from
ConstructFinder.digits_only?(@components[@pos]) && @components[@pos + 1] =~ /^weeks?$/ && @components[@pos + 2] == 'from' && @length = @components[@pos].to_i # if "x weeks from"
end
def match_x_weeks_from_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 3]) # if "x weeks from monday"
end
def found_x_weeks_from_dayname
@constructs << DateConstruct.new(date: @curdate.x_weeks_from_day(@length, @day_index), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
# Reduntant, preprocess out!
def match_x_weeks_from_this_dayname
@components[@pos + 3] == 'this' && @day_index = ZDate.days_of_week.index(@components[@pos + 4]) # if "x weeks from this monday"
end
# Reduntant, preprocess out!
def found_x_weeks_from_this_dayname
# this is the exact some construct as found_x_weeks_from_dayname, just position and comp_end has to increment by 1 more; pretty stupid, this should be caught in preprocessing
@constructs << DateConstruct.new(date: @curdate.x_weeks_from_day(@length, @day_index), comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_x_weeks_from_next_dayname
@components[@pos + 3] == 'next' && @day_index = ZDate.days_of_week.index(@components[@pos + 4]) # if "x weeks from next monday"
end
def found_x_weeks_from_next_dayname
@constructs << DateConstruct.new(date: @curdate.x_weeks_from_day(@length + 1, @day_index), comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_x_weeks_from_tomorrow
@components[@pos + 3] == 'tomorrow' # if "x weeks from tomorrow"
end
def found_x_weeks_from_tomorrow
@constructs << DateConstruct.new(date: @curdate.add_days(1).add_weeks(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_weeks_from_now
@components[@pos + 3] =~ /\b(today)|(now)\b/ # if "x weeks from today"
end
def found_x_weeks_from_now
@constructs << DateConstruct.new(date: @curdate.x_weeks_from_day(@length, @curdate.dayindex), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_weeks_from_yesterday
@components[@pos + 3] == 'yesterday' # "x weeks from yesterday"
end
def found_x_weeks_from_yesterday
@constructs << DateConstruct.new(date: @curdate.sub_days(1).add_weeks(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_months_from
ConstructFinder.digits_only?(@components[@pos]) && @components[@pos + 1] =~ /^months?$/ && @components[@pos + 2] == 'from' && @length = @components[@pos].to_i # if "x months from"
end
def match_x_months_from_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 3]) # if "x months from monday"
end
def found_x_months_from_dayname
@constructs << DateConstruct.new(date: @curdate.this(@day_index).add_months(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_months_from_this_dayname
@components[@pos + 3] == 'this' && @day_index = ZDate.days_of_week.index(@components[@pos + 4]) # if "x months from this monday"
end
def found_x_months_from_this_dayname
@constructs << DateConstruct.new(date: @curdate.this(@day_index).add_months(@length), comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_x_months_from_next_dayname
@components[@pos + 3] == 'next' && @day_index = ZDate.days_of_week.index(@components[@pos + 4]) # if "x months from next monday"
end
def found_x_months_from_next_dayname
@constructs << DateConstruct.new(date: @curdate.next(@day_index).add_months(@length), comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_x_months_from_tomorrow
@components[@pos + 3] == 'tomorrow' # if "x months from tomorrow"
end
def found_x_months_from_tomorrow
@constructs << DateConstruct.new(date: @curdate.add_days(1).add_months(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_months_from_now
@components[@pos + 3] =~ /\b(today)|(now)\b/ # if "x months from today"
end
def found_x_months_from_now
@constructs << DateConstruct.new(date: @curdate.add_months(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_months_from_yesterday
@components[@pos + 3] == 'yesterday' # "x months from yesterday"
end
def found_x_months_from_yesterday
@constructs << DateConstruct.new(date: @curdate.sub_days(1).add_months(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_days_from
ConstructFinder.digits_only?(@components[@pos]) && @components[@pos + 1] =~ /^days?$/ && @components[@pos + 2] == 'from' && @length = @components[@pos].to_i # 3 days from
end
def match_x_days_from_now
@components[@pos + 3] =~ /\b(now)|(today)\b/ # 3 days from today; 3 days from now
end
def found_x_days_from_now
@constructs << DateConstruct.new(date: @curdate.add_days(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_days_from_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 3]) # 3 days from monday, why would someone do this?
end
def found_x_days_from_dayname
@constructs << DateConstruct.new(date: @curdate.this(@day_index).add_days(@length), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_dayname_from
ConstructFinder.digits_only?(@components[@pos]) && (@day_index = ZDate.days_of_week.index(@components[@pos + 1])) && @components[@pos + 2] == 'from' && @length = @components[@pos].to_i # "2 tuesdays from"
end
def match_x_dayname_from_now
@components[@pos + 3] =~ /\b(today)|(now)\b/ # if "2 tuesdays from now"
end
def found_x_dayname_from_now
# this isn't exactly intuitive. If someone says "two tuesday from now" and it is tuesday, they mean "in two weeks." If it is not tuesday, they mean "next tuesday"
d = (@days_index == @curdate.dayindex) ? @curdate.add_weeks(@length) : @curdate.x_weeks_from_day(@length - 1, @day_index)
@constructs << DateConstruct.new(date: d, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_dayname_from_tomorrow
@components[@pos + 3] == 'tomorrow'
end
def found_x_dayname_from_tomorrow
# If someone says "two tuesday from tomorrow" and tomorrow is tuesday, they mean "two weeks from tomorrow." If it is not tuesday, this person does not make sense, but we can interpet it as "next tuesday"
tomorrow_index = (@curdate.dayindex + 1) % 7
d = (@days_index == tomorrow_index) ? @curdate.add_days(1).add_weeks(@length) : @curdate.x_weeks_from_day(@length - 1, @day_index)
@constructs << DateConstruct.new(date: d, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_dayname_from_yesterday
@components[@pos + 3] == 'yesterday'
end
def found_x_dayname_from_yesterday
# If someone says "two tuesday from yesterday" and yesterday was tuesday, they mean "two weeks from yesterday." If it is not tuesday, this person does not make sense, but we can interpet it as "next tuesday"
yesterday_index = (@curdate.dayindex == 0 ? 6 : @curdate.dayindex - 1)
d = (@days_index == yesterday_index) ? @curdate.sub_days(1).add_weeks(@length) : @curdate.x_weeks_from_day(@length - 1, @day_index)
@constructs << DateConstruct.new(date: d, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_x_dayname_from_this
@components[@pos + 3] == 'this' # "two tuesdays from this"
end
def found_x_dayname_from_this
dc = DateConstruct.new(date: @curdate.this(@day_index).add_weeks(@length), comp_start: @pos, found_in: __method__)
if @components[@post + 4] == 'one' || ZDate.days_of_week.index(@components[@pos + 4]) # talk about redundant (2 tuesdays from this one, 2 tuesdays from this tuesday)
dc.comp_end = @pos += 4
else
dc.comp_end = @pos += 3
end
@constructs << dc
end
def match_x_dayname_from_next
@components[@pos + 3] == 'next' # "two tuesdays from next"
end
def found_x_dayname_from_next
dc = DateConstruct.new(date: @curdate.next(@day_index).add_weeks(@length), comp_start: @pos, found_in: __method__)
if @components[@post + 4] == 'one' || ZDate.days_of_week.index(@components[@pos + 4]) # talk about redundant (2 tuesdays from next one, 2 tuesdays from next tuesday)
dc.comp_end = @pos += 4
else
dc.comp_end = @pos += 3
end
@constructs << dc
end
def match_x_minutes_from_now
ConstructFinder.digits_only?(@components[@pos]) && @components[@pos + 1] =~ /minutes?/ && @components[@pos + 2] == 'from' && @components[@pos + 3] =~ /^(today|now)$/ && @length = @components[@pos].to_i
end
def found_x_minutes_from_now
date = nil # define out of scope of block
time = @curtime.add_minutes(@length) { |days_to_increment| date = @curdate.add_days(days_to_increment) }
@constructs << DateConstruct.new(date: date, comp_start: @pos, comp_end: @pos + 4, found_in: __method__)
@constructs << TimeConstruct.new(time: time, comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_x_hours_from_now
ConstructFinder.digits_only?(@components[@pos]) && @components[@pos + 1] =~ /hours?/ && @components[@pos + 2] == 'from' && @components[@pos + 3] =~ /^(today|now)$/ && @length = @components[@pos].to_i
end
def found_x_hours_from_now
date = nil
time = @curtime.add_hours(@length) { |days_to_increment| date = @curdate.add_days(days_to_increment) }
@constructs << DateConstruct.new(date: date, comp_start: @pos, comp_end: @pos + 4, found_in: __method__)
@constructs << TimeConstruct.new(time: time, comp_start: @pos, comp_end: @pos += 4, found_in: __method__)
end
def match_ordinal_dayname
@components[@pos] =~ /(1st|2nd|3rd|4th|5th)/ && (@day_index = ZDate.days_of_week.index(@components[@pos + 1])) && @week_num = @components[@pos].to_i # last saturday
end
def match_ordinal_dayname_this_month
@components[@pos + 2] == 'this' && @components[@pos + 3] == 'month' # last saturday this month
end
def found_ordinal_dayname_this_month
@constructs << DateConstruct.new(date: @curdate.ordinal_dayindex(@week_num, @day_index), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_ordinal_dayname_next_month
@components[@pos + 2] == 'next' && @components[@pos + 3] == 'month' # 1st monday next month
end
def found_ordinal_dayname_next_month
@constructs << DateConstruct.new(date: @curdate.add_months(1).ordinal_dayindex(@week_num, @day_index), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_ordinal_dayname_monthname
@month_index = ZDate.months_of_year.index(@components[@pos + 2]) # second friday december
end
def found_ordinal_dayname_monthname
@constructs << DateConstruct.new(date: @curdate.jump_to_month(@month_index + 1).ordinal_dayindex(@week_num, @day_index), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_ordinal_this_month
@components[@pos] =~ /(0?[1-9]|[12][0-9]|3[01])(st|nd|rd|th)/ && @components[@pos + 1] == 'this' && @components[@pos + 2] = 'month' && @length = @components[@pos].to_i # 28th this month
end
def match_ordinal_next_month
@components[@pos] =~ /(0?[1-9]|[12][0-9]|3[01])(st|nd|rd|th)/ && @components[@pos + 1] == 'next' && @components[@pos + 2] = 'month' && @length = @components[@pos].to_i # 28th next month
end
def found_ordinal_this_month
if @curdate.day > @length
# e.g. it is the 30th of the month and a user types "1st of the month", they mean "first of next month"
date = @curdate.add_months(1).beginning_of_month.add_days(@length - 1)
else
date = @curdate.beginning_of_month.add_days(@length - 1)
end
@constructs << DateConstruct.new(date: date, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def found_ordinal_next_month
@constructs << DateConstruct.new(date: @curdate.add_months(1).beginning_of_month.add_days(@length - 1), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_first_day
@components[@pos] == '1st' && @components[@pos + 1] == 'day' # 1st day
end
def match_first_day_this_month
@components[@pos + 2] == 'this' && @components[@pos + 3] == 'month' # 1st day this month
end
def found_first_day_this_month
@constructs << DateConstruct.new(date: @curdate.beginning_of_month, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_first_day_next_month
@components[@pos + 2] == 'next' && @components[@pos + 3] == 'month' # 1st day next month
end
def found_first_day_next_month
@constructs << DateConstruct.new(date: @curdate.add_months(1).beginning_of_month, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_first_day_monthname
@month_index = ZDate.months_of_year.index(@components[@pos + 2]) # 1st day december
end
def found_first_day_monthname
@constructs << DateConstruct.new(date: @curdate.jump_to_month(@month_index + 1), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_last_day
@components[@pos] == 'last' && @components[@pos + 1] == 'day' # last day
end
def match_last_day_this_month
@components[@pos + 2] == 'this' && @components[@pos + 3] == 'month' # 1st day this month
end
def found_last_day_this_month
@constructs << DateConstruct.new(date: @curdate.end_of_month, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_last_day_next_month
@components[@pos + 2] == 'next' && @components[@pos + 3] == 'month' # 1st day next month
end
def found_last_day_next_month
@constructs << DateConstruct.new(date: @curdate.add_months(1).end_of_month, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_last_day_monthname
@month_index = ZDate.months_of_year.index(@components[@pos + 2]) # 1st day december
end
def found_last_day_monthname
@constructs << DateConstruct.new(date: @curdate.jump_to_month(@month_index + 1).end_of_month, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_at
@components[@pos] == 'at'
end
def match_at_time
@components[@pos + 1] && @time1 = ZTime.interpret(@components[@pos + 1])
end
def match_at_time_through_time
@components[@pos + 2] =~ /^(to|until|through)$/ && @components[@pos + 3] && @time2 = ZTime.interpret(@components[@pos + 3])
end
def found_at_time_through_time
@constructs << TimeSpanConstruct.new(start_time: @time1, end_time: @time2, comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def found_at_time
@constructs << TimeConstruct.new(time: @time1, comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_all_day
@components[@pos] == 'all' && @components[@pos + 1] == 'day' # all day
end
def found_all_day
@constructs << NullConstruct.new(comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_tomorrow
@components[@pos] == 'tomorrow'
end
def match_tomorrow_through
@components[@pos + 1] == 'until' || @components[@pos + 1] == 'to' || @components[@pos + 1] == 'through' # "tomorrow through"
end
def match_tomorrow_through_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 2]) # tomorrow through thursday
end
def found_tomorrow_through_dayname
@constructs << DateSpanConstruct.new(start_date: @curdate.add_days(1), end_date: @curdate.add_days(1).this(@day_index), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_tomorrow_through_date
@date1 = ZDate.interpret(@components[@pos + 2], @curdate) # tomorrow until 9/21
end
def found_tomorrow_through_date
@constructs << DateSpanConstruct.new(start_date: @curdate.add_days(1), end_date: @date1, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def found_tomorrow
@constructs << DateConstruct.new(date: @curdate.add_days(1), comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_now
@components[@pos] == 'today' || @components[@pos] == 'now'
end
def match_now_through
@components[@pos + 1] == 'until' || @components[@pos + 1] == 'to' || @components[@pos + 1] == 'through' # "today through"
end
def match_now_through_dayname
@day_index = ZDate.days_of_week.index(@components[@pos + 2]) # today through thursday
end
def found_now_through_dayname
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.this(@day_index), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
# redundant!! preprocess this out of here!
def match_now_through_following_dayname
@components[@pos + 2] =~ /following|this/ && @day_index = ZDate.days_of_week.index(@components[@pos + 3]) # today through following friday
end
# redundant!! preprocess this out of here!
def found_now_through_following_dayname
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.this(@day_index), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def match_now_through_date
@date1 = ZDate.interpret(@components[@pos + 2], @curdate) # now until 9/21
end
def found_now_through_date
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @date1, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_now_through_tomorrow
@components[@pos + 2] == 'tomorrow'
end
def found_now_through_tomorrow
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.add_days(1), comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def match_now_through_next_dayname
@components[@pos + 2] == 'next' && @day_index = ZDate.days_of_week.index(@components[@pos + 3]) # Today through next friday
end
def found_now_through_next_dayname
@constructs << DateSpanConstruct.new(start_date: @curdate, end_date: @curdate.next(@day_index), comp_start: @pos, comp_end: @pos += 3, found_in: __method__)
end
def found_now
@constructs << DateConstruct.new(date: @curdate, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_dayname
@day_index = ZDate.days_of_week.index(@components[@pos])
end
def match_dayname_the_ordinal
@components[@pos + 1] == 'the' && @date1 = ZDate.interpret(@components[@pos + 2], @curdate) # if "tue the 23rd"
end
def found_dayname_the_ordinal
# user may have specified "monday the 2nd" while in the previous month, so first check if dayname matches date.dayname, if it doesn't increment by a month and check again
if @date1.dayname == @components[@pos] || ((tmp = @date1.add_months(1)) && tmp.dayname == @components[@pos] && @date1 = tmp)
@constructs << DateConstruct.new(date: @date1, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
end
def match_dayname_x_weeks_from_this
@components[@pos + 1] && ConstructFinder.digits_only?(@components[@pos + 1]) && @components[@pos + 2] =~ /\bweeks?\b/ && @components[@pos + 3] =~ /\b(from)|(after)/ && @components[@pos + 4] == 'this' && @length = @components[@pos + 1] # "monday two weeks from this
end
def found_dayname_x_weeks_from_this
dc = DateConstruct.new(date: @curdate.this(@dayindex).add_weeks(@length), comp_start: @pos, found_in: __method__)
if ZDate.days_of_week.include?(@components[@pos + 5]) # redundant
dc.comp_end = @pos += 5
else
dc.comp_end = @pos += 4
end
@constructs << dc
end
def match_dayname_x_weeks_from_next
@components[@pos + 1] && ConstructFinder.digits_only?(@components[@pos + 1]) && @components[@pos + 2] =~ /\bweeks?\b/ && @components[@pos + 3] =~ /\b(from)|(after)/ && @components[@pos + 4] == 'next' && @length = @components[@pos + 1] # "monday two weeks from this
end
def found_dayname_x_weeks_from_next
dc = DateConstruct.new(date: @curdate.next(@dayindex).add_weeks(@length), comp_start: @pos, found_in: __method__)
if ZDate.days_of_week.include?(@components[@pos + 5]) # redundant
dc.comp_end = @pos += 5
else
dc.comp_end = @pos += 4
end
@constructs << h
end
# redundant, same as found_this_dayname
def found_dayname
day_to_add = @curdate.this(@day_index)
@constructs << DateConstruct.new(date: day_to_add, comp_start: @pos, comp_end: @pos, found_in: __method__)
while @components[@pos + 1] && @day_index = ZDate.days_of_week.index(@components[@pos + 1])
# note @pos gets incremented here:
@constructs << DateConstruct.new(date: day_to_add = day_to_add.this(@day_index), comp_start: @pos + 1, comp_end: @pos += 1, found_in: __method__)
end
end
def match_through_monthname
@components[@pos] == 'through' && @month_index = ZDate.months_of_year.index(@components[@pos + 1])
end
def found_through_monthname
# this is really a wrapper, we don't know when the start date is, so make sure @constructs gets wrapper first, as date constructs always have to appear after wrapper
@constructs << WrapperConstruct.new(wrapper_type: 1, comp_start: @pos, comp_end: @pos + 1, found_in: __method__)
@constructs << DateConstruct.new(date: @curdate.jump_to_month(@month_index + 1).sub_days(1), comp_start: @pos, comp_end: @pos += 1, found_in: __method__)
end
def match_monthname
# note it is important that all other uses of monthname come after indicating words such as "the third day of december"; otherwise they will be converted here
@month_index = ZDate.months_of_year.index(@components[@pos])
end
def found_monthname
sd = @curdate.jump_to_month(@month_index + 1)
ed = sd.end_of_month
@constructs << DateSpanConstruct.new(start_date: sd, end_date: ed, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_start
@components[@pos] == 'start'
end
def found_start
# wrapper_type 0 is a start wrapper
@constructs << WrapperConstruct.new(wrapper_type: 0, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_through
@components[@pos] == 'through'
end
def found_through
# wrapper_type 1 is an end wrapper
@constructs << WrapperConstruct.new(wrapper_type: 1, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_time
@time1 = ZTime.interpret(@components[@pos])
end
def match_time_through_time
@components[@pos + 1] =~ /^(to|through)$/ && @time2 = ZTime.interpret(@components[@pos + 2])
end
def found_time_through_time
@constructs << TimeSpanConstruct.new(start_time: @time1, end_time: @time2, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def found_time
@constructs << TimeConstruct.new(time: @time1, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
def match_date
@date1 = ZDate.interpret(@components[@pos], @curdate)
end
def match_date_through_date
@components[@pos + 1] =~ /^(through|to|until)$/ && @date2 = ZDate.interpret(@components[@pos + 2], @curdate)
end
def found_date_through_date
@constructs << DateSpanConstruct.new(start_date: @date1, end_date: @date2, comp_start: @pos, comp_end: @pos += 2, found_in: __method__)
end
def found_date
@constructs << DateConstruct.new(date: @date1, comp_start: @pos, comp_end: @pos, found_in: __method__)
end
class << self
def digits_only?(str)
str =~ /^\d+$/ # no characters other than digits
end
# valid hour, 24hour, and minute could use some cleaning
def ordinal_only?(str)
str =~ %r{^(0?[1-9]|[12][0-9]|3[01])(?:st|nd|rd|th)?$}
end
end
end # END class ConstructFinder
end