chronic 0.2.2 → 0.3.0

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.
Files changed (57) hide show
  1. data/HISTORY.md +76 -0
  2. data/LICENSE +21 -0
  3. data/README.md +165 -0
  4. data/Rakefile +145 -18
  5. data/benchmark/benchmark.rb +13 -0
  6. data/chronic.gemspec +85 -0
  7. data/lib/chronic/chronic.rb +59 -49
  8. data/lib/chronic/grabber.rb +2 -2
  9. data/lib/chronic/handlers.rb +167 -112
  10. data/lib/{numerizer → chronic/numerizer}/numerizer.rb +17 -23
  11. data/lib/chronic/ordinal.rb +6 -6
  12. data/lib/chronic/pointer.rb +3 -3
  13. data/lib/chronic/repeater.rb +26 -12
  14. data/lib/chronic/repeaters/repeater_day.rb +17 -12
  15. data/lib/chronic/repeaters/repeater_day_name.rb +17 -12
  16. data/lib/chronic/repeaters/repeater_day_portion.rb +15 -14
  17. data/lib/chronic/repeaters/repeater_fortnight.rb +14 -9
  18. data/lib/chronic/repeaters/repeater_hour.rb +15 -10
  19. data/lib/chronic/repeaters/repeater_minute.rb +15 -10
  20. data/lib/chronic/repeaters/repeater_month.rb +20 -15
  21. data/lib/chronic/repeaters/repeater_month_name.rb +21 -16
  22. data/lib/chronic/repeaters/repeater_season.rb +136 -9
  23. data/lib/chronic/repeaters/repeater_season_name.rb +38 -17
  24. data/lib/chronic/repeaters/repeater_second.rb +15 -10
  25. data/lib/chronic/repeaters/repeater_time.rb +53 -43
  26. data/lib/chronic/repeaters/repeater_week.rb +16 -11
  27. data/lib/chronic/repeaters/repeater_weekday.rb +77 -0
  28. data/lib/chronic/repeaters/repeater_weekend.rb +14 -9
  29. data/lib/chronic/repeaters/repeater_year.rb +19 -13
  30. data/lib/chronic/scalar.rb +16 -14
  31. data/lib/chronic/separator.rb +25 -10
  32. data/lib/chronic/time_zone.rb +4 -3
  33. data/lib/chronic.rb +21 -19
  34. data/test/helper.rb +7 -0
  35. data/test/test_Chronic.rb +17 -18
  36. data/test/test_DaylightSavings.rb +118 -0
  37. data/test/test_Handler.rb +37 -38
  38. data/test/test_Numerizer.rb +8 -5
  39. data/test/test_RepeaterDayName.rb +15 -16
  40. data/test/test_RepeaterFortnight.rb +16 -17
  41. data/test/test_RepeaterHour.rb +18 -19
  42. data/test/test_RepeaterMinute.rb +34 -0
  43. data/test/test_RepeaterMonth.rb +16 -17
  44. data/test/test_RepeaterMonthName.rb +17 -18
  45. data/test/test_RepeaterTime.rb +20 -22
  46. data/test/test_RepeaterWeek.rb +16 -17
  47. data/test/test_RepeaterWeekday.rb +55 -0
  48. data/test/test_RepeaterWeekend.rb +21 -22
  49. data/test/test_RepeaterYear.rb +17 -18
  50. data/test/test_Span.rb +5 -6
  51. data/test/test_Time.rb +11 -12
  52. data/test/test_Token.rb +5 -6
  53. data/test/test_parsing.rb +314 -196
  54. metadata +74 -49
  55. data/History.txt +0 -49
  56. data/README.txt +0 -149
  57. data/test/suite.rb +0 -9
@@ -1,90 +1,133 @@
1
1
  module Chronic
2
2
 
3
- class << self
4
-
5
- def definitions #:nodoc:
6
- @definitions ||=
3
+ class << self
4
+
5
+ def definitions(options={}) #:nodoc:
6
+ options[:endian_precedence] = [:middle, :little] if options[:endian_precedence].nil?
7
+
8
+ # ensure the endian precedence is exactly two elements long
9
+ raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
10
+
11
+ # handler for dd/mm/yyyy
12
+ @little_endian_handler ||= Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
13
+
14
+ # handler for mm/dd/yyyy
15
+ @middle_endian_handler ||= Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy)
16
+
17
+ # ensure we have valid endian values
18
+ options[:endian_precedence].each do |e|
19
+ raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
20
+ end
21
+
22
+ @definitions ||=
7
23
  {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
8
-
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),
24
+
25
+ :date => [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),
10
26
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
11
27
  Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
