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