chronic 0.4.1 → 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 (64) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +5 -0
  3. data/.yardopts +3 -0
  4. data/HISTORY.md +75 -2
  5. data/README.md +10 -5
  6. data/Rakefile +12 -106
  7. data/chronic.gemspec +12 -78
  8. data/lib/chronic/chronic.rb +271 -101
  9. data/lib/chronic/grabber.rb +12 -3
  10. data/lib/chronic/handler.rb +90 -0
  11. data/lib/chronic/handlers.rb +247 -295
  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 +12 -13
  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 +19 -20
  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 +14 -5
  38. data/lib/chronic/token.rb +14 -4
  39. data/lib/chronic.rb +66 -89
  40. data/test/helper.rb +1 -1
  41. data/test/test_Chronic.rb +97 -3
  42. data/test/test_DaylightSavings.rb +1 -1
  43. data/test/test_Handler.rb +1 -6
  44. data/test/test_MiniDate.rb +3 -3
  45. data/test/test_Numerizer.rb +1 -1
  46. data/test/test_RepeaterDayName.rb +1 -1
  47. data/test/test_RepeaterFortnight.rb +1 -1
  48. data/test/test_RepeaterHour.rb +1 -1
  49. data/test/test_RepeaterMinute.rb +1 -1
  50. data/test/test_RepeaterMonth.rb +5 -1
  51. data/test/test_RepeaterMonthName.rb +1 -1
  52. data/test/test_RepeaterSeason.rb +40 -0
  53. data/test/test_RepeaterTime.rb +1 -1
  54. data/test/test_RepeaterWeek.rb +1 -1
  55. data/test/test_RepeaterWeekday.rb +1 -1
  56. data/test/test_RepeaterWeekend.rb +1 -1
  57. data/test/test_RepeaterYear.rb +8 -1
  58. data/test/test_Span.rb +1 -1
  59. data/test/test_Token.rb +1 -1
  60. data/test/test_parsing.rb +228 -117
  61. metadata +30 -32
  62. data/Manifest.txt +0 -59
  63. data/benchmark/benchmark.rb +0 -13
  64. data/test/test_Time.rb +0 -49
@@ -1,188 +1,109 @@
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
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)
9
11
 
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
+ day_or_time(day_start, time_tokens, options)
13
+ end
12
14
 
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
+ # 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
15
19
 
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
+ return if month_overflow?(Chronic.now.year, month.index, day)
20
21
 
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
22
+ handle_m_d(month, day, tokens[2..tokens.size], options)
23
+ end
83
24
 
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
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
90
35
  end
91
36
 
92
- # not an anchor, perhaps it's an arrow
37
+ return if month_overflow?(Chronic.now.year, month.index, day)
93
38
 
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
39
+ handle_m_d(month, day, tokens[token_range], options)
40
+ end
101
41
 
102
- # not an arrow, let's hope it's a narrow
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
103
46
 
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
47
+ return if month_overflow?(Chronic.now.year, month.index, day)
111
48
 
112
- # I guess you're out of luck!
113
- puts "-none" if Chronic.debug
114
- return nil
49
+ handle_m_d(month, day, tokens[2..tokens.size], options)
115
50
  end
116
51
 
117
- #--------------
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
118
56
 
119
- def apply_endian_precedences(precedences)
120
- date_defs = @definitions[:date]
57
+ return if month_overflow?(Chronic.now.year, month.index, day)
121
58
 
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
59
+ handle_m_d(month, day, tokens[2..tokens.size], options)
130
60
  end
131
61
 
132
- def endian_variable_name_for(e)
133
- "@#{e.to_s}_endian_handler".to_sym
134
- end
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)
135
67
 
136
- # exchange two elements in an array
137
- def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
68
+ return if month_overflow?(year, month, day)
138
69
 
139
- def day_or_time(day_start, time_tokens, options)
140
- outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
141
-
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
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
147
75
  end
148
76
  end
149
77
 
150
- #--------------
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
151
82
 
152
- def handle_m_d(month, day, time_tokens, options) #:nodoc:
153
- month.start = @now
154
- span = month.this(options[:context])
155
-
156
- day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
83
+ return if month_overflow?(Chronic.now.year, month.index, day)
157
84
 
158
- day_or_time(day_start, time_tokens, options)
85
+ handle_m_d(month, day, tokens[2..tokens.size], options)
159
86
  end
160
87
 
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:
88
+ # Handle repeater-month-name/ordinal-day with separator-on
89
+ def handle_rmn_od_on(tokens, options)
166
90
  if tokens.size > 3
167
- handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
91
+ month = tokens[2].get_tag(RepeaterMonthName)
92
+ day = tokens[3].get_tag(OrdinalDay).type
93
+ token_range = 0..1
168
94
  else