12
28
  Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
29
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
13
30
  Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
31
+ Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
14
32
  Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
15
33
  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),
34
+ @middle_endian_handler,
35
+ @little_endian_handler,
18
36
  Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
19
37
  Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
20
-
38
+
21
39
  # tonight at 7pm
22
40
  :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
23
41
  Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
24
42
  Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
25
-
43
+
26
44
  # 3 weeks from now, in 2 months
27
45
  :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
28
46
  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
29
47
  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
30
-
48
+
31
49
  # 3rd week in march
32
50
  :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
33
51
  Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
34
52
  }
53
+
54
+ apply_endian_precedences(options[:endian_precedence])
55
+
56
+ @definitions
35
57
  end
36
-
37
- def tokens_to_span(tokens, options) #:nodoc:
58
+
59
+ def tokens_to_span(tokens, options) #:nodoc:
38
60
  # maybe it's a specific date
39
-
40
- self.definitions[:date].each do |handler|
41
- if handler.match(tokens, self.definitions)
61
+
62
+ definitions = self.definitions(options)
63
+ definitions[:date].each do |handler|
64
+ if handler.match(tokens, definitions)
42
65
  puts "-date" if Chronic.debug
43
66
  good_tokens = tokens.select { |o| !o.get_tag Separator }
44
67
  return self.send(handler.handler_method, good_tokens, options)
45
68
  end
46
69
  end
47
-
70
+
48
71
  # I guess it's not a specific date, maybe it's just an anchor
49
-
50
- self.definitions[:anchor].each do |handler|
51
- if handler.match(tokens, self.definitions)
72
+
73
+ definitions[:anchor].each do |handler|
74
+ if handler.match(tokens, definitions)
52
75
  puts "-anchor" if Chronic.debug
53
76
  good_tokens = tokens.select { |o| !o.get_tag Separator }
54
77
  return self.send(handler.handler_method, good_tokens, options)
55
78
  end
56
79
  end
57
-
80
+
58
81
  # not an anchor, perhaps it's an arrow
59
-
60
- self.definitions[:arrow].each do |handler|
61
- if handler.match(tokens, self.definitions)
82
+
83
+ definitions[:arrow].each do |handler|
84
+ if handler.match(tokens, definitions)
62
85
  puts "-arrow" if Chronic.debug
63
86
  good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
64
87
  return self.send(handler.handler_method, good_tokens, options)
65
88
  end
66
89
  end
67
-
90
+
68
91
  # not an arrow, let's hope it's a narrow
69
-
70
- self.definitions[:narrow].each do |handler|
71
- if handler.match(tokens, self.definitions)
92
+
93
+ definitions[:narrow].each do |handler|
94
+ if handler.match(tokens, definitions)
72
95
  puts "-narrow" if Chronic.debug
73
96
  #good_tokens = tokens.select { |o| !o.get_tag Separator }
74
97
  return self.send(handler.handler_method, tokens, options)
75
98
  end
76
99
  end
77
-
100
+
78
101
  # I guess you're out of luck!
79
102
  puts "-none" if Chronic.debug
80
103
  return nil
81
104
  end
82
-
105
+
83
106
  #--------------
84
-
107
+
108
+ def apply_endian_precedences(precedences)
109
+ date_defs = @definitions[:date]
110
+
111
+ # map the precedence array to indices on @definitions[:date]
112
+ indices = precedences.map { |e|
113
+ handler = instance_variable_get(endian_variable_name_for(e))
114
+ date_defs.index(handler)
115
+ }
116
+
117
+ # swap the handlers if we discover they are at odds with the desired preferences
118
+ swap(date_defs, indices.first, indices.last) if indices.first > indices.last
119
+ end
120
+
121
+ def endian_variable_name_for(e)
122
+ "@#{e.to_s}_endian_handler".to_sym
123
+ end
124
+
125
+ # exchange two elements in an array
126
+ def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
127
+
85
128
  def day_or_time(day_start, time_tokens, options)
86
129
  outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
87
-
130
+
88
131
  if !time_tokens.empty?
89
132
  @now = outer_span.begin
90
133
  time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
@@ -93,30 +136,46 @@ module Chronic
93
136
  return outer_span
94
137
  end
95
138
  end
96
-
139
+
97
140
  #--------------
98
-
141
+
99
142
  def handle_m_d(month, day, time_tokens, options) #:nodoc:
