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