chronic 0.4.1 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gemtest +0 -0
- data/.gitignore +5 -0
- data/.yardopts +3 -0
- data/HISTORY.md +75 -2
- data/README.md +10 -5
- data/Rakefile +12 -106
- data/chronic.gemspec +12 -78
- data/lib/chronic/chronic.rb +271 -101
- data/lib/chronic/grabber.rb +12 -3
- data/lib/chronic/handler.rb +90 -0
- data/lib/chronic/handlers.rb +247 -295
- 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 +12 -13
- 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 +19 -20
- 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 +14 -5
- data/lib/chronic/token.rb +14 -4
- data/lib/chronic.rb +66 -89
- data/test/helper.rb +1 -1
- 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 +228 -117
- metadata +30 -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,109 @@
|
|
|
1
1
|
module Chronic
|
|
2
|
+
module Handlers
|
|
3
|
+
module_function
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# Handle month/day
|
|
6
|
+
def handle_m_d(month, day, time_tokens, options)
|
|
7
|
+
month.start = Chronic.now
|
|
8
|
+
span = month.this(options[:context])
|
|
9
|
+
year, month = span.begin.year, span.begin.month
|
|
10
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
day_or_time(day_start, time_tokens, options)
|
|
13
|
+
end
|
|
12
14
|
|
|
13
|
-
|
|
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
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
options[:endian_precedence].each do |e|
|
|
18
|
-
raise ChronicPain, "Unknown endian type: #{e.to_s}" unless instance_variable_defined?(endian_variable_name_for(e))
|
|
19
|
-
end
|
|
20
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Handler.new([:repeater_time, :repeater_day_portion?], nil)
|
|
24
|
-
],
|
|
25
|
-
|
|
26
|
-
:date => [
|
|
27
|
-
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
|
|
28
|
-
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
|
29
|
-
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
|
|
30
|
-
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
|
31
|
-
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
|
|
32
|
-
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
|
33
|
-
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
|
|
34
|
-
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
|
35
|
-
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
|
36
|
-
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
|
37
|
-
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
|
38
|
-
@middle_endian_handler,
|
|
39
|
-
@little_endian_handler,
|
|
40
|
-
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
|
41
|
-
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
|
|
42
|
-
],
|
|
43
|
-
|
|
44
|
-
# tonight at 7pm
|
|
45
|
-
:anchor => [
|
|
46
|
-
Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
47
|
-
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
|
48
|
-
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
|
|
49
|
-
],
|
|
50
|
-
|
|
51
|
-
# 3 weeks from now, in 2 months
|
|
52
|
-
:arrow => [
|
|
53
|
-
Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
|
54
|
-
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
|
55
|
-
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
|
|
56
|
-
],
|
|
57
|
-
|
|
58
|
-
# 3rd week in march
|
|
59
|
-
:narrow => [
|
|
60
|
-
Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
|
61
|
-
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
apply_endian_precedences(options[:endian_precedence])
|
|
66
|
-
|
|
67
|
-
@definitions
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def tokens_to_span(tokens, options) #:nodoc:
|
|
71
|
-
# maybe it's a specific date
|
|
72
|
-
|
|
73
|
-
definitions = self.definitions(options)
|
|
74
|
-
definitions[:date].each do |handler|
|
|
75
|
-
if handler.match(tokens, definitions)
|
|
76
|
-
puts "-date" if Chronic.debug
|
|
77
|
-
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
78
|
-
return self.send(handler.handler_method, good_tokens, options)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# I guess it's not a specific date, maybe it's just an anchor
|
|
22
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
23
|
+
end
|
|
83
24
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
25
|
+
# Handle repeater-month-name/scalar-day with separator-on
|
|
26
|
+
def handle_rmn_sd_on(tokens, options)
|
|
27
|
+
if tokens.size > 3
|
|
28
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
|
29
|
+
day = tokens[3].get_tag(ScalarDay).type
|
|
30
|
+
token_range = 0..1
|
|
31
|
+
else
|
|
32
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
33
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
34
|
+
token_range = 0..0
|
|
90
35
|
end
|
|
91
36
|
|
|
92
|
-
|
|
37
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
93
38
|
|
|
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
|
|
39
|
+
handle_m_d(month, day, tokens[token_range], options)
|
|
40
|
+
end
|
|
101
41
|
|
|
102
|
-
|
|
42
|
+
# Handle repeater-month-name/ordinal-day
|
|
43
|
+
def handle_rmn_od(tokens, options)
|
|
44
|
+
month = tokens[0].get_tag(RepeaterMonthName)
|
|
45
|
+
day = tokens[1].get_tag(OrdinalDay).type
|
|
103
46
|
|
|
104
|
-
|
|
105
|
-
if handler.match(tokens, definitions)
|
|
106
|
-
puts "-narrow" if Chronic.debug
|
|
107
|
-
#good_tokens = tokens.select { |o| !o.get_tag Separator }
|
|
108
|
-
return self.send(handler.handler_method, tokens, options)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
47
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
111
48
|
|
|
112
|
-
|
|
113
|
-
puts "-none" if Chronic.debug
|
|
114
|
-
return nil
|
|
49
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
115
50
|
end
|
|
116
51
|
|
|
117
|
-
|
|
52
|
+
# Handle ordinal-day/repeater-month-name
|
|
53
|
+
def handle_od_rmn(tokens, options)
|
|
54
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
55
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
|
118
56
|
|
|
119
|
-
|
|
120
|
-
date_defs = @definitions[:date]
|
|
57
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
121
58
|
|
|
122
|
-
|
|
123
|
-
indices = precedences.map { |e|
|
|
124
|
-
handler = instance_variable_get(endian_variable_name_for(e))
|
|
125
|
-
date_defs.index(handler)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
# swap the handlers if we discover they are at odds with the desired preferences
|
|
129
|
-
swap(date_defs, indices.first, indices.last) if indices.first > indices.last
|
|
59
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
130
60
|
end
|
|
131
61
|
|
|
132
|
-
def
|
|
133
|
-
|
|
134
|
-
|
|
62
|
+
def handle_sy_rmn_od(tokens, options)
|
|
63
|
+
year = tokens[0].get_tag(ScalarYear).type
|
|
64
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
65
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
66
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
135
67
|
|
|
136
|
-
|
|
137
|
-
def swap(arr, a, b); arr[a], arr[b] = arr[b], arr[a]; end
|
|
68
|
+
return if month_overflow?(year, month, day)
|
|
138
69
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
145
|
-
else
|
|
146
|
-
outer_span
|
|
70
|
+
begin
|
|
71
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
72
|
+
day_or_time(day_start, time_tokens, options)
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
nil
|
|
147
75
|
end
|
|
148
76
|
end
|
|
149
77
|
|
|
150
|
-
|
|
78
|
+
# Handle scalar-day/repeater-month-name
|
|
79
|
+
def handle_sd_rmn(tokens, options)
|
|
80
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
81
|
+
day = tokens[0].get_tag(ScalarDay).type
|
|
151
82
|
|
|
152
|
-
|
|
153
|
-
month.start = @now
|
|
154
|
-
span = month.this(options[:context])
|
|
155
|
-
|
|
156
|
-
day_start = Chronic.time_class.local(span.begin.year, span.begin.month, day)
|
|
83
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
157
84
|
|
|
158
|
-
|
|
85
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
159
86
|
end
|
|
160
87
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def handle_rmn_sd_on(tokens, options) #:nodoc:
|
|
88
|
+
# Handle repeater-month-name/ordinal-day with separator-on
|
|
89
|
+
def handle_rmn_od_on(tokens, options)
|
|
166
90
|
if tokens.size > 3
|
|
167
|
-
|
|
91
|
+
month = tokens[2].get_tag(RepeaterMonthName)
|
|
92
|
+
day = tokens[3].get_tag(OrdinalDay).type
|
|
93
|
+
token_range = 0..1
|
|
168
94
|
else
|
|
169
|
-
|
|
95
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
96
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
97
|
+
token_range = 0..0
|
|
170
98
|
end
|
|
171
|
-
end
|
|
172
99
|
|
|
173
|
-
|
|
174
|
-
handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
|
|
175
|
-
end
|
|
100
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
176
101
|
|
|
177
|
-
|
|
178
|
-
if tokens.size > 3
|
|
179
|
-
handle_m_d(tokens[2].get_tag(RepeaterMonthName), tokens[3].get_tag(OrdinalDay).type, tokens[0..1], options)
|
|
180
|
-
else
|
|
181
|
-
handle_m_d(tokens[1].get_tag(RepeaterMonthName), tokens[2].get_tag(OrdinalDay).type, tokens[0..0], options)
|
|
182
|
-
end
|
|
102
|
+
handle_m_d(month, day, tokens[token_range], options)
|
|
183
103
|
end
|
|
184
104
|
|
|
185
|
-
|
|
105
|
+
# Handle repeater-month-name/scalar-year
|
|
106
|
+
def handle_rmn_sy(tokens, options)
|
|
186
107
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
187
108
|
year = tokens[1].get_tag(ScalarYear).type
|
|
188
109
|
|
|
@@ -195,24 +116,34 @@ module Chronic
|
|
|
195
116
|
end
|
|
196
117
|
|
|
197
118
|
begin
|
|
198
|
-
|
|
119
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
|
120
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
|
199
121
|
rescue ArgumentError
|
|
200
122
|
nil
|
|
201
123
|
end
|
|
202
124
|
end
|
|
203
125
|
|
|
204
|
-
|
|
205
|
-
|
|
126
|
+
# Handle generic timestamp (ruby 1.8)
|
|
127
|
+
def handle_rdn_rmn_sd_t_tz_sy(tokens, options)
|
|
128
|
+
t = Chronic.time_class.parse(options[:text])
|
|
129
|
+
Span.new(t, t + 1)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Handle generic timestamp (ruby 1.9)
|
|
133
|
+
def handle_sy_sm_sd_t_tz(tokens, options)
|
|
134
|
+
t = Chronic.time_class.parse(options[:text])
|
|
206
135
|
Span.new(t, t + 1)
|
|
207
136
|
end
|
|
208
137
|
|
|
209
|
-
|
|
138
|
+
# Handle repeater-month-name/scalar-day/scalar-year
|
|
139
|
+
def handle_rmn_sd_sy(tokens, options)
|
|
210
140
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
211
141
|
day = tokens[1].get_tag(ScalarDay).type
|
|
212
142
|
year = tokens[2].get_tag(ScalarYear).type
|
|
213
|
-
|
|
214
143
|
time_tokens = tokens.last(tokens.size - 3)
|
|
215
144
|
|
|
145
|
+
return if month_overflow?(year, month, day)
|
|
146
|
+
|
|
216
147
|
begin
|
|
217
148
|
day_start = Chronic.time_class.local(year, month, day)
|
|
218
149
|
day_or_time(day_start, time_tokens, options)
|
|
@@ -221,13 +152,32 @@ module Chronic
|
|
|
221
152
|
end
|
|
222
153
|
end
|
|
223
154
|
|
|
224
|
-
|
|
155
|
+
# Handle repeater-month-name/ordinal-day/scalar-year
|
|
156
|
+
def handle_rmn_od_sy(tokens, options)
|
|
225
157
|
month = tokens[0].get_tag(RepeaterMonthName).index
|
|
226
158
|
day = tokens[1].get_tag(OrdinalDay).type
|
|
227
159
|
year = tokens[2].get_tag(ScalarYear).type
|
|
160
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
161
|
+
|
|
162
|
+
return if month_overflow?(year, month, day)
|
|
228
163
|
|
|
164
|
+
begin
|
|
165
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
166
|
+
day_or_time(day_start, time_tokens, options)
|
|
167
|
+
rescue ArgumentError
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Handle oridinal-day/repeater-month-name/scalar-year
|
|
173
|
+
def handle_od_rmn_sy(tokens, options)
|
|
174
|
+
day = tokens[0].get_tag(OrdinalDay).type
|
|
175
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
176
|
+
year = tokens[2].get_tag(ScalarYear).type
|
|
229
177
|
time_tokens = tokens.last(tokens.size - 3)
|
|
230
178
|
|
|
179
|
+
return if month_overflow?(year, month, day)
|
|
180
|
+
|
|
231
181
|
begin
|
|
232
182
|
day_start = Chronic.time_class.local(year, month, day)
|
|
233
183
|
day_or_time(day_start, time_tokens, options)
|
|
@@ -236,40 +186,46 @@ module Chronic
|
|
|
236
186
|
end
|
|
237
187
|
end
|
|
238
188
|
|
|
239
|
-
|
|
189
|
+
# Handle scalar-day/repeater-month-name/scalar-year
|
|
190
|
+
def handle_sd_rmn_sy(tokens, options)
|
|
240
191
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
241
192
|
time_tokens = tokens.last(tokens.size - 3)
|
|
242
|
-
|
|
193
|
+
handle_rmn_sd_sy(new_tokens + time_tokens, options)
|
|
243
194
|
end
|
|
244
195
|
|
|
245
|
-
|
|
196
|
+
# Handle scalar-month/scalar-day/scalar-year (endian middle)
|
|
197
|
+
def handle_sm_sd_sy(tokens, options)
|
|
246
198
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
247
199
|
day = tokens[1].get_tag(ScalarDay).type
|
|
248
200
|
year = tokens[2].get_tag(ScalarYear).type
|
|
249
|
-
|
|
250
201
|
time_tokens = tokens.last(tokens.size - 3)
|
|
251
202
|
|
|
203
|
+
return if month_overflow?(year, month, day)
|
|
204
|
+
|
|
252
205
|
begin
|
|
253
|
-
day_start = Chronic.time_class.local(year, month, day)
|
|
206
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
254
207
|
day_or_time(day_start, time_tokens, options)
|
|
255
208
|
rescue ArgumentError
|
|
256
209
|
nil
|
|
257
210
|
end
|
|
258
211
|
end
|
|
259
212
|
|
|
260
|
-
|
|
213
|
+
# Handle scalar-day/scalar-month/scalar-year (endian little)
|
|
214
|
+
def handle_sd_sm_sy(tokens, options)
|
|
261
215
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
262
216
|
time_tokens = tokens.last(tokens.size - 3)
|
|
263
|
-
|
|
217
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
264
218
|
end
|
|
265
219
|
|
|
266
|
-
|
|
220
|
+
# Handle scalar-year/scalar-month/scalar-day
|
|
221
|
+
def handle_sy_sm_sd(tokens, options)
|
|
267
222
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
268
223
|
time_tokens = tokens.last(tokens.size - 3)
|
|
269
|
-
|
|
224
|
+
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
270
225
|
end
|
|
271
226
|
|
|
272
|
-
|
|
227
|
+
# Handle scalar-month/scalar-year
|
|
228
|
+
def handle_sm_sy(tokens, options)
|
|
273
229
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
274
230
|
year = tokens[1].get_tag(ScalarYear).type
|
|
275
231
|
|
|
@@ -282,7 +238,42 @@ module Chronic
|
|
|
282
238
|
end
|
|
283
239
|
|
|
284
240
|
begin
|
|
285
|
-
|
|
241
|
+
end_time = Chronic.time_class.local(next_month_year, next_month_month)
|
|
242
|
+
Span.new(Chronic.time_class.local(year, month), end_time)
|
|
243
|
+
rescue ArgumentError
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Handle RepeaterDayName RepeaterMonthName OrdinalDay
|
|
249
|
+
def handle_rdn_rmn_od(tokens, options)
|
|
250
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
251
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
252
|
+
year = Chronic.now.year
|
|
253
|
+
|
|
254
|
+
return if month_overflow?(year, month.index, day)
|
|
255
|
+
|
|
256
|
+
begin
|
|
257
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
258
|
+
end_time = Chronic.time_class.local(year, month.index, day + 1)
|
|
259
|
+
Span.new(start_time, end_time)
|
|
260
|
+
rescue ArgumentError
|
|
261
|
+
nil
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay
|
|
266
|
+
def handle_rdn_rmn_sd(tokens, options)
|
|
267
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
268
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
269
|
+
year = Chronic.now.year
|
|
270
|
+
|
|
271
|
+
return if month_overflow?(year, month.index, day)
|
|
272
|
+
|
|
273
|
+
begin
|
|
274
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
275
|
+
end_time = Chronic.time_class.local(year, month.index, day + 1)
|
|
276
|
+
Span.new(start_time, end_time)
|
|
286
277
|
rescue ArgumentError
|
|
287
278
|
nil
|
|
288
279
|
end
|
|
@@ -290,19 +281,22 @@ module Chronic
|
|
|
290
281
|
|
|
291
282
|
# anchors
|
|
292
283
|
|
|
293
|
-
|
|
284
|
+
# Handle repeaters
|
|
285
|
+
def handle_r(tokens, options)
|
|
294
286
|
dd_tokens = dealias_and_disambiguate_times(tokens, options)
|
|
295
|
-
|
|
287
|
+
get_anchor(dd_tokens, options)
|
|
296
288
|
end
|
|
297
289
|
|
|
298
|
-
|
|
290
|
+
# Handle repeater/grabber/repeater
|
|
291
|
+
def handle_r_g_r(tokens, options)
|
|
299
292
|
new_tokens = [tokens[1], tokens[0], tokens[2]]
|
|
300
|
-
|
|
293
|
+
handle_r(new_tokens, options)
|
|
301
294
|
end
|
|
302
295
|
|
|
303
296
|
# arrows
|
|
304
297
|
|
|
305
|
-
|
|
298
|
+
# Handle scalar/repeater/pointer helper
|
|
299
|
+
def handle_srp(tokens, span, options)
|
|
306
300
|
distance = tokens[0].get_tag(Scalar).type
|
|
307
301
|
repeater = tokens[1].get_tag(Repeater)
|
|
308
302
|
pointer = tokens[2].get_tag(Pointer).type
|
|
@@ -310,125 +304,138 @@ module Chronic
|
|
|
310
304
|
repeater.offset(span, distance, pointer)
|
|
311
305
|
end
|
|
312
306
|
|
|
313
|
-
|
|
307
|
+
# Handle scalar/repeater/pointer
|
|
308
|
+
def handle_s_r_p(tokens, options)
|
|
314
309
|
repeater = tokens[1].get_tag(Repeater)
|
|
310
|
+
span = Span.new(Chronic.now, Chronic.now + 1)
|
|
315
311
|
|
|
316
|
-
|
|
317
|
-
# case true
|
|
318
|
-
# when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
|
|
319
|
-
# self.parse("this hour", :guess => false, :now => @now)
|
|
320
|
-
# when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
|
|
321
|
-
# self.parse("this minute", :guess => false, :now => @now)
|
|
322
|
-
# when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
|
|
323
|
-
# self.parse("this second", :guess => false, :now => @now)
|
|
324
|
-
# else
|
|
325
|
-
# raise(ChronicPain, "Invalid repeater: #{repeater.class}")
|
|
326
|
-
# end
|
|
327
|
-
|
|
328
|
-
span = Span.new(@now, @now + 1)
|
|
329
|
-
|
|
330
|
-
self.handle_srp(tokens, span, options)
|
|
312
|
+
handle_srp(tokens, span, options)
|
|
331
313
|
end
|
|
332
314
|
|
|
333
|
-
|
|
315
|
+
# Handle pointer/scalar/repeater
|
|
316
|
+
def handle_p_s_r(tokens, options)
|
|
334
317
|
new_tokens = [tokens[1], tokens[2], tokens[0]]
|
|
335
|
-
|
|
318
|
+
handle_s_r_p(new_tokens, options)
|
|
336
319
|
end
|
|
337
320
|
|
|
338
|
-
|
|
321
|
+
# Handle scalar/repeater/pointer/anchor
|
|
322
|
+
def handle_s_r_p_a(tokens, options)
|
|
339
323
|
anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
|
|
340
|
-
|
|
324
|
+
handle_srp(tokens, anchor_span, options)
|
|
341
325
|
end
|
|
342
326
|
|
|
343
327
|
# narrows
|
|
344
328
|
|
|
345
|
-
|
|
329
|
+
# Handle oridinal repeaters
|
|
330
|
+
def handle_orr(tokens, outer_span, options)
|
|
346
331
|
repeater = tokens[1].get_tag(Repeater)
|
|
347
332
|
repeater.start = outer_span.begin - 1
|
|
348
333
|
ordinal = tokens[0].get_tag(Ordinal).type
|
|
349
334
|
span = nil
|
|
335
|
+
|
|
350
336
|
ordinal.times do
|
|
351
337
|
span = repeater.next(:future)
|
|
352
|
-
|
|
338
|
+
|
|
339
|
+
if span.begin >= outer_span.end
|
|
353
340
|
span = nil
|
|
354
341
|
break
|
|
355
342
|
end
|
|
356
343
|
end
|
|
344
|
+
|
|
357
345
|
span
|
|
358
346
|
end
|
|
359
347
|
|
|
360
|
-
|
|
348
|
+
# Handle ordinal/repeater/separator/repeater
|
|
349
|
+
def handle_o_r_s_r(tokens, options)
|
|
361
350
|
outer_span = get_anchor([tokens[3]], options)
|
|
362
351
|
handle_orr(tokens[0..1], outer_span, options)
|
|
363
352
|
end
|
|
364
353
|
|
|
365
|
-
|
|
354
|
+
# Handle ordinal/repeater/grabber/repeater
|
|
355
|
+
def handle_o_r_g_r(tokens, options)
|
|
366
356
|
outer_span = get_anchor(tokens[2..3], options)
|
|
367
357
|
handle_orr(tokens[0..1], outer_span, options)
|
|
368
358
|
end
|
|
369
359
|
|
|
370
360
|
# support methods
|
|
371
361
|
|
|
372
|
-
def
|
|
362
|
+
def day_or_time(day_start, time_tokens, options)
|
|
363
|
+
outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
|
|
364
|
+
|
|
365
|
+
if !time_tokens.empty?
|
|
366
|
+
Chronic.now = outer_span.begin
|
|
367
|
+
get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
|
|
368
|
+
else
|
|
369
|
+
outer_span
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def get_anchor(tokens, options)
|
|
373
374
|
grabber = Grabber.new(:this)
|
|
374
375
|
pointer = :future
|
|
375
376
|
|
|
376
|
-
repeaters =
|
|
377
|
+
repeaters = get_repeaters(tokens)
|
|
377
378
|
repeaters.size.times { tokens.pop }
|
|
378
379
|
|
|
379
380
|
if tokens.first && tokens.first.get_tag(Grabber)
|
|
380
|
-
grabber = tokens.
|
|
381
|
-
tokens.pop
|
|
381
|
+
grabber = tokens.shift.get_tag(Grabber)
|
|
382
382
|
end
|
|
383
383
|
|
|
384
384
|
head = repeaters.shift
|
|
385
|
-
head.start =
|
|
385
|
+
head.start = Chronic.now
|
|
386
386
|
|
|
387
387
|
case grabber.type
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
when :last
|
|
389
|
+
outer_span = head.next(:past)
|
|
390
|
+
when :this
|
|
391
|
+
if options[:context] != :past and repeaters.size > 0
|
|
392
|
+
outer_span = head.this(:none)
|
|
393
|
+
else
|
|
394
|
+
outer_span = head.this(options[:context])
|
|
395
|
+
end
|
|
396
|
+
when :next
|
|
397
|
+
outer_span = head.next(:future)
|
|
398
|
+
else
|
|
399
|
+
raise ChronicPain, "Invalid grabber"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
if Chronic.debug
|
|
403
|
+
puts "Handler-class: #{head.class}"
|
|
404
|
+
puts "--#{outer_span}"
|
|
399
405
|
end
|
|
400
406
|
|
|
401
|
-
puts "--#{outer_span}" if Chronic.debug
|
|
402
407
|
find_within(repeaters, outer_span, pointer)
|
|
403
408
|
end
|
|
404
409
|
|
|
405
|
-
def get_repeaters(tokens)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
410
|
+
def get_repeaters(tokens)
|
|
411
|
+
tokens.map { |token| token.get_tag(Repeater) }.compact.sort.reverse
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def month_overflow?(year, month, day)
|
|
415
|
+
if Date.leap?(year)
|
|
416
|
+
day > RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
|
|
417
|
+
else
|
|
418
|
+
day > RepeaterMonth::MONTH_DAYS[month - 1]
|
|
411
419
|
end
|
|
412
|
-
repeaters.sort.reverse
|
|
413
420
|
end
|
|
414
421
|
|
|
415
422
|
# Recursively finds repeaters within other repeaters.
|
|
416
423
|
# Returns a Span representing the innermost time span
|
|
417
424
|
# or nil if no repeater union could be found
|
|
418
|
-
def find_within(tags, span, pointer)
|
|
425
|
+
def find_within(tags, span, pointer)
|
|
419
426
|
puts "--#{span}" if Chronic.debug
|
|
420
427
|
return span if tags.empty?
|
|
421
428
|
|
|
422
|
-
head
|
|
423
|
-
head.start = pointer == :future ? span.begin : span.end
|
|
429
|
+
head = tags.shift
|
|
430
|
+
head.start = (pointer == :future ? span.begin : span.end)
|
|
424
431
|
h = head.this(:none)
|
|
425
432
|
|
|
426
433
|
if span.cover?(h.begin) || span.cover?(h.end)
|
|
427
|
-
find_within(
|
|
434
|
+
find_within(tags, h, pointer)
|
|
428
435
|
end
|
|
429
436
|
end
|
|
430
437
|
|
|
431
|
-
def dealias_and_disambiguate_times(tokens, options)
|
|
438
|
+
def dealias_and_disambiguate_times(tokens, options)
|
|
432
439
|
# handle aliases of am/pm
|
|
433
440
|
# 5:00 in the morning -> 5:00 am
|
|
434
441
|
# 7:00 in the evening -> 7:00 pm
|
|
@@ -449,49 +456,39 @@ module Chronic
|
|
|
449
456
|
end
|
|
450
457
|
end
|
|
451
458
|
|
|
452
|
-
if
|
|
459
|
+
if day_portion_index && time_index
|
|
453
460
|
t1 = tokens[day_portion_index]
|
|
454
461
|
t1tag = t1.get_tag(RepeaterDayPortion)
|
|
455
462
|
|
|
456
|
-
|
|
463
|
+
case t1tag.type
|
|
464
|
+
when :morning
|
|
457
465
|
puts '--morning->am' if Chronic.debug
|
|
458
466
|
t1.untag(RepeaterDayPortion)
|
|
459
467
|
t1.tag(RepeaterDayPortion.new(:am))
|
|
460
|
-
|
|
468
|
+
when :afternoon, :evening, :night
|
|
461
469
|
puts "--#{t1tag.type}->pm" if Chronic.debug
|
|
462
470
|
t1.untag(RepeaterDayPortion)
|
|
463
471
|
t1.tag(RepeaterDayPortion.new(:pm))
|
|
464
472
|
end
|
|
465
473
|
end
|
|
466
474
|
|
|
467
|
-
# tokens.each_with_index do |t0, i|
|
|
468
|
-
# t1 = tokens[i + 1]
|
|
469
|
-
# if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
|
|
470
|
-
# if [:morning].include?(t1tag.type)
|
|
471
|
-
# puts '--morning->am' if Chronic.debug
|
|
472
|
-
# t1.untag(RepeaterDayPortion)
|
|
473
|
-
# t1.tag(RepeaterDayPortion.new(:am))
|
|
474
|
-
# elsif [:afternoon, :evening, :night].include?(t1tag.type)
|
|
475
|
-
# puts "--#{t1tag.type}->pm" if Chronic.debug
|
|
476
|
-
# t1.untag(RepeaterDayPortion)
|
|
477
|
-
# t1.tag(RepeaterDayPortion.new(:pm))
|
|
478
|
-
# end
|
|
479
|
-
# end
|
|
480
|
-
# end
|
|
481
|
-
|
|
482
475
|
# handle ambiguous times if :ambiguous_time_range is specified
|
|
483
476
|
if options[:ambiguous_time_range] != :none
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
477
|
+
ambiguous_tokens = []
|
|
478
|
+
|
|
479
|
+
tokens.each_with_index do |token, i|
|
|
480
|
+
ambiguous_tokens << token
|
|
481
|
+
next_token = tokens[i + 1]
|
|
482
|
+
|
|
483
|
+
if token.get_tag(RepeaterTime) && token.get_tag(RepeaterTime).type.ambiguous? && (!next_token || !next_token.get_tag(RepeaterDayPortion))
|
|
489
484
|
distoken = Token.new('disambiguator')
|
|
485
|
+
|
|
490
486
|
distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
|
|
491
|
-
|
|
487
|
+
ambiguous_tokens << distoken
|
|
492
488
|
end
|
|
493
489
|
end
|
|
494
|
-
|
|
490
|
+
|
|
491
|
+
tokens = ambiguous_tokens
|
|
495
492
|
end
|
|
496
493
|
|
|
497
494
|
tokens
|
|
@@ -499,49 +496,4 @@ module Chronic
|
|
|
499
496
|
|
|
500
497
|
end
|
|
501
498
|
|
|
502
|
-
class Handler #:nodoc:
|
|
503
|
-
attr_accessor :pattern, :handler_method
|
|
504
|
-
|
|
505
|
-
def initialize(pattern, handler_method)
|
|
506
|
-
@pattern = pattern
|
|
507
|
-
@handler_method = handler_method
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
def constantize(name)
|
|
511
|
-
camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
|
|
512
|
-
::Chronic.module_eval(camel, __FILE__, __LINE__)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
def match(tokens, definitions)
|
|
516
|
-
token_index = 0
|
|
517
|
-
@pattern.each do |element|
|
|
518
|
-
name = element.to_s
|
|
519
|
-
optional = name.reverse[0..0] == '?'
|
|
520
|
-
name = name.chop if optional
|
|
521
|
-
if element.instance_of? Symbol
|
|
522
|
-
klass = constantize(name)
|
|
523
|
-
match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
|
|
524
|
-
return false if !match && !optional
|
|
525
|
-
(token_index += 1; next) if match
|
|
526
|
-
next if !match && optional
|
|
527
|
-
elsif element.instance_of? String
|
|
528
|
-
return true if optional && token_index == tokens.size
|
|
529
|
-
sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
|
|
530
|
-
sub_handlers.each do |sub_handler|
|
|
531
|
-
return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
|
|
532
|
-
end
|
|
533
|
-
return false
|
|
534
|
-
else
|
|
535
|
-
raise(ChronicPain, "Invalid match type: #{element.class}")
|
|
536
|
-
end
|
|
537
|
-
end
|
|
538
|
-
return false if token_index != tokens.size
|
|
539
|
-
return true
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
def ==(other)
|
|
543
|
-
self.pattern == other.pattern
|
|
544
|
-
end
|
|
545
|
-
end
|
|
546
|
-
|
|
547
499
|
end
|