100
143
  month.start = @now
101
144
  span = month.this(options[:context])
102
-
103
- day_start = Time.local(span.begin.year, span.begin.month, day)
104
-
145
+
146
+ day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
147
+
105
148
  day_or_time(day_start, time_tokens, options)
106
149
  end
107
-
150
+
108
151
  def handle_rmn_sd(tokens, options) #:nodoc:
109
152
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
110
153
  end
111
-
154
+
155
+ def handle_rmn_sd_on(tokens, options) #:nodoc:
156
+ if tokens.size > 3
157
+ handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
158
+ else
159
+ handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options)
160
+ end
161
+ end
162
+
112
163
  def handle_rmn_od(tokens, options) #:nodoc:
113
164
  handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
114
165
  end
115
-
166
+
167
+ def handle_rmn_od_on(tokens, options) #:nodoc:
168
+ if tokens.size > 3
169
+ handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
170
+ else
171
+ handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options)
172
+ end
173
+ end
174
+
116
175
  def handle_rmn_sy(tokens, options) #:nodoc:
117
176
  month = tokens[0].get_tag(RepeaterMonthName).index
118
177
  year = tokens[1].get_tag(ScalarYear).type
119
-
178
+
120
179
  if month == 12
121
180
  next_month_year = year + 1
122
181
  next_month_month = 1
@@ -124,79 +183,71 @@ module Chronic
124
183
  next_month_year = year
125
184
  next_month_month = month + 1
126
185
  end
127
-
186
+
128
187
  begin
129
- Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
188
+ Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
130
189
  rescue ArgumentError
131
190
  nil
132
191
  end
133
192
  end
134
-
193
+
135
194
  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
139
-
140
- begin
141
- day_start = Time.local(year, month, day)
142
- day_or_time(day_start, [tokens[3]], options)
143
- rescue ArgumentError
144
- nil
145
- end
195
+ t = Chronic.time_class.parse(@text)
196
+ Span.new(t, t + 1)
146
197
  end
147
-
198
+
148
199
  def handle_rmn_sd_sy(tokens, options) #:nodoc:
149
200
  month = tokens[0].get_tag(RepeaterMonthName).index
150
201
  day = tokens[1].get_tag(ScalarDay).type
151
202
  year = tokens[2].get_tag(ScalarYear).type
152
-
203
+
153
204
  time_tokens = tokens.last(tokens.size - 3)
154
-
205
+
155
206
  begin
156
- day_start = Time.local(year, month, day)
207
+ day_start = Chronic.time_class.local(year, month, day)
157
208
  day_or_time(day_start, time_tokens, options)
158
209
  rescue ArgumentError
159
210
  nil
160
211
  end
161
212
  end
162
-
213
+
163
214
  def handle_sd_rmn_sy(tokens, options) #:nodoc:
164
215
  new_tokens = [tokens[1], tokens[0], tokens[2]]
165
216
  time_tokens = tokens.last(tokens.size - 3)
166
217
  self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
167
218
  end
168
-
219
+
169
220
  def handle_sm_sd_sy(tokens, options) #:nodoc:
170
221
  month = tokens[0].get_tag(ScalarMonth).type
171
222
  day = tokens[1].get_tag(ScalarDay).type
172
223
  year = tokens[2].get_tag(ScalarYear).type
173
-
224
+
174
225
  time_tokens = tokens.last(tokens.size - 3)
175
-
226
+
176
227
  begin
177
- day_start = Time.local(year, month, day) #:nodoc:
228
+ day_start = Chronic.time_class.local(year, month, day) #:nodoc:
178
229
  day_or_time(day_start, time_tokens, options)
179
230
  rescue ArgumentError
180
231
  nil
181
232
  end
182
233
  end
183
-
234
+
184
235
  def handle_sd_sm_sy(tokens, options) #:nodoc:
185
236
  new_tokens = [tokens[1], tokens[0], tokens[2]]
186
237
  time_tokens = tokens.last(tokens.size - 3)
187
238
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
188
239
  end
189
-
240
+
190
241
  def handle_sy_sm_sd(tokens, options) #:nodoc:
191
242
  new_tokens = [tokens[1], tokens[2], tokens[0]]
192
243
  time_tokens = tokens.last(tokens.size - 3)
193
244
  self.handle_sm_sd_sy(new_tokens + time_tokens, options)
194
245
  end
195
-
246
+
196
247
  def handle_sm_sy(tokens, options) #:nodoc:
197
248
  month = tokens[0].get_tag(ScalarMonth).type
