chronic 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gemtest +0 -0
- data/.gitignore +5 -0
- data/.yardopts +3 -0
- data/HISTORY.md +36 -2
- data/Manifest.txt +6 -2
- data/README.md +8 -2
- data/Rakefile +9 -101
- data/chronic.gemspec +12 -78
- data/lib/chronic/chronic.rb +225 -91
- data/lib/chronic/grabber.rb +12 -3
- data/lib/chronic/handlers.rb +94 -198
- data/lib/chronic/mini_date.rb +6 -2
- data/lib/chronic/ordinal.rb +15 -4
- data/lib/chronic/pointer.rb +12 -3
- data/lib/chronic/repeater.rb +28 -9
- data/lib/chronic/repeaters/repeater_day.rb +0 -1
- data/lib/chronic/repeaters/repeater_day_name.rb +0 -1
- data/lib/chronic/repeaters/repeater_day_portion.rb +0 -1
- data/lib/chronic/repeaters/repeater_fortnight.rb +0 -1
- data/lib/chronic/repeaters/repeater_hour.rb +0 -1
- data/lib/chronic/repeaters/repeater_minute.rb +0 -1
- data/lib/chronic/repeaters/repeater_month.rb +13 -2
- data/lib/chronic/repeaters/repeater_month_name.rb +13 -21
- data/lib/chronic/repeaters/repeater_season.rb +0 -19
- data/lib/chronic/repeaters/repeater_season_name.rb +0 -2
- data/lib/chronic/repeaters/repeater_second.rb +0 -1
- data/lib/chronic/repeaters/repeater_time.rb +1 -2
- data/lib/chronic/repeaters/repeater_week.rb +0 -1
- data/lib/chronic/repeaters/repeater_weekday.rb +0 -1
- data/lib/chronic/repeaters/repeater_weekend.rb +0 -1
- data/lib/chronic/repeaters/repeater_year.rb +19 -8
- data/lib/chronic/scalar.rb +29 -1
- data/lib/chronic/season.rb +37 -0
- data/lib/chronic/separator.rb +24 -7
- data/lib/chronic/tag.rb +10 -1
- data/lib/chronic/time_zone.rb +12 -3
- data/lib/chronic/token.rb +14 -4
- data/lib/chronic.rb +62 -49
- data/test/test_Chronic.rb +52 -2
- data/test/test_RepeaterMonth.rb +4 -0
- data/test/test_RepeaterSeason.rb +40 -0
- data/test/test_RepeaterYear.rb +7 -0
- data/test/test_Time.rb +1 -1
- data/test/test_parsing.rb +159 -113
- metadata +31 -29
- data/benchmark/benchmark.rb +0 -13
data/lib/chronic/handlers.rb
CHANGED
|
@@ -1,156 +1,10 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
+
module Handlers
|
|
3
|
+
module_function
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def definitions(options={}) #:nodoc:
|
|
6
|
-
options[:endian_precedence] ||= [:middle, :little]
|
|
7
|
-
# ensure the endian precedence is exactly two elements long
|
|
8
|
-
raise ChronicPain, "More than two elements specified for endian precedence array" unless options[:endian_precedence].length == 2
|
|
9
|
-
|
|
10
|
-
# handler for dd/mm/yyyy
|
|
11
|
-
@little_endian_handler ||= Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
|
|
12
|
-
|
|
13
|
-
# handler for mm/dd/yyyy
|
|
14
|
-
@middle_endian_handler ||= Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy)
|
|
15
|
-
|
|
16
|
-
# ensure we have valid endian values
|
|
17
|
-
options[:endian_precedence].each do |e|
|
|
18
|
-
raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
@definitions ||= {
|
|
22
|
-
:time => [
|
|
23
|
-
Handler.new([:repeater_time, :repeater_day_portion?], nil)
|
|
24
|
-
],
|
|
25
|
-
|
|
26
|
-
:date => [
|
|
27
|
-
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
|
|
28
|
-
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
|
29
|
-
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
|
|
30
|
-
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
|
31
|
-
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
|
|
32
|
-
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
|
33
|
-
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
|
|
34
|
-
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
|
35
|
-
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
|
36
|
-
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
|
37
|
-
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
|
38
|
-
@middle_endian_handler,
|
|
39
|
-
@little_endian_handler,
|
|
40
|
-
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
|
41
|
-
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
|
|
42
|
-
],
|
|
43
|
-
|
|
44
|
-
# tonight at 7pm
|
|
45
|
-
:anchor => [
|
|
46
|
-
Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
47
|
-
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
48
|
-
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
|
|
49
|
-
],
|
|
50
|
-
|
|
51
|
-
# 3 weeks from now, in 2 months
|
|
52
|
-
:arrow => [
|
|
53
|
-
Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
|
54
|
-
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
|
55
|
-
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
|
|
56
|
-
],
|
|
57
|
-
|
|
58
|
-
# 3rd week in march
|
|
59
|
-
:narrow => [
|
|
60
|
-
Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
|
61
|
-
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
apply_endian_precedences(options[:endian_precedence])
|
|
66
|
-
|
|
67
|
-
@definitions
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def tokens_to_span(tokens, options) #:nodoc:
|
|
71
|
-
# maybe it's a specific date
|
|
72
|
-
|
|
73
|
-
definitions = self.definitions(options)
|
|
74
|
-
definitions[:date].each do |handler|
|
|
75
|
-
if handler.match(tokens, definitions)
|
|
76
|
-
puts "-date" if Chronic.debug
|
|
77
|
-
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
78
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# I guess it's not a specific date, maybe it's just an anchor
|
|
83
|
-
|
|
84
|
-
definitions[:anchor].each do |handler|
|
|
85
|
-
if handler.match(tokens, definitions)
|
|
86
|
-
puts "-anchor" if Chronic.debug
|
|
87
|
-
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
88
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# not an anchor, perhaps it's an arrow
|
|
93
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
# not an arrow, let's hope it's a narrow
|
|
103
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
# I guess you're out of luck!
|
|
113
|
-
puts "-none" if Chronic.debug
|
|
114
|
-
return nil
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
#--------------
|
|
118
|
-
|
|
119
|
-
def apply_endian_precedences(precedences)
|
|
120
|
-
date_defs = @definitions[:date]
|
|
121
|
-
|
|
122
|
-
# map the precedence array to indices on @definitions[:date]
|
|
123
|
-
indices = precedences.map { |e|
|
|
124
|
-
handler = instance_variable_get(endian_variable_name_for(e))
|
|
125
|
-
date_defs.index(handler)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
# swap the handlers if we discover they are at odds with the desired preferences
|
|
129
|
-
swap(date_defs, indices.first, indices.last) if indices.first > indices.last
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def endian_variable_name_for(e)
|
|
133
|
-
"@#{e.to_s}_endian_handler".to_sym
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# exchange two elements in an array
|
|
137
|
-
def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
|
|
138
|
-
|
|
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
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
#--------------
|
|
151
|
-
|
|
5
|
+
# Handle month/day
|
|
152
6
|
def handle_m_d(month, day, time_tokens, options) #:nodoc:
|
|
153
|
-
month.start =
|
|
7
|
+
month.start = Chronic.now
|
|
154
8
|
span = month.this(options[:context])
|
|
155
9
|
|
|
156
10
|
day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
|
|
@@ -158,10 +12,12 @@ module Chronic
|
|
|
158
12
|
day_or_time(day_start, time_tokens, options)
|
|
159
13
|
end
|
|
160
14
|
|
|
15
|
+
# Handle repeater-month-name/scalar-day
|
|
161
16
|
def handle_rmn_sd(tokens, options) #:nodoc:
|
|
162
17
|
handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
|
|
163
18
|
end
|
|
164
19
|
|
|
20
|
+
# Handle repeater-month-name/scalar-day with separator-on
|
|
165
21
|
def handle_rmn_sd_on(tokens, options) #:nodoc:
|
|
166
22
|
if tokens.size > 3
|
|
167
23
|
handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(ScalarDay).type, tokens[0..1], options)
|
|
@@ -170,10 +26,17 @@ module Chronic
|
|
|
170
26
|
end
|
|
171
27
|
end
|
|
172
28
|
|
|
29
|
+
# Handle repeater-month-name/ordinal-day
|
|
173
30
|
def handle_rmn_od(tokens, options) #:nodoc:
|
|
174
31
|
handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
|
|
175
32
|
end
|
|
176
33
|
|
|
34
|
+
# Handle ordinal-day/repeater-month-name
|
|
35
|
+
def handle_od_rmn(tokens, options) #:nodoc:
|
|
36
|
+
handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[0].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Handle repeater-month-name/ordinal-day with separator-on
|
|
177
40
|
def handle_rmn_od_on(tokens, options) #:nodoc:
|
|
178
41
|
if tokens.size > 3
|
|
179
42
|
handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
|
|
@@ -182,6 +45,7 @@ module Chronic
|
|
|
182
45
|
end
|
|
183
46
|
end
|
|
184
47
|
|
|
48
|
+
# Handle repeater-month-name/scalar-year
|
|
185
49
|
def handle_rmn_sy(tokens, options) #:nodoc:
|
|
186
50
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
187
51
|
year = tokens[1].get_tag(ScalarYear).type
|
|
@@ -201,11 +65,13 @@ module Chronic
|
|
|
201
65
|
end
|
|
202
66
|
end
|
|
203
67
|
|
|
68
|
+
# Handle generic timestamp
|
|
204
69
|
def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
|
|
205
|
-
t = Chronic.time_class.parse(
|
|
70
|
+
t = Chronic.time_class.parse(options[:text])
|
|
206
71
|
Span.new(t, t + 1)
|
|
207
72
|
end
|
|
208
73
|
|
|
74
|
+
# Handle repeater-month-name/scalar-day/scalar-year
|
|
209
75
|
def handle_rmn_sd_sy(tokens, options) #:nodoc:
|
|
210
76
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
211
77
|
day = tokens[1].get_tag(ScalarDay).type
|
|
@@ -221,6 +87,7 @@ module Chronic
|
|
|
221
87
|
end
|
|
222
88
|
end
|
|
223
89
|
|
|
90
|
+
# Handle repeater-month-name/ordinal-day/scalar-year
|
|
224
91
|
def handle_rmn_od_sy(tokens, options) #:nodoc:
|
|
225
92
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
226
93
|
day = tokens[1].get_tag(OrdinalDay).type
|
|
@@ -236,12 +103,30 @@ module Chronic
|
|
|
236
103
|
end
|
|
237
104
|
end
|
|
238
105
|
|
|
106
|
+
# Handle oridinal-day/repeater-month-name/scalar-year
|
|
107
|
+
def handle_od_rmn_sy(tokens, options) #:nodoc:
|
|
108
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
|
109
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
110
|
+
year = tokens[2].get_tag(ScalarYear).type
|
|
111
|
+
|
|
112
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
113
|
+
|
|
114
|
+
begin
|
|
115
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
116
|
+
day_or_time(day_start, time_tokens, options)
|
|
117
|
+
rescue ArgumentError
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Handle scalar-day/repeater-month-name/scalar-year
|
|
239
123
|
def handle_sd_rmn_sy(tokens, options) #:nodoc:
|
|
240
124
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
241
125
|
time_tokens = tokens.last(tokens.size - 3)
|
|
242
|
-
|
|
126
|
+
handle_rmn_sd_sy(new_tokens + time_tokens, options)
|
|
243
127
|
end
|
|
244
128
|
|
|
129
|
+
# Handle scalar-month/scalar-day/scalar-year (endian middle)
|
|
245
130
|
def handle_sm_sd_sy(tokens, options) #:nodoc:
|
|
246
131
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
247
132
|
day = tokens[1].get_tag(ScalarDay).type
|
|
@@ -257,18 +142,21 @@ module Chronic
|
|
|
257
142
|
end
|
|
258
143
|
end
|
|
259
144
|
|
|
145
|
+
# Handle scalar-day/scalar-month/scalar-year (endian little)
|
|
260
146
|
def handle_sd_sm_sy(tokens, options) #:nodoc:
|
|
261
147
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
262
148
|
time_tokens = tokens.last(tokens.size - 3)
|
|
263
|
-
|
|
149
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
264
150
|
end
|
|
265
151
|
|
|
152
|
+
# Handle scalar-year/scalar-month/scalar-day
|
|
266
153
|
def handle_sy_sm_sd(tokens, options) #:nodoc:
|
|
267
154
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
268
155
|
time_tokens = tokens.last(tokens.size - 3)
|
|
269
|
-
|
|
156
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
270
157
|
end
|
|
271
158
|
|
|
159
|
+
# Handle scalar-month/scalar-year
|
|
272
160
|
def handle_sm_sy(tokens, options) #:nodoc:
|
|
273
161
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
274
162
|
year = tokens[1].get_tag(ScalarYear).type
|
|
@@ -290,18 +178,21 @@ module Chronic
|
|
|
290
178
|
|
|
291
179
|
# anchors
|
|
292
180
|
|
|
181
|
+
# Handle repeaters
|
|
293
182
|
def handle_r(tokens, options) #:nodoc:
|
|
294
183
|
dd_tokens = dealias_and_disambiguate_times(tokens, options)
|
|
295
|
-
|
|
184
|
+
get_anchor(dd_tokens, options)
|
|
296
185
|
end
|
|
297
186
|
|
|
187
|
+
# Handle repeater/grabber/repeater
|
|
298
188
|
def handle_r_g_r(tokens, options) #:nodoc:
|
|
299
189
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
300
|
-
|
|
190
|
+
handle_r(new_tokens, options)
|
|
301
191
|
end
|
|
302
192
|
|
|
303
193
|
# arrows
|
|
304
194
|
|
|
195
|
+
# Handle scalar/repeater/pointer helper
|
|
305
196
|
def handle_srp(tokens, span, options) #:nodoc:
|
|
306
197
|
distance = tokens[0].get_tag(Scalar).type
|
|
307
198
|
repeater = tokens[1].get_tag(Repeater)
|
|
@@ -310,38 +201,29 @@ module Chronic
|
|
|
310
201
|
repeater.offset(span, distance, pointer)
|
|
311
202
|
end
|
|
312
203
|
|
|
204
|
+
# Handle scalar/repeater/pointer
|
|
313
205
|
def handle_s_r_p(tokens, options) #:nodoc:
|
|
314
206
|
repeater = tokens[1].get_tag(Repeater)
|
|
207
|
+
span = Span.new(Chronic.now, Chronic.now + 1)
|
|
315
208
|
|
|
316
|
-
|
|
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)
|
|
209
|
+
handle_srp(tokens, span, options)
|
|
331
210
|
end
|
|
332
211
|
|
|
212
|
+
# Handle pointer/scalar/repeater
|
|
333
213
|
def handle_p_s_r(tokens, options) #:nodoc:
|
|
334
214
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
335
|
-
|
|
215
|
+
handle_s_r_p(new_tokens, options)
|
|
336
216
|
end
|
|
337
217
|
|
|
218
|
+
# Handle scalar/repeater/pointer/anchor
|
|
338
219
|
def handle_s_r_p_a(tokens, options) #:nodoc:
|
|
339
220
|
anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
|
|
340
|
-
|
|
221
|
+
handle_srp(tokens, anchor_span, options)
|
|
341
222
|
end
|
|
342
223
|
|
|
343
224
|
# narrows
|
|
344
225
|
|
|
226
|
+
# Handle oridinal repeaters
|
|
345
227
|
def handle_orr(tokens, outer_span, options) #:nodoc:
|
|
346
228
|
repeater = tokens[1].get_tag(Repeater)
|
|
347
229
|
repeater.start = outer_span.begin - 1
|
|
@@ -357,11 +239,13 @@ module Chronic
|
|
|
357
239
|
span
|
|
358
240
|
end
|
|
359
241
|
|
|
242
|
+
# Handle ordinal/repeater/separator/repeater
|
|
360
243
|
def handle_o_r_s_r(tokens, options) #:nodoc:
|
|
361
244
|
outer_span = get_anchor([tokens[3]], options)
|
|
362
245
|
handle_orr(tokens[0..1], outer_span, options)
|
|
363
246
|
end
|
|
364
247
|
|
|
248
|
+
# Handle ordinal/repeater/grabber/repeater
|
|
365
249
|
def handle_o_r_g_r(tokens, options) #:nodoc:
|
|
366
250
|
outer_span = get_anchor(tokens[2..3], options)
|
|
367
251
|
handle_orr(tokens[0..1], outer_span, options)
|
|
@@ -369,11 +253,22 @@ module Chronic
|
|
|
369
253
|
|
|
370
254
|
# support methods
|
|
371
255
|
|
|
256
|
+
def day_or_time(day_start, time_tokens, options)
|
|
257
|
+
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
|
258
|
+
|
|
259
|
+
if !time_tokens.empty?
|
|
260
|
+
Chronic.now = outer_span.begin
|
|
261
|
+
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
262
|
+
else
|
|
263
|
+
outer_span
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
372
267
|
def get_anchor(tokens, options) #:nodoc:
|
|
373
268
|
grabber = Grabber.new(:this)
|
|
374
269
|
pointer = :future
|
|
375
270
|
|
|
376
|
-
repeaters =
|
|
271
|
+
repeaters = get_repeaters(tokens)
|
|
377
272
|
repeaters.size.times { tokens.pop }
|
|
378
273
|
|
|
379
274
|
if tokens.first && tokens.first.get_tag(Grabber)
|
|
@@ -382,7 +277,7 @@ module Chronic
|
|
|
382
277
|
end
|
|
383
278
|
|
|
384
279
|
head = repeaters.shift
|
|
385
|
-
head.start =
|
|
280
|
+
head.start = Chronic.now
|
|
386
281
|
|
|
387
282
|
case grabber.type
|
|
388
283
|
when :last
|
|
@@ -404,8 +299,8 @@ module Chronic
|
|
|
404
299
|
|
|
405
300
|
def get_repeaters(tokens) #:nodoc:
|
|
406
301
|
repeaters = []
|
|
407
|
-
|
|
408
|
-
|
|
302
|
+
tokens.each do |token|
|
|
303
|
+
if t = token.get_tag(Repeater)
|
|
409
304
|
repeaters << t
|
|
410
305
|
end
|
|
411
306
|
end
|
|
@@ -464,21 +359,6 @@ module Chronic
|
|
|
464
359
|
end
|
|
465
360
|
end
|
|
466
361
|
|
|
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
362
|
# handle ambiguous times if :ambiguous_time_range is specified
|
|
483
363
|
if options[:ambiguous_time_range] != :none
|
|
484
364
|
ttokens = []
|
|
@@ -499,24 +379,38 @@ module Chronic
|
|
|
499
379
|
|
|
500
380
|
end
|
|
501
381
|
|
|
502
|
-
class Handler
|
|
503
|
-
|
|
382
|
+
class Handler
|
|
383
|
+
# @return [Array] A list of patterns
|
|
384
|
+
attr_accessor :pattern
|
|
385
|
+
|
|
386
|
+
# @return [Symbol] The method which handles this list of patterns.
|
|
387
|
+
# This method should exist inside the {Handlers} module
|
|
388
|
+
attr_accessor :handler_method
|
|
504
389
|
|
|
390
|
+
# @param [Array] pattern A list of patterns to match tokens against
|
|
391
|
+
# @param [Symbol] handler_method The method to be invoked when patterns
|
|
392
|
+
# are matched. This method should exist inside the {Handlers} module
|
|
505
393
|
def initialize(pattern, handler_method)
|
|
506
394
|
@pattern = pattern
|
|
507
395
|
@handler_method = handler_method
|
|
508
396
|
end
|
|
509
397
|
|
|
398
|
+
# @param [#to_s] The snake_case name representing a Chronic constant
|
|
399
|
+
# @return [Class] The class represented by `name`
|
|
400
|
+
# @raise [NameError] Raises if this constant could not be found
|
|
510
401
|
def constantize(name)
|
|
511
|
-
|
|
512
|
-
::Chronic.module_eval(camel, __FILE__, __LINE__)
|
|
402
|
+
Chronic.const_get name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
|
|
513
403
|
end
|
|
514
404
|
|
|
405
|
+
# @param [Array] tokens
|
|
406
|
+
# @param [Hash] definitions
|
|
407
|
+
# @return [Boolean]
|
|
408
|
+
# @see Chronic.tokens_to_span
|
|
515
409
|
def match(tokens, definitions)
|
|
516
410
|
token_index = 0
|
|
517
411
|
@pattern.each do |element|
|
|
518
412
|
name = element.to_s
|
|
519
|
-
optional = name
|
|
413
|
+
optional = name[-1, 1] == '?'
|
|
520
414
|
name = name.chop if optional
|
|
521
415
|
if element.instance_of? Symbol
|
|
522
416
|
klass = constantize(name)
|
|
@@ -539,8 +433,10 @@ module Chronic
|
|
|
539
433
|
return true
|
|
540
434
|
end
|
|
541
435
|
|
|
436
|
+
# @param [Handler] The handler to compare
|
|
437
|
+
# @return [Boolean] True if these handlers match
|
|
542
438
|
def ==(other)
|
|
543
|
-
|
|
439
|
+
@pattern == other.pattern
|
|
544
440
|
end
|
|
545
441
|
end
|
|
546
442
|
|
data/lib/chronic/mini_date.rb
CHANGED
|
@@ -3,13 +3,17 @@ module Chronic
|
|
|
3
3
|
attr_accessor :month, :day
|
|
4
4
|
|
|
5
5
|
def initialize(month, day)
|
|
6
|
-
|
|
6
|
+
unless (1..12).include?(month)
|
|
7
|
+
raise(InvalidArgumentException, "1..12 are valid months")
|
|
8
|
+
end
|
|
9
|
+
|
|
7
10
|
@month = month
|
|
8
11
|
@day = day
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def is_between?(md_start, md_end)
|
|
12
|
-
return false if (@month==md_start.month && @month==md_end.month &&
|
|
15
|
+
return false if (@month == md_start.month && @month == md_end.month &&
|
|
16
|
+
(@day < md_start.day || @day > md_end.day))
|
|
13
17
|
return true if (@month == md_start.month and @day >= md_start.day) ||
|
|
14
18
|
(@month == md_end.month and @day <= md_end.day)
|
|
15
19
|
i = (md_start.month % 12) + 1
|
data/lib/chronic/ordinal.rb
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Ordinal < Tag
|
|
2
|
+
class Ordinal < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Ordinal tags to
|
|
5
|
+
# each token
|
|
6
|
+
#
|
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
9
|
+
# @return [Array] list of tokens
|
|
3
10
|
def self.scan(tokens, options)
|
|
4
|
-
tokens.
|
|
5
|
-
if t = scan_for_ordinals(
|
|
6
|
-
if t = scan_for_days(
|
|
11
|
+
tokens.each do |token|
|
|
12
|
+
if t = scan_for_ordinals(token) then token.tag(t) end
|
|
13
|
+
if t = scan_for_days(token) then token.tag(t) end
|
|
7
14
|
end
|
|
8
15
|
end
|
|
9
16
|
|
|
17
|
+
# @param [Token] token
|
|
18
|
+
# @return [Ordinal, nil]
|
|
10
19
|
def self.scan_for_ordinals(token)
|
|
11
20
|
Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
|
12
21
|
end
|
|
13
22
|
|
|
23
|
+
# @param [Token] token
|
|
24
|
+
# @return [OrdinalDay, nil]
|
|
14
25
|
def self.scan_for_days(token)
|
|
15
26
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
|
16
27
|
unless $1.to_i > 31 || $1.to_i < 1
|
data/lib/chronic/pointer.rb
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Pointer < Tag
|
|
2
|
+
class Pointer < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Pointer tags to
|
|
5
|
+
# each token
|
|
6
|
+
#
|
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
9
|
+
# @return [Array] list of tokens
|
|
3
10
|
def self.scan(tokens, options)
|
|
4
|
-
tokens.
|
|
5
|
-
if t = scan_for_all(
|
|
11
|
+
tokens.each do |token|
|
|
12
|
+
if t = scan_for_all(token) then token.tag(t) end
|
|
6
13
|
end
|
|
7
14
|
end
|
|
8
15
|
|
|
16
|
+
# @param [Token] token
|
|
17
|
+
# @return [Pointer, nil]
|
|
9
18
|
def self.scan_for_all(token)
|
|
10
19
|
scan_for token, self,
|
|
11
20
|
{
|
data/lib/chronic/repeater.rb
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
-
class Repeater < Tag
|
|
2
|
+
class Repeater < Tag
|
|
3
|
+
|
|
4
|
+
# Scan an Array of {Token}s and apply any necessary Repeater tags to
|
|
5
|
+
# each token
|
|
6
|
+
#
|
|
7
|
+
# @param [Array<Token>] tokens Array of tokens to scan
|
|
8
|
+
# @param [Hash] options Options specified in {Chronic.parse}
|
|
9
|
+
# @return [Array] list of tokens
|
|
3
10
|
def self.scan(tokens, options)
|
|
4
|
-
tokens.
|
|
5
|
-
if t = scan_for_season_names(
|
|
6
|
-
if t = scan_for_month_names(
|
|
7
|
-
if t = scan_for_day_names(
|
|
8
|
-
if t = scan_for_day_portions(
|
|
9
|
-
if t = scan_for_times(
|
|
10
|
-
if t = scan_for_units(
|
|
11
|
+
tokens.each do |token|
|
|
12
|
+
if t = scan_for_season_names(token) then token.tag(t); next end
|
|
13
|
+
if t = scan_for_month_names(token) then token.tag(t); next end
|
|
14
|
+
if t = scan_for_day_names(token) then token.tag(t); next end
|
|
15
|
+
if t = scan_for_day_portions(token) then token.tag(t); next end
|
|
16
|
+
if t = scan_for_times(token) then token.tag(t); next end
|
|
17
|
+
if t = scan_for_units(token) then token.tag(t); next end
|
|
11
18
|
end
|
|
12
19
|
end
|
|
13
20
|
|
|
21
|
+
# @param [Token] token
|
|
22
|
+
# @return [RepeaterSeasonName, nil]
|
|
14
23
|
def self.scan_for_season_names(token)
|
|
15
24
|
scan_for token, RepeaterSeasonName,
|
|
16
25
|
{
|
|
@@ -21,6 +30,8 @@ module Chronic
|
|
|
21
30
|
}
|
|
22
31
|
end
|
|
23
32
|
|
|
33
|
+
# @param [Token] token
|
|
34
|
+
# @return [RepeaterMonthName, nil]
|
|
24
35
|
def self.scan_for_month_names(token)
|
|
25
36
|
scan_for token, RepeaterMonthName,
|
|
26
37
|
{
|
|
@@ -39,6 +50,8 @@ module Chronic
|
|
|
39
50
|
}
|
|
40
51
|
end
|
|
41
52
|
|
|
53
|
+
# @param [Token] token
|
|
54
|
+
# @return [RepeaterDayName, nil]
|
|
42
55
|
def self.scan_for_day_names(token)
|
|
43
56
|
scan_for token, RepeaterDayName,
|
|
44
57
|
{
|
|
@@ -55,6 +68,8 @@ module Chronic
|
|
|
55
68
|
}
|
|
56
69
|
end
|
|
57
70
|
|
|
71
|
+
# @param [Token] token
|
|
72
|
+
# @return [RepeaterDayPortion, nil]
|
|
58
73
|
def self.scan_for_day_portions(token)
|
|
59
74
|
scan_for token, RepeaterDayPortion,
|
|
60
75
|
{
|
|
@@ -67,10 +82,14 @@ module Chronic
|
|
|
67
82
|
}
|
|
68
83
|
end
|
|
69
84
|
|
|
85
|
+
# @param [Token] token
|
|
86
|
+
# @return [RepeaterTime, nil]
|
|
70
87
|
def self.scan_for_times(token)
|
|
71
88
|
scan_for token, RepeaterTime, /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
|
|
72
89
|
end
|
|
73
90
|
|
|
91
|
+
# @param [Token] token
|
|
92
|
+
# @return [Repeater] A new instance of a subclass of Repeater
|
|
74
93
|
def self.scan_for_units(token)
|
|
75
94
|
{
|
|
76
95
|
/^years?$/ => :year,
|
|
@@ -100,7 +119,7 @@ module Chronic
|
|
|
100
119
|
|
|
101
120
|
# returns the width (in seconds or months) of this repeatable.
|
|
102
121
|
def width
|
|
103
|
-
raise("
|
|
122
|
+
raise("Repeater#width must be overridden in subclasses")
|
|
104
123
|
end
|
|
105
124
|
|
|
106
125
|
# returns the next occurance of this repeatable.
|