chronic 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/HISTORY.md CHANGED
@@ -1,4 +1,11 @@
1
- # 0.4.1
1
+ # 0.4.2 / 2011-06-07
2
+
3
+ * Fix MonthRepeater for fetching past month offsets when current day is
4
+ later than the last day of a past month (ie on 29th of March when parsing
5
+ `last month` Chronic would return March instead of February. Now Chronic
6
+ returns the last day of the past month)
7
+
8
+ # 0.4.1 / 2011-06-05
2
9
 
3
10
  * Fix MiniDate ranges for parsing seasons (Thomas Walpole)
4
11
 
@@ -27,7 +34,7 @@
27
34
  * Fix undefined variable in RepeaterHour (Ryan Garver)
28
35
  * Added support for parsing 'Fourty' as another mis-spelling (Lee Reilly)
29
36
  * Added ordinal format support: ie 'February 14th, 2004' (Jeff Felchner)
30
- * Fix dates when working with daylight saving times Mike Mangino
37
+ * Fix dates when working with daylight saving times (Mike Mangino)
31
38
 
32
39
  # 0.3.0 / 2010-10-22
33
40
 
data/Rakefile CHANGED
@@ -54,14 +54,6 @@ task :coverage do
54
54
  sh "open coverage/index.html"
55
55
  end
56
56
 
57
- require 'rake/rdoctask'
58
- Rake::RDocTask.new do |rdoc|
59
- rdoc.rdoc_dir = 'rdoc'
60
- rdoc.title = "#{name} #{version}"
61
- rdoc.rdoc_files.include('README*')
62
- rdoc.rdoc_files.include('lib/**/*.rb')
63
- end
64
-
65
57
  desc "Open an irb session preloaded with this library"
66
58
  task :console do
67
59
  sh "irb -Ilib -rchronic"
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'chronic'
3
- s.version = '0.4.1'
3
+ s.version = '0.4.2'
4
4
  s.rubyforge_project = 'chronic'
5
5
 
6
6
  s.summary = "Natural language date/time parsing."
@@ -1,33 +1,82 @@
1
- #=============================================================================
1
+ require 'time'
2
+ require 'date'
3
+
4
+ # Parse natural language dates and times into Time or {Chronic::Span} objects
2
5
  #
3
- # Name: Chronic
4
- # Author: Tom Preston-Werner
5
- # Purpose: Parse natural language dates and times into Time or
6
- # Chronic::Span objects
6
+ # @example
7
+ # require 'chronic'
7
8
  #
8
- #=============================================================================
9
-
9
+ # Time.now #=> Sun Aug 27 23:18:25 PDT 2006
10
+ #
11
+ # Chronic.parse('tomorrow')
12
+ # #=> Mon Aug 28 12:00:00 PDT 2006
13
+ #
14
+ # Chronic.parse('monday', :context => :past)
15
+ # #=> Mon Aug 21 12:00:00 PDT 2006
16
+ #
17
+ # Chronic.parse('this tuesday 5:00')
18
+ # #=> Tue Aug 29 17:00:00 PDT 2006
19
+ #
20
+ # Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
21
+ # #=> Tue Aug 29 05:00:00 PDT 2006
22
+ #
23
+ # Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
24
+ # #=> Sat May 27 12:00:00 PDT 2000
25
+ #
26
+ # Chronic.parse('may 27th', :guess => false)
27
+ # #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
28
+ #
29
+ # @author Tom Preston-Werner, Lee Jarvis
10
30
  module Chronic
11
- VERSION = "0.4.1"
31
+ VERSION = "0.4.2"
12
32
 
13
33
  class << self
34
+
35
+ # @return [Boolean] true when debug mode is enabled
14
36
  attr_accessor :debug
37
+
38
+ # @example
39
+ # require 'chronic'
40
+ # require 'active_support/time'
41
+ #
42
+ # Time.zone = 'UTC'
43
+ # Chronic.time_class = Time.zone
44
+ # Chronic.parse('June 15 2006 at 5:54 AM')
45
+ # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
46
+ #
47
+ # @return [Time] The time class Chronic uses internally
15
48
  attr_accessor :time_class