198
249
  year = tokens[1].get_tag(ScalarYear).type
199
-
250
+
200
251
  if month == 12
201
252
  next_month_year = year + 1
202
253
  next_month_month = 1
@@ -204,40 +255,40 @@ module Chronic
204
255
  next_month_year = year
205
256
  next_month_month = month + 1
206
257
  end
207
-
258
+
208
259
  begin
209
- Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
260
+ Span.new(Chronic.time_class.local(year, month), Chronic.time_class.local(next_month_year, next_month_month))
210
261
  rescue ArgumentError
211
262
  nil
212
263
  end
213
264
  end
214
-
265
+
215
266
  # anchors
216
-
267
+
217
268
  def handle_r(tokens, options) #:nodoc:
218
269
  dd_tokens = dealias_and_disambiguate_times(tokens, options)
219
270
  self.get_anchor(dd_tokens, options)
220
271
  end
221
-
272
+
222
273
  def handle_r_g_r(tokens, options) #:nodoc:
223
274
  new_tokens = [tokens[1], tokens[0], tokens[2]]
224
275
  self.handle_r(new_tokens, options)
225
276
  end
226
-
277
+
227
278
  # arrows
228
-
279
+
229
280
  def handle_srp(tokens, span, options) #:nodoc:
230
281
  distance = tokens[0].get_tag(Scalar).type
231
282
  repeater = tokens[1].get_tag(Repeater)
232
283
  pointer = tokens[2].get_tag(Pointer).type
233
-
284
+
234
285
  repeater.offset(span, distance, pointer)
235
286
  end
236
-
287
+
237
288
  def handle_s_r_p(tokens, options) #:nodoc:
238
289
  repeater = tokens[1].get_tag(Repeater)
239
-
240
- # span =
290
+
291
+ # span =
241
292
  # case true
242
293
  # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
243
294
  # self.parse("this hour", :guess => false, :now => @now)
@@ -248,24 +299,24 @@ module Chronic
248
299
  # else
249
300
  # raise(ChronicPain, "Invalid repeater: #{repeater.class}")
250
301
  # end
251
-
302
+
252
303
  span = self.parse("this second", :guess => false, :now => @now)
253
-
304
+
254
305
  self.handle_srp(tokens, span, options)
255
306
  end
256
-
307
+
257
308
  def handle_p_s_r(tokens, options) #:nodoc:
258
309
  new_tokens = [tokens[1], tokens[2], tokens[0]]
259
310
  self.handle_s_r_p(new_tokens, options)
260
311
  end
261
-
312
+
262
313
  def handle_s_r_p_a(tokens, options) #:nodoc:
263
314
  anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
264
315
  self.handle_srp(tokens, anchor_span, options)
265
316
  end
266
-
317
+
267
318
  # narrows
268
-
319
+
269
320
  def handle_orr(tokens, outer_span, options) #:nodoc:
270
321
  repeater = tokens[1].get_tag(Repeater)
271
322
  repeater.start = outer_span.begin - 1
@@ -280,34 +331,34 @@ module Chronic
280
331
  end
281
332
  span
282
333
  end
283
-
334
+
284
335
  def handle_o_r_s_r(tokens, options) #:nodoc:
285
336
  outer_span = get_anchor([tokens[3]], options)
286
337
  handle_orr(tokens[0..1], outer_span, options)
287
338
  end
288
-
339
+
289
340
  def handle_o_r_g_r(tokens, options) #:nodoc:
290
341
  outer_span = get_anchor(tokens[2..3], options)
291
342
  handle_orr(tokens[0..1], outer_span, options)
292
343
  end
293
-
344
+
294
345
  # support methods
295
-
346
+
296
347
  def get_anchor(tokens, options) #:nodoc:
297
348
  grabber = Grabber.new(:this)
298
349
  pointer = :future
299
-
350
+
300
351
  repeaters = self.get_repeaters(tokens)
301
352
  repeaters.size.times { tokens.pop }
302
-
353
+
303
354
  if tokens.first && tokens.first.get_tag(Grabber)
304
355
  grabber = tokens.first.get_tag(Grabber)
305
356
  tokens.pop
306
357
  end
307
-
358
+
308
359
  head = repeaters.shift
309
360
  head.start = @now
310
-
361
+
311
362
  case grabber.type
312
363
  when :last
313
364
  outer_span = head.next(:past)
@@ -321,11 +372,11 @@ module Chronic
321
372
  outer_span = head.next(:future)