169
- handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(ScalarDay).type, tokens[0..0], options)
95
+ month = tokens[1].get_tag(RepeaterMonthName)
96
+ day = tokens[2].get_tag(OrdinalDay).type
97
+ token_range = 0..0
170
98
  end
171
- end
172
99
 
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
100
+ return if month_overflow?(Chronic.now.year, month.index, day)
176
101
 
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
102
+ handle_m_d(month, day, tokens[token_range], options)
183
103
  end
184
104
 
185
- def handle_rmn_sy(tokens, options) #:nodoc:
105
+ # Handle repeater-month-name/scalar-year
106
+ def handle_rmn_sy(tokens, options)
186
107
  month = tokens[0].get_tag(RepeaterMonthName).index
187
108
  year = tokens[1].get_tag(ScalarYear).type
188
109
 
@@ -195,24 +116,34 @@ module Chronic
195
116
  end
196
117
 
197
118
  begin
198
- Span.new(Chronic.time_class.local(year, month), Chronic.time_class.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)
199
121
  rescue ArgumentError
200
122
  nil
201
123
  end
202
124
  end
203
125
 
204
- def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
205
- t = Chronic.time_class.parse(@text)
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])
206
135
  Span.new(t, t + 1)
207
136
  end
208
137
 
209
- def handle_rmn_sd_sy(tokens, options) #:nodoc:
138
+ # Handle repeater-month-name/scalar-day/scalar-year
139
+ def handle_rmn_sd_sy(tokens, options)
210
140
  month = tokens[0].get_tag(RepeaterMonthName).index
211
141
  day = tokens[1].get_tag(ScalarDay).type
212
142
  year = tokens[2].get_tag(ScalarYear).type
213
-
214
143
  time_tokens = tokens.last(tokens.size - 3)
215
144
 
145
+ return if month_overflow?(year, month, day)
146
+
216
147
  begin
217
148
  day_start = Chronic.time_class.local(year, month, day)
218
149
  day_or_time(day_start, time_tokens, options)
@@ -221,13 +152,32 @@ module Chronic
221
152
  end
222
153
  end
223
154
 
224
- def handle_rmn_od_sy(tokens, options) #:nodoc:
155
+ # Handle repeater-month-name/ordinal-day/scalar-year
156
+ def handle_rmn_od_sy(tokens, options)
225
157
  month = tokens[0].get_tag(RepeaterMonthName).index
226
158
  day = tokens[1].get_tag(OrdinalDay).type
227
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)
228
163
 
164
+ begin
165
+ day_start = Chronic.time_class.local(year, month, day)
166
+ day_or_time(day_start, time_tokens, options)
167
+ rescue ArgumentError
168
+ nil
169
+ end
170
+ end
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
229
177
  time_tokens = tokens.last(tokens.size - 3)
230
178
 
179
+ return if month_overflow?(year, month, day)
180
+
231
181
  begin
232
182
  day_start = Chronic.time_class.local(year, month, day)
233
183
  day_or_time(day_start, time_tokens, options)
@@ -236,40 +186,46 @@ module Chronic
236
186
  end
237
187
  end
238
188
 
239
- def handle_sd_rmn_sy(tokens, options) #:nodoc:
189
+ # Handle scalar-day/repeater-month-name/scalar-year
190
+ def handle_sd_rmn_sy(tokens, options)
240
191
  new_tokens = [tokens[1], tokens[0], tokens[2]]
241
192
  time_tokens = tokens.last(tokens.size - 3)
242
- self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
193
+ handle_rmn_sd_sy(new_tokens + time_tokens, options)
243
194
  end
244
195
 
245
- def handle_sm_sd_sy(tokens, options) #:nodoc:
196
+ # Handle scalar-month/scalar-day/scalar-year (endian middle)
197
+ def handle_sm_sd_sy(tokens, options)
246
198
  month = tokens[0].get_tag(ScalarMonth).type
247
199
  day = tokens[1].get_tag(ScalarDay).type
248
200
  year = tokens[2].get_tag(ScalarYear).type
249
-
250
201
  time_tokens = tokens.last(tokens.size - 3)
251
202
 
203
+ return if month_overflow?(year, month, day)
204
+
252
205
  begin
253
- day_start = Chronic.time_class.local(year, month, day) #:nodoc:
206
+ day_start = Chronic.time_class.local(year, month, day)
254
207
  day_or_time(day_start, time_tokens, options)
255
208
  rescue ArgumentError
256
209
  nil
257
210
  end
258
211
  end
259
212
 
260
- def handle_sd_sm_sy(tokens, options) #:nodoc:
213
+ # Handle scalar-day/scalar-month/scalar-year (endian little)
214
+ def handle_sd_sm_sy(tokens, options)
261
215
  new_tokens = [tokens[1], tokens[0], tokens[2]]