49
+
50
+ # The current Time Chronic is using to base from
51
+ #
52
+ # @example
53
+ # Time.now #=> 2011-06-06 14:13:43 +0100
54
+ # Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
55
+ #
56
+ # now = Time.local(2025, 12, 24)
57
+ # Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
58
+ #
59
+ # @return [Time, nil]
60
+ attr_accessor :now
16
61
  end
17
62
 
18
63
  self.debug = false
19
64
  self.time_class = Time
20
65
  end
21
66
 
22
- require 'time'
23
- require 'date'
24
-
25
67
  require 'chronic/chronic'
26
68
  require 'chronic/handlers'
27
69
  require 'chronic/mini_date'
28
70
  require 'chronic/tag'
29
71
  require 'chronic/span'
30
72
  require 'chronic/token'
73
+ require 'chronic/grabber'
74
+ require 'chronic/pointer'
75
+ require 'chronic/scalar'
76
+ require 'chronic/ordinal'
77
+ require 'chronic/separator'
78
+ require 'chronic/time_zone'
79
+ require 'chronic/numerizer'
31
80
 
32
81
  require 'chronic/repeater'
33
82
  require 'chronic/repeaters/repeater_year'
@@ -47,42 +96,6 @@ require 'chronic/repeaters/repeater_minute'
47
96
  require 'chronic/repeaters/repeater_second'
48
97
  require 'chronic/repeaters/repeater_time'
49
98
 
50
- require 'chronic/grabber'
51
- require 'chronic/pointer'
52
- require 'chronic/scalar'
53
- require 'chronic/ordinal'
54
- require 'chronic/separator'
55
- require 'chronic/time_zone'
56
-
57
- require 'chronic/numerizer'
58
-
59
- # class Time
60
- # def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
61
- # # extra_seconds = second > 60 ? second - 60 : 0
62
- # # extra_minutes = minute > 59 ? minute - 59 : 0
63
- # # extra_hours = hour > 23 ? hour - 23 : 0
64
- # # extra_days = day >
65
- #
66
- # if month > 12
67
- # if month % 12 == 0
68
- # year += (month - 12) / 12
69
- # month = 12
70
- # else
71
- # year += month / 12
72
- # month = month % 12
73
- # end
74
- # end
75
- #
76
- # base = Time.local(year, month)
77
- # puts base
78
- # offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
79
- # puts offset.to_s
80
- # date = base + offset
81
- # puts date
82
- # date
83
- # end
84
- # end
85
-
86
99
  class Time
87
100
  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
88
101
  if second >= 60
@@ -11,76 +11,76 @@ module Chronic
11
11
 
12
12
  class << self
13
13
 
14
- # Parses a string containing a natural language date or time. If the parser
15
- # can find a date or time, either a Time or Chronic::Span will be returned
16
- # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
17
- # +nil+ will be returned.
14
+ # Parses a string containing a natural language date or time
18
15
  #
19
- # Options are:
16
+ # If the parser can find a date or time, either a Time or Chronic::Span
17
+ # will be returned (depending on the value of `:guess`). If no
18
+ # date or time can be found, `nil` will be returned
20
19
  #
21
- # [<tt>:context</tt>]
22
- # <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
20
+ # @option opts [Symbol] :context (:future)
21
+ # * If your string represents a birthday, you can set `:context` to
22
+ # `:past` and if an ambiguous string is given, it will assume it is
23
+ # in the past. Specify `:future` or omit to set a future context.
23
24
  #
24
- # If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
25
- # and if an ambiguous string is given, it will assume it is in the
26
- # past. Specify <tt>:future</tt> or omit to set a future context.
25
+ # @option opts [Object] :now (Time.now)
26
+ # * By setting `:now` to a Time, all computations will be based off of
27
+ # that time instead of `Time.now`. If set to nil, Chronic will use
28
+ # `Time.now`
27
29
  #
28
- # [<tt>:now</tt>]
29
- # Time (defaults to Time.now)
30
+ # @option opts [Boolean] :guess (true)
31
+ # * By default, the parser will guess a single point in time for the
32
+ # given date or time. If you'd rather have the entire time span
33
+ # returned, set `:guess` to `false` and a {Chronic::Span} will
34
+ # be returned
30
35
  #
