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