chronic 0.1.5 → 0.6.5

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