31
- # By setting <tt>:now</tt> to a Time, all computations will be based off
32
- # of that time instead of Time.now. If set to nil, Chronic will use Time.now.
33
- #
34
- # [<tt>:guess</tt>]
35
- # +true+ or +false+ (defaults to +true+)
36
- #
37
- # By default, the parser will guess a single point in time for the
38
- # given date or time. If you'd rather have the entire time span returned,
39
- # set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
40
- #
41
- # [<tt>:ambiguous_time_range</tt>]
42
- # Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
43
- #
44
- # If an Integer is given, ambiguous times (like 5:00) will be
36
+ # @option opts [Integer] :ambiguous_time_range (6)
37
+ # * If an Integer is given, ambiguous times (like 5:00) will be
45
38
  # assumed to be within the range of that time in the AM to that time
46
- # in the PM. For example, if you set it to <tt>7</tt>, then the parser will
47
- # look for the time between 7am and 7pm. In the case of 5:00, it would
48
- # assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
49
- # will be made, and the first matching instance of that time will
50
- # be used.
39
+ # in the PM. For example, if you set it to `7`, then the parser
40
+ # will look for the time between 7am and 7pm. In the case of 5:00, it
41
+ # would assume that means 5:00pm. If `:none` is given, no
42
+ # assumption will be made, and the first matching instance of that
43
+ # time will be used
51
44
  #
52
- # [<tt>:endian_precedence</tt>]
53
- # Array (defaults to <tt>[:middle, :little]</tt>)
54
- #
55
- # By default, Chronic will parse "03/04/2011" as the fourth day
45
+ # @option opts [Array] :endian_precedence ([:middle, :little])
46
+ # * By default, Chronic will parse "03/04/2011" as the fourth day
56
47
  # of the third month. Alternatively you can tell Chronic to parse
57
48
  # this as the third day of the fourth month by altering the
58
- # <tt>:endian_precedence</tt> to <tt>[:little, :middle]</tt>.
59
- def parse(text, specified_options = {})
49
+ # `:endian_precedence` to `[:little, :middle]`
50
+ #
51
+ # @option opts [Integer] :ambiguous_year_future_bias (50)
52
+ # * When parsing two digit years (ie 79) unlike Rubys Time class,
53
+ # Chronic will attempt to assume the full year using this figure.
54
+ # Chronic will look x amount of years into the future and past. If
55
+ # the two digit year is `now + x years` it's assumed to be the
56
+ # future, `now - x years` is assumed to be the past
57
+ #
58
+ # @return [Time, Chronic::Span, nil]
59
+ def parse(text, opts={})
60
60
  @text = text
61
- options = DEFAULT_OPTIONS.merge specified_options
61
+ options = DEFAULT_OPTIONS.merge opts
62
62
 
63
63
  # ensure the specified options are valid
64
- (specified_options.keys-DEFAULT_OPTIONS.keys).each {|key| raise(InvalidArgumentException, "#{key} is not a valid option key.")}
64
+ (opts.keys - DEFAULT_OPTIONS.keys).each do |key|
65
+ raise InvalidArgumentException, "#{key} is not a valid option key."
66
+ end
65
67
 
66
- [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
68
+ unless [:past, :future, :none].include?(options[:context])
69
+ raise InvalidArgumentException, "Invalid context, :past/:future only"
70
+ end
67
71
 
72
+ options[:text] = text
68
73
  options[:now] ||= Chronic.time_class.now
69
- @now = options[:now]
70
-
71
- # put the text into a normal format to ease scanning
72
- text = pre_normalize(text)
74
+ Chronic.now = options[:now]
73
75
 
74
76
  # tokenize words
75
- @tokens = tokenize(text, options)
77
+ tokens = tokenize(text, options)
76
78
 
77
79
  if Chronic.debug
78
- puts "+---------------------------------------------------"
79
- puts "| " + @tokens.to_s
80
- puts "+---------------------------------------------------"
80
+ puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}"
81
81
  end
82
82
 
83
- span = tokens_to_span(@tokens, options)
83
+ span = tokens_to_span(tokens, options)
84
84
 
85
85
  if options[:guess]
86
86
  guess span
@@ -89,52 +89,66 @@ module Chronic
89
89
  end
90
90
  end
