chronic 0.2.2 → 0.10.2

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