5 def definitions #:nodoc:
7 {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
9 :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
10 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
11 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
12 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
13 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
14 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
15 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
16 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
17 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
18 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
19 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
22 :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
23 Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
24 Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
26 # 3 weeks from now, in 2 months
27 :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
28 Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
29 Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
32 :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
33 Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
37 def tokens_to_span(tokens, options) #:nodoc:
38 # maybe it's a specific date
40 self.definitions[:date].each do |handler|
41 if handler.match(tokens, self.definitions)
42 puts "-date" if Chronic.debug
43 good_tokens = tokens.select { |o| !o.get_tag Separator }
44 return self.send(handler.handler_method, good_tokens, options)
48 # I guess it's not a specific date, maybe it's just an anchor
50 self.definitions[:anchor].each do |handler|
51 if handler.match(tokens, self.definitions)
52 puts "-anchor" if Chronic.debug
53 good_tokens = tokens.select { |o| !o.get_tag Separator }
54 return self.send(handler.handler_method, good_tokens, options)
58 # not an anchor, perhaps it's an arrow
60 self.definitions[:arrow].each do |handler|
61 if handler.match(tokens, self.definitions)
62 puts "-arrow" if Chronic.debug
63 good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
64 return self.send(handler.handler_method, good_tokens, options)
68 # not an arrow, let's hope it's a narrow
70 self.definitions[:narrow].each do |handler|
71 if handler.match(tokens, self.definitions)
72 puts "-narrow" if Chronic.debug
73 #good_tokens = tokens.select { |o| !o.get_tag Separator }
74 return self.send(handler.handler_method, tokens, options)
78 # I guess you're out of luck!
79 puts "-none" if Chronic.debug
85 def day_or_time(day_start, time_tokens, options)
86 outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
88 if !time_tokens.empty?
89 @now = outer_span.begin
90 time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
99 def handle_m_d(month, day, time_tokens, options) #:nodoc:
101 span = month.this(options[:context])
103 day_start = Time.local(span.begin.year, span.begin.month, day)
105 day_or_time(day_start, time_tokens, options)
108 def handle_rmn_sd(tokens, options) #:nodoc:
109 handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
112 def handle_rmn_od(tokens, options) #:nodoc:
113 handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
116 def handle_rmn_sy(tokens, options) #:nodoc:
117 month = tokens[0].get_tag(RepeaterMonthName).index
118 year = tokens[1].get_tag(ScalarYear).type
121 next_month_year = year + 1
124 next_month_year = year
125 next_month_month = month + 1
129 Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
135 def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
136 month = tokens[1].get_tag(RepeaterMonthName).index
137 day = tokens[2].get_tag(ScalarDay).type
138 year = tokens[5].get_tag(ScalarYear).type
141 day_start = Time.local(year, month, day)
142 day_or_time(day_start, [tokens[3]], options)
148 def handle_rmn_sd_sy(tokens, options) #:nodoc:
149 month = tokens[0].get_tag(RepeaterMonthName).index
150 day = tokens[1].get_tag(ScalarDay).type
151 year = tokens[2].get_tag(ScalarYear).type
153 time_tokens = tokens.last(tokens.size - 3)
156 day_start = Time.local(year, month, day)
157 day_or_time(day_start, time_tokens, options)
163 def handle_sd_rmn_sy(tokens, options) #:nodoc:
164 new_tokens = [tokens[1], tokens[0], tokens[2]]
165 time_tokens = tokens.last(tokens.size - 3)
166 self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
169 def handle_sm_sd_sy(tokens, options) #:nodoc:
170 month = tokens[0].get_tag(ScalarMonth).type
171 day = tokens[1].get_tag(ScalarDay).type
172 year = tokens[2].get_tag(ScalarYear).type
174 time_tokens = tokens.last(tokens.size - 3)
177 day_start = Time.local(year, month, day) #:nodoc:
178 day_or_time(day_start, time_tokens, options)
184 def handle_sd_sm_sy(tokens, options) #:nodoc:
185 new_tokens = [tokens[1], tokens[0], tokens[2]]
186 time_tokens = tokens.last(tokens.size - 3)
187 self.handle_sm_sd_sy(new_tokens + time_tokens, options)
190 def handle_sy_sm_sd(tokens, options) #:nodoc:
191 new_tokens = [tokens[1], tokens[2], tokens[0]]
192 time_tokens = tokens.last(tokens.size - 3)
193 self.handle_sm_sd_sy(new_tokens + time_tokens, options)
196 def handle_sm_sy(tokens, options) #:nodoc:
197 month = tokens[0].get_tag(ScalarMonth).type
198 year = tokens[1].get_tag(ScalarYear).type
201 next_month_year = year + 1
204 next_month_year = year
205 next_month_month = month + 1
209 Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
217 def handle_r(tokens, options) #:nodoc:
218 dd_tokens = dealias_and_disambiguate_times(tokens, options)
219 self.get_anchor(dd_tokens, options)
222 def handle_r_g_r(tokens, options) #:nodoc:
223 new_tokens = [tokens[1], tokens[0], tokens[2]]
224 self.handle_r(new_tokens, options)
229 def handle_srp(tokens, span, options) #:nodoc:
230 distance = tokens[0].get_tag(Scalar).type
231 repeater = tokens[1].get_tag(Repeater)
232 pointer = tokens[2].get_tag(Pointer).type
234 repeater.offset(span, distance, pointer)
237 def handle_s_r_p(tokens, options) #:nodoc:
238 repeater = tokens[1].get_tag(Repeater)
242 # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
243 # self.parse("this hour", :guess => false, :now => @now)
244 # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
245 # self.parse("this minute", :guess => false, :now => @now)
246 # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
247 # self.parse("this second", :guess => false, :now => @now)
249 # raise(ChronicPain, "Invalid repeater: #{repeater.class}")
252 span = self.parse("this second", :guess => false, :now => @now)
254 self.handle_srp(tokens, span, options)
257 def handle_p_s_r(tokens, options) #:nodoc:
258 new_tokens = [tokens[1], tokens[2], tokens[0]]
259 self.handle_s_r_p(new_tokens, options)
262 def handle_s_r_p_a(tokens, options) #:nodoc:
263 anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
264 self.handle_srp(tokens, anchor_span, options)
269 def handle_orr(tokens, outer_span, options) #:nodoc:
270 repeater = tokens[1].get_tag(Repeater)
271 repeater.start = outer_span.begin - 1
272 ordinal = tokens[0].get_tag(Ordinal).type
275 span = repeater.next(:future)
276 if span.begin > outer_span.end
284 def handle_o_r_s_r(tokens, options) #:nodoc:
285 outer_span = get_anchor([tokens[3]], options)
286 handle_orr(tokens[0..1], outer_span, options)
289 def handle_o_r_g_r(tokens, options) #:nodoc:
290 outer_span = get_anchor(tokens[2..3], options)
291 handle_orr(tokens[0..1], outer_span, options)
296 def get_anchor(tokens, options) #:nodoc:
297 grabber = Grabber.new(:this)
300 repeaters = self.get_repeaters(tokens)
301 repeaters.size.times { tokens.pop }
303 if tokens.first && tokens.first.get_tag(Grabber)
304 grabber = tokens.first.get_tag(Grabber)
308 head = repeaters.shift
313 outer_span = head.next(:past)
315 if repeaters.size > 0
316 outer_span = head.this(:none)
318 outer_span = head.this(options[:context])
321 outer_span = head.next(:future)
322 else raise(ChronicPain, "Invalid grabber")
325 puts "--#{outer_span}" if Chronic.debug
326 anchor = find_within(repeaters, outer_span, pointer)
329 def get_repeaters(tokens) #:nodoc:
331 tokens.each do |token|
332 if t = token.get_tag(Repeater)
336 repeaters.sort.reverse
339 # Recursively finds repeaters within other repeaters.
340 # Returns a Span representing the innermost time span
341 # or nil if no repeater union could be found
342 def find_within(tags, span, pointer) #:nodoc:
343 puts "--#{span}" if Chronic.debug
344 return span if tags.empty?
347 head.start = pointer == :future ? span.begin : span.end
350 if span.include?(h.begin) || span.include?(h.end)
351 return find_within(rest, h, pointer)
357 def dealias_and_disambiguate_times(tokens, options) #:nodoc:
358 # handle aliases of am/pm
359 # 5:00 in the morning -> 5:00 am
360 # 7:00 in the evening -> 7:00 pm
362 day_portion_index = nil
363 tokens.each_with_index do |t, i|
364 if t.get_tag(RepeaterDayPortion)
365 day_portion_index = i
371 tokens.each_with_index do |t, i|
372 if t.get_tag(RepeaterTime)
378 if (day_portion_index && time_index)
379 t1 = tokens[day_portion_index]
380 t1tag = t1.get_tag(RepeaterDayPortion)
382 if [:morning].include?(t1tag.type)
383 puts '--morning->am' if Chronic.debug
384 t1.untag(RepeaterDayPortion)
385 t1.tag(RepeaterDayPortion.new(:am))
386 elsif [:afternoon, :evening, :night].include?(t1tag.type)
387 puts "--#{t1tag.type}->pm" if Chronic.debug
388 t1.untag(RepeaterDayPortion)
389 t1.tag(RepeaterDayPortion.new(:pm))
393 # tokens.each_with_index do |t0, i|
395 # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
396 # if [:morning].include?(t1tag.type)
397 # puts '--morning->am' if Chronic.debug
398 # t1.untag(RepeaterDayPortion)
399 # t1.tag(RepeaterDayPortion.new(:am))
400 # elsif [:afternoon, :evening, :night].include?(t1tag.type)
401 # puts "--#{t1tag.type}->pm" if Chronic.debug
402 # t1.untag(RepeaterDayPortion)
403 # t1.tag(RepeaterDayPortion.new(:pm))
408 # handle ambiguous times if :ambiguous_time_range is specified
409 if options[:ambiguous_time_range] != :none
411 tokens.each_with_index do |t0, i|
414 if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
415 distoken = Token.new('disambiguator')
416 distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
428 class Handler #:nodoc:
429 attr_accessor :pattern, :handler_method
431 def initialize(pattern, handler_method)
433 @handler_method = handler_method
436 def constantize(name)
437 camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
438 ::Chronic.module_eval(camel, __FILE__, __LINE__)
441 def match(tokens, definitions)
443 @pattern.each do |element|
445 optional = name.reverse[0..0] == '?'
446 name = name.chop if optional
447 if element.instance_of? Symbol
448 klass = constantize(name)
449 match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
450 return false if !match && !optional
451 (token_index += 1; next) if match
452 next if !match && optional
453 elsif element.instance_of? String
454 return true if optional && token_index == tokens.size
455 sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
456 sub_handlers.each do |sub_handler|
457 return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
461 raise(ChronicPain, "Invalid match type: #{element.class}")
464 return false if token_index != tokens.size