262
216
  time_tokens = tokens.last(tokens.size - 3)
263
- self.handle_sm_sd_sy(new_tokens + time_tokens, options)
217
+ handle_sm_sd_sy(new_tokens + time_tokens, options)
264
218
  end
265
219
 
266
- def handle_sy_sm_sd(tokens, options) #:nodoc:
220
+ # Handle scalar-year/scalar-month/scalar-day
221
+ def handle_sy_sm_sd(tokens, options)
267
222
  new_tokens = [tokens[1], tokens[2], tokens[0]]
268
223
  time_tokens = tokens.last(tokens.size - 3)
269
- self.handle_sm_sd_sy(new_tokens + time_tokens, options)
224
+ handle_sm_sd_sy(new_tokens + time_tokens, options)
270
225
  end
271
226
 
272
- def handle_sm_sy(tokens, options) #:nodoc:
227
+ # Handle scalar-month/scalar-year
228
+ def handle_sm_sy(tokens, options)
273
229
  month = tokens[0].get_tag(ScalarMonth).type
274
230
  year = tokens[1].get_tag(ScalarYear).type
275
231
 
@@ -282,7 +238,42 @@ module Chronic
282
238
  end
283
239
 
284
240
  begin
285
- Span.new(Chronic.time_class.local(year, month), Chronic.time_class.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)
243
+ rescue ArgumentError
244
+ nil
245
+ end
246
+ end
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)
286
277
  rescue ArgumentError
287
278
  nil
288
279
  end
@@ -290,19 +281,22 @@ module Chronic
290
281
 
291
282
  # anchors
292
283
 
293
- def handle_r(tokens, options) #:nodoc:
284
+ # Handle repeaters
285
+ def handle_r(tokens, options)
294
286
  dd_tokens = dealias_and_disambiguate_times(tokens, options)
295
- self.get_anchor(dd_tokens, options)
287
+ get_anchor(dd_tokens, options)
296
288
  end
297
289
 
298
- def handle_r_g_r(tokens, options) #:nodoc:
290
+ # Handle repeater/grabber/repeater
291
+ def handle_r_g_r(tokens, options)
299
292
  new_tokens = [tokens[1], tokens[0], tokens[2]]
300
- self.handle_r(new_tokens, options)
293
+ handle_r(new_tokens, options)
301
294
  end
302
295
 
303
296
  # arrows
304
297
 
305
- def handle_srp(tokens, span, options) #:nodoc:
298
+ # Handle scalar/repeater/pointer helper
299
+ def handle_srp(tokens, span, options)
306
300
  distance = tokens[0].get_tag(Scalar).type
307
301
  repeater = tokens[1].get_tag(Repeater)
308
302
  pointer = tokens[2].get_tag(Pointer).type
@@ -310,125 +304,138 @@ module Chronic
310
304
  repeater.offset(span, distance, pointer)
311
305
  end
312
306
 
313
- def handle_s_r_p(tokens, options) #:nodoc:
307
+ # Handle scalar/repeater/pointer
308
+ def handle_s_r_p(tokens, options)
314
309
  repeater = tokens[1].get_tag(Repeater)
310
+ span = Span.new(Chronic.now, Chronic.now + 1)
315
311
 
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)
312
+ handle_srp(tokens, span, options)
331
313
  end
332
314
 
333
- def handle_p_s_r(tokens, options) #:nodoc:
315
+ # Handle pointer/scalar/repeater
316
+ def handle_p_s_r(tokens, options)
334
317
  new_tokens = [tokens[1], tokens[2], tokens[0]]
335
- self.handle_s_r_p(new_tokens, options)
318
+ handle_s_r_p(new_tokens, options)
336
319
  end
337
320
 
338
- def handle_s_r_p_a(tokens, options) #:nodoc:
321
+ # Handle scalar/repeater/pointer/anchor
322
+ def handle_s_r_p_a(tokens, options)
339
323
  anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
340
- self.handle_srp(tokens, anchor_span, options)
324
+ handle_srp(tokens, anchor_span, options)
341
325
  end
342
326
 
343
327
  # narrows
344
328
 
345
- def handle_orr(tokens, outer_span, options) #:nodoc:
329
+ # Handle oridinal repeaters
330
+ def handle_orr(tokens, outer_span, options)
346
331
  repeater = tokens[1].get_tag(Repeater)
347
332
  repeater.start = outer_span.begin - 1
348
333
  ordinal = tokens[0].get_tag(Ordinal).type
349
334
  span = nil
335
+
350
336
  ordinal.times do
351
337
  span = repeater.next(:future)
352
- if span.begin > outer_span.end
338
+
339
+ if span.begin >= outer_span.end
353
340
  span = nil
354
341
  break
355
342
  end
356
343
  end
