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