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