91
91
 
92
- # Clean up the specified input text by stripping unwanted characters,
93
- # converting idioms to their canonical form, converting number words
94
- # to numbers (three => 3), and converting ordinal words to numeric
92
+ # Clean up the specified text ready for parsing
93
+ #
94
+ # Clean up the string by stripping unwanted characters, converting
95
+ # idioms to their canonical form, converting number words to numbers
96
+ # (three => 3), and converting ordinal words to numeric
95
97
  # ordinals (third => 3rd)
98
+ #
99
+ # @example
100
+ # Chronic.pre_normalize('first day in May')
101
+ # #=> "1st day in may"
102
+ #
103
+ # Chronic.pre_normalize('tomorrow after noon')
104
+ # #=> "next day future 12:00"
105
+ #
106
+ # Chronic.pre_normalize('one hundred and thirty six days from now')
107
+ # #=> "136 days future this second"
108
+ #
109
+ # @param [String] text The string to normalize
110
+ # @return [String] A new string ready for Chronic to parse
96
111
  def pre_normalize(text) #:nodoc:
97
- normalized_text = text.to_s.downcase
98
- normalized_text.gsub!(/['"\.,]/, '')
99
- normalized_text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
100
- normalized_text = numericize_numbers(normalized_text)
101
- normalized_text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
102
- normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
103
- normalized_text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\1')
104
- normalized_text.gsub!(/\btoday\b/, 'this day')
105
- normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
106
- normalized_text.gsub!(/\byesterday\b/, 'last day')
107
- normalized_text.gsub!(/\bnoon\b/, '12:00')
108
- normalized_text.gsub!(/\bmidnight\b/, '24:00')
109
- normalized_text.gsub!(/\bbefore now\b/, 'past')
110
- normalized_text.gsub!(/\bnow\b/, 'this second')
111
- normalized_text.gsub!(/\b(ago|before)\b/, 'past')
112
- normalized_text.gsub!(/\bthis past\b/, 'last')
113
- normalized_text.gsub!(/\bthis last\b/, 'last')
114
- normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
115
- normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
116
- normalized_text.gsub!(/\btonight\b/, 'this night')
117
- normalized_text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
118
- normalized_text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
119
- normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
120
- normalized_text
112
+ text = text.to_s.downcase
113
+ text.gsub!(/['"\.,]/, '')
114
+ text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
115
+ text = numericize_numbers(text)
116
+ text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
117
+ text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
118
+ text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\1')
119
+ text.gsub!(/\btoday\b/, 'this day')
120
+ text.gsub!(/\btomm?orr?ow\b/, 'next day')
121
+ text.gsub!(/\byesterday\b/, 'last day')
122
+ text.gsub!(/\bnoon\b/, '12:00')
123
+ text.gsub!(/\bmidnight\b/, '24:00')
124
+ text.gsub!(/\bbefore now\b/, 'past')
125
+ text.gsub!(/\bnow\b/, 'this second')
126
+ text.gsub!(/\b(ago|before)\b/, 'past')
127
+ text.gsub!(/\bthis past\b/, 'last')
128
+ text.gsub!(/\bthis last\b/, 'last')
129
+ text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
130
+ text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
131
+ text.gsub!(/\btonight\b/, 'this night')
132
+ text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
133
+ text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
134
+ text.gsub!(/\b(hence|after|from)\b/, 'future')
135
+ text
121
136
  end
122
137
 
123
- # Convert number words to numbers (three => 3)
124
- def numericize_numbers(text) #:nodoc:
138
+ # Convert number words to numbers (three => 3, fourth => 4th)
139
+ #
140
+ # @see Numerizer.numerize
141
+ # @param [String] text The string to convert
142
+ # @return [String] A new string with words converted to numbers
143
+ def numericize_numbers(text)
125
144
  Numerizer.numerize(text)
126
145
  end
127
146
 
128
- def tokenize(text, options) #:nodoc:
129
- tokens = text.split(' ').map { |word| Token.new(word) }
130
- [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
131
- tokens = tok.scan(tokens, options)
132
- end
133
- tokens.delete_if { |token| !token.tagged? }
134
- end
135
-
136
147
  # Guess a specific time within the given span
137
- def guess(span) #:nodoc:
148
+ #
149
+ # @param [Span] span
150
+ # @return [Time]
151
+ def guess(span)
138
152
  return nil if span.nil?
139
153
  if span.width > 1
140
154
  span.begin + (span.width / 2)
@@ -142,16 +156,133 @@ module Chronic
142
156
  span.begin
143
157
  end
144
158
  end
159
+
160
+ # List of {Handler} definitions. See {parse} for a list of options this
161
+ # method accepts
162
+ #
163
+ # @see parse
164
+ # @return [Hash] A Hash of Handler definitions
165
+ def definitions(options={})
166
+ options[:endian_precedence] ||= [:middle, :little]
167
+
168
+ @definitions ||= {
169
+ :time => [
170
+ Handler.new([:repeater_time, :repeater_day_portion?], nil)
171
+ ],
172
+
173
+ :date => [
174
+ Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
175
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
176
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
177
+ Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
178
+ Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
179
+ Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
180
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
181
+ Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
182
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
183
+ Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
184
+ Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
185
+ Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
186
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
187
+ ],
188
+
189
+ # tonight at 7pm
190
+ :anchor => [
191
+ Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
192
+ Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
193
+ Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
194
+ ],
195
+
196
+ # 3 weeks from now, in 2 months
197
+ :arrow => [
198
+ Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
199
+ Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
200
+ Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
201
+ ],
202
+
203
+ # 3rd week in march
204
+ :narrow => [
205
+ Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
206
+ Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
207
+ ]
208
+ }
209
+
210
+ endians = [
211
+ Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
212
+ Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
213
+ ]
214
+
215
+ case endian = Array(options[:endian_precedence]).first
216
+ when :little
217
+ @definitions[:endian] = endians.reverse
218
+ when :middle
219
+ @definitions[:endian] = endians
220
+ else
221
+ raise InvalidArgumentException, "Unknown endian option '#{endian}'"
222
+ end
223
+
224
+ @definitions
225
+ end
226
+
227
+ private
228
+
229
+ def tokenize(text, options)
230
+ text = pre_normalize(text)
231
+ tokens = text.split(' ').map { |word| Token.new(word) }
232
+ [Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
233
+ tokens = tok.scan(tokens, options)
234
+ end
235
+ tokens.select { |token| token.tagged? }
236
+ end
237
+
238
+ def tokens_to_span(tokens, options) #:nodoc:
239
+ definitions = definitions(options)
240
+
241
+ (definitions[:date] + definitions[:endian]).each do |handler|
242
+ if handler.match(tokens, definitions)
243
+ puts "-date" if Chronic.debug
244
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
245
+ return Handlers.send(handler.handler_method, good_tokens, options)
246
+ end
247
+ end
248
+
249
+ definitions[:anchor].each do |handler|
250
+ if handler.match(tokens, definitions)
251
+ puts "-anchor" if Chronic.debug
252
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
253
+ return Handlers.send(handler.handler_method, good_tokens, options)
254
+ end
255
+ end
256
+
257
+ definitions[:arrow].each do |handler|
258
+ if handler.match(tokens, definitions)
259
+ puts "-arrow" if Chronic.debug
260
+ tags = [SeparatorAt, SeparatorSlashOrDash, SeparatorComma]
261
+ good_tokens = tokens.reject { |o| tags.any? { |t| o.get_tag(t) } }
262
+ return Handlers.send(handler.handler_method, good_tokens, options)
263
+ end
264
+ end
265
+
266
+ definitions[:narrow].each do |handler|
267
+ if handler.match(tokens, definitions)
268
+ puts "-narrow" if Chronic.debug
269
+ good_tokens = tokens.select { |o| !o.get_tag Separator }
270
+ return Handlers.send(handler.handler_method, tokens, options)
271
+ end
272
+ end
273
+
274
+ puts "-none" if Chronic.debug
275
+ return nil
276
+ end
277
+
145
278
  end
146
279
 
147
280
  # Internal exception
148
- class ChronicPain < Exception #:nodoc:
149
-
281
+ class ChronicPain < Exception
150
282
  end
151
283
 
152
284
  # This exception is raised if an invalid argument is provided to
153
285
  # any of Chronic's methods
154
286
  class InvalidArgumentException < Exception
155
-
156
287
  end
157
288
  end