344
+
357
345
  span
358
346
  end
359
347
 
360
- def handle_o_r_s_r(tokens, options) #:nodoc:
348
+ # Handle ordinal/repeater/separator/repeater
349
+ def handle_o_r_s_r(tokens, options)
361
350
  outer_span = get_anchor([tokens[3]], options)
362
351
  handle_orr(tokens[0..1], outer_span, options)
363
352
  end
364
353
 
365
- def handle_o_r_g_r(tokens, options) #:nodoc:
354
+ # Handle ordinal/repeater/grabber/repeater
355
+ def handle_o_r_g_r(tokens, options)
366
356
  outer_span = get_anchor(tokens[2..3], options)
367
357
  handle_orr(tokens[0..1], outer_span, options)
368
358
  end
369
359
 
370
360
  # support methods
371
361
 
372
- def get_anchor(tokens, options) #:nodoc:
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)
373
374
  grabber = Grabber.new(:this)
374
375
  pointer = :future
375
376
 
376
- repeaters = self.get_repeaters(tokens)
377
+ repeaters = get_repeaters(tokens)
377
378
  repeaters.size.times { tokens.pop }
378
379
 
379
380
  if tokens.first && tokens.first.get_tag(Grabber)
380
- grabber = tokens.first.get_tag(Grabber)
381
- tokens.pop
381
+ grabber = tokens.shift.get_tag(Grabber)
382
382
  end
383
383
 
384
384
  head = repeaters.shift
385
- head.start = @now
385
+ head.start = Chronic.now
386
386
 
387
387
  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")
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
399
+ raise ChronicPain, "Invalid grabber"
400
+ end
401
+
402
+ if Chronic.debug
403
+ puts "Handler-class: #{head.class}"
404
+ puts "--#{outer_span}"
399
405
  end
400
406
 
401
- puts "--#{outer_span}" if Chronic.debug
402
407
  find_within(repeaters, outer_span, pointer)
403
408
  end
404
409
 
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
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]
411
419
  end
412
- repeaters.sort.reverse
413
420
  end
414
421
 
415
422
  # Recursively finds repeaters within other repeaters.
416
423
  # Returns a Span representing the innermost time span
417
424
  # or nil if no repeater union could be found
418
- def find_within(tags, span, pointer) #:nodoc:
425
+ def find_within(tags, span, pointer)
419
426
  puts "--#{span}" if Chronic.debug
420
427
  return span if tags.empty?
421
428
 
422
- head, *rest = tags
423
- head.start = pointer == :future ? span.begin : span.end
429
+ head = tags.shift
430
+ head.start = (pointer == :future ? span.begin : span.end)
424
431
  h = head.this(:none)
425
432
 
426
433
  if span.cover?(h.begin) || span.cover?(h.end)
427
- find_within(rest, h, pointer)
434
+ find_within(tags, h, pointer)
428
435
  end
429
436
  end
430
437
 
431
- def dealias_and_disambiguate_times(tokens, options) #:nodoc:
438
+ def dealias_and_disambiguate_times(tokens, options)
432
439
  # handle aliases of am/pm
433
440
  # 5:00 in the morning -> 5:00 am
434
441
  # 7:00 in the evening -> 7:00 pm
@@ -449,49 +456,39 @@ module Chronic
449
456
  end
450
457
  end
451
458
 
452
- if (day_portion_index && time_index)
459
+ if day_portion_index && time_index
453
460
  t1 = tokens[day_portion_index]
454
461
  t1tag = t1.get_tag(RepeaterDayPortion)
455
462
 
456
- if [:morning].include?(t1tag.type)
463
+ case t1tag.type
464
+ when :morning
457
465
  puts '--morning->am' if Chronic.debug
458
466
  t1.untag(RepeaterDayPortion)
459
467
  t1.tag(RepeaterDayPortion.new(:am))
460
- elsif [:afternoon, :evening, :night].include?(t1tag.type)
468
+ when :afternoon, :evening, :night
461
469
  puts "--#{t1tag.type}->pm" if Chronic.debug
462
470
  t1.untag(RepeaterDayPortion)
463
471
  t1.tag(RepeaterDayPortion.new(:pm))
464
472
  end
465
473
  end
466
474
 
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
475
  # handle ambiguous times if :ambiguous_time_range is specified
483
476
  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))
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))
489
484
  distoken = Token.new('disambiguator')
485
+
490
486
  distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
491
- ttokens << distoken
487
+ ambiguous_tokens << distoken
492
488
  end
493
489
  end
494
- tokens = ttokens
490
+
491
+ tokens = ambiguous_tokens
495
492
  end
496
493
 
497
494
  tokens
@@ -499,49 +496,4 @@ module Chronic
499
496
 
500
497
  end
501
498
 
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
499
  end