322
373
  else raise(ChronicPain, "Invalid grabber")
323
374
  end
324
-
375
+
325
376
  puts "--#{outer_span}" if Chronic.debug
326
377
  anchor = find_within(repeaters, outer_span, pointer)
327
378
  end
328
-
379
+
329
380
  def get_repeaters(tokens) #:nodoc:
330
381
  repeaters = []
331
382
  tokens.each do |token|
@@ -335,30 +386,30 @@ module Chronic
335
386
  end
336
387
  repeaters.sort.reverse
337
388
  end
338
-
389
+
339
390
  # Recursively finds repeaters within other repeaters.
340
391
  # Returns a Span representing the innermost time span
341
392
  # or nil if no repeater union could be found
342
393
  def find_within(tags, span, pointer) #:nodoc:
343
394
  puts "--#{span}" if Chronic.debug
344
395
  return span if tags.empty?
345
-
396
+
346
397
  head, *rest = tags
347
398
  head.start = pointer == :future ? span.begin : span.end
348
399
  h = head.this(:none)
349
-
400
+
350
401
  if span.include?(h.begin) || span.include?(h.end)
351
402
  return find_within(rest, h, pointer)
352
403
  else
353
404
  return nil
354
405
  end
355
406
  end
356
-
407
+
357
408
  def dealias_and_disambiguate_times(tokens, options) #:nodoc:
358
409
  # handle aliases of am/pm
359
410
  # 5:00 in the morning -> 5:00 am
360
411
  # 7:00 in the evening -> 7:00 pm
361
-
412
+
362
413
  day_portion_index = nil
363
414
  tokens.each_with_index do |t, i|
364
415
  if t.get_tag(RepeaterDayPortion)
@@ -366,7 +417,7 @@ module Chronic
366
417
  break
367
418
  end
368
419
  end
369
-
420
+
370
421
  time_index = nil
371
422
  tokens.each_with_index do |t, i|
372
423
  if t.get_tag(RepeaterTime)
@@ -374,11 +425,11 @@ module Chronic
374
425
  break
375
426
  end
376
427
  end
377
-
428
+
378
429
  if (day_portion_index && time_index)
379
430
  t1 = tokens[day_portion_index]
380
431
  t1tag = t1.get_tag(RepeaterDayPortion)
381
-
432
+
382
433
  if [:morning].include?(t1tag.type)
383
434
  puts '--morning->am' if Chronic.debug
384
435
  t1.untag(RepeaterDayPortion)
@@ -389,7 +440,7 @@ module Chronic
389
440
  t1.tag(RepeaterDayPortion.new(:pm))
390
441
  end
391
442
  end
392
-
443
+
393
444
  # tokens.each_with_index do |t0, i|
394
445
  # t1 = tokens[i + 1]
395
446
  # if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
@@ -404,7 +455,7 @@ module Chronic
404
455
  # end
405
456
  # end
406
457
  # end
407
-
458
+
408
459
  # handle ambiguous times if :ambiguous_time_range is specified
409
460
  if options[:ambiguous_time_range] != :none
410
461
  ttokens = []
@@ -419,25 +470,25 @@ module Chronic
419
470
  end
420
471
  tokens = ttokens
421
472
  end
422
-
473
+
423
474
  tokens
424
475
  end
425
-
476
+
426
477
  end
427
-
478
+
428
479
  class Handler #:nodoc:
429
480
  attr_accessor :pattern, :handler_method
430
-
481
+
431
482
  def initialize(pattern, handler_method)
432
483
  @pattern = pattern
433
484
  @handler_method = handler_method
434
485
  end
435
-
486
+
436
487
  def constantize(name)
437
488
  camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
438
489
  ::Chronic.module_eval(camel, __FILE__, __LINE__)
439
490
  end
440
-
491
+
441
492
  def match(tokens, definitions)
442
493
  token_index = 0
443
494
  @pattern.each do |element|
@@ -464,6 +515,10 @@ module Chronic
464
515
  return false if token_index != tokens.size
465
516
  return true
466
517
  end
518
+
519
+ def ==(other)
520
+ self.pattern == other.pattern
521
+ end
467
522
  end
468
-
469
- end
523
+
524
+ end
@@ -44,32 +44,34 @@ class Numerizer
44
44
  ['trillion', 1_000_000_000_000],
45
45
  ]
46
46
 
47
- class << self
48
- def numerize(string)
47
+ def self.numerize(string)
49
48
  string = string.dup
50
-
49
+
51
50
  # preprocess
52
- string.gsub!(/ +|([^\d])-([^d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
51
+ string.gsub!(/ +|([^\d])-([^\d])/, '\1 \2') # will mutilate hyphenated-words but shouldn't matter for date extraction
53
52
  string.gsub!(/a half/, 'haAlf') # take the 'a' out so it doesn't turn into a 1, save the half for the end
54
53
 
55
54
  # easy/direct replacements
56
-
55
+
57
56
  DIRECT_NUMS.each do |dn|
58
- string.gsub!(/#{dn[0]}/i, dn[1])
57
+ string.gsub!(/#{dn[0]}/i, '<num>' + dn[1])
59
58
  end
60
59
 
61
60
  # ten, twenty, etc.
62
61
 
63
62
  TEN_PREFIXES.each do |tp|
64
- string.gsub!(/(?:#{tp[0]})( *\d(?=[^\d]|$))*/i) { (tp[1] + $1.to_i).to_s }
63
+ string.gsub!(/(?:#{tp[0]}) *<num>(\d(?=[^\d]|$))*/i) { '<num>' + (tp[1] + $1.to_i).to_s }
64
+ end
65
+
66
+ TEN_PREFIXES.each do |tp|
67
+ string.gsub!(/#{tp[0]}/i) { '<num>' + tp[1].to_s }
65
68
  end
66
69
 
67
70
  # hundreds, thousands, millions, etc.
68
71
 
69
72
  BIG_PREFIXES.each do |bp|
70
- string.gsub!(/(\d*) *#{bp[0]}/i) { (bp[1] * $1.to_i).to_s}
73
+ string.gsub!(/(?:<num>)?(\d*) *#{bp[0]}/i) { '<num>' + (bp[1] * $1.to_i).to_s}
71
74
  andition(string)
72
- #combine_numbers(string) # Should to be more efficient way to do this
73
75
  end
74
76
 
75
77
  # fractional addition
@@ -77,27 +79,19 @@ class << self
77
79
  # (with extraneous .0's and such )
78
80
  string.gsub!(/(\d+)(?: | and |-)*haAlf/i) { ($1.to_f + 0.5).to_s }
79
81
 
80
- string
82
+ string.gsub(/<num>/, '')
81
83
  end
82
84
 
83
- private
84
- def andition(string)
85
+ private
86
+
87
+ def self.andition(string)
85
88
  sc = StringScanner.new(string)
86
- while(sc.scan_until(/(\d+)( | and )(\d+)(?=[^\w]|$)/i))
89
+ while(sc.scan_until(/<num>(\d+)( | and )<num>(\d+)(?=[^\w]|$)/i))
87
90
  if sc[2] =~ /and/ || sc[1].size > sc[3].size
88
- string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[3].to_i).to_s
91
+ string[(sc.pos - sc.matched_size)..(sc.pos-1)] = '<num>' + (sc[1].to_i + sc[3].to_i).to_s
89
92
  sc.reset
90
93
  end
91
94
  end
92
95
  end
93
96
 
94
- # def combine_numbers(string)
95
- # sc = StringScanner.new(string)
96
- # while(sc.scan_until(/(\d+)(?: | and |-)(\d+)(?=[^\w]|$)/i))
97
- # string[(sc.pos - sc.matched_size)..(sc.pos-1)] = (sc[1].to_i + sc[2].to_i).to_s
98
- # sc.reset
99
- # end
100
- # end
101
-
102
97
  end
103
- end
@@ -9,32 +9,32 @@ module Chronic
9
9
  end
10
10
  tokens
11
11
  end
12
-
12
+
13
13
  def self.scan_for_ordinals(token)
14
14
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
15
15
  return Ordinal.new($1.to_i)
16
16
  end
17
17
  return nil
18
18
  end
19
-
19
+
20
20
  def self.scan_for_days(token)
21
21
  if token.word =~ /^(\d*)(st|nd|rd|th)$/
22
- unless $1.to_i > 31
22
+ unless $1.to_i > 31 || $1.to_i < 1
23
23
  return OrdinalDay.new(token.word.to_i)
24
24
  end
25
25
  end
26
26
  return nil
27
27
  end
28
-
28
+
29
29
  def to_s
30
30
  'ordinal'
31
31
  end
32
32
  end
33
-
33
+
34
34
  class OrdinalDay < Ordinal #:nodoc:
35
35
  def to_s
36
36
  super << '-day-' << @type.to_s
37
37
  end
38
38
  end
39
39
 
40
- end